1# frozen_string_literal: false
2#
3# server.rb -- GenericServer Class
4#
5# Author: IPR -- Internet Programming with Ruby -- writers
6# Copyright (c) 2000, 2001 TAKAHASHI Masayoshi, GOTOU Yuuzou
7# Copyright (c) 2002 Internet Programming with Ruby writers. All rights
8# reserved.
9#
10# $IPR: server.rb,v 1.62 2003/07/22 19:20:43 gotoyuzo Exp $
11
12require 'socket'
13require_relative 'config'
14require_relative 'log'
15
16module WEBrick
17
18  ##
19  # Server error exception
20
21  class ServerError < StandardError; end
22
23  ##
24  # Base server class
25
26  class SimpleServer
27
28    ##
29    # A SimpleServer only yields when you start it
30
31    def SimpleServer.start
32      yield
33    end
34  end
35
36  ##
37  # A generic module for daemonizing a process
38
39  class Daemon
40
41    ##
42    # Performs the standard operations for daemonizing a process.  Runs a
43    # block, if given.
44
45    def Daemon.start
46      Process.daemon
47      File.umask(0)
48      yield if block_given?
49    end
50  end
51
52  ##
53  # Base TCP server class.  You must subclass GenericServer and provide a #run
54  # method.
55
56  class GenericServer
57
58    ##
59    # The server status.  One of :Stop, :Running or :Shutdown
60
61    attr_reader :status
62
63    ##
64    # The server configuration
65
66    attr_reader :config
67
68    ##
69    # The server logger.  This is independent from the HTTP access log.
70
71    attr_reader :logger
72
73    ##
74    # Tokens control the number of outstanding clients.  The
75    # <code>:MaxClients</code> configuration sets this.
76
77    attr_reader :tokens
78
79    ##
80    # Sockets listening for connections.
81
82    attr_reader :listeners
83
84    ##
85    # Creates a new generic server from +config+.  The default configuration
86    # comes from +default+.
87
88    def initialize(config={}, default=Config::General)
89      @config = default.dup.update(config)
90      @status = :Stop
91      @config[:Logger] ||= Log::new
92      @logger = @config[:Logger]
93
94      @tokens = Thread::SizedQueue.new(@config[:MaxClients])
95      @config[:MaxClients].times{ @tokens.push(nil) }
96
97      webrickv = WEBrick::VERSION
98      rubyv = "#{RUBY_VERSION} (#{RUBY_RELEASE_DATE}) [#{RUBY_PLATFORM}]"
99      @logger.info("WEBrick #{webrickv}")
100      @logger.info("ruby #{rubyv}")
101
102      @listeners = []
103      @shutdown_pipe = nil
104      unless @config[:DoNotListen]
105        if @config[:Listen]
106          warn(":Listen option is deprecated; use GenericServer#listen", uplevel: 1)
107        end
108        listen(@config[:BindAddress], @config[:Port])
109        if @config[:Port] == 0
110          @config[:Port] = @listeners[0].addr[1]
111        end
112      end
113    end
114
115    ##
116    # Retrieves +key+ from the configuration
117
118    def [](key)
119      @config[key]
120    end
121
122    ##
123    # Adds listeners from +address+ and +port+ to the server.  See
124    # WEBrick::Utils::create_listeners for details.
125
126    def listen(address, port)
127      @listeners += Utils::create_listeners(address, port)
128    end
129
130    ##
131    # Starts the server and runs the +block+ for each connection.  This method
132    # does not return until the server is stopped from a signal handler or
133    # another thread using #stop or #shutdown.
134    #
135    # If the block raises a subclass of StandardError the exception is logged
136    # and ignored.  If an IOError or Errno::EBADF exception is raised the
137    # exception is ignored.  If an Exception subclass is raised the exception
138    # is logged and re-raised which stops the server.
139    #
140    # To completely shut down a server call #shutdown from ensure:
141    #
142    #   server = WEBrick::GenericServer.new
143    #   # or WEBrick::HTTPServer.new
144    #
145    #   begin
146    #     server.start
147    #   ensure
148    #     server.shutdown
149    #   end
150
151    def start(&block)
152      raise ServerError, "already started." if @status != :Stop
153      server_type = @config[:ServerType] || SimpleServer
154
155      setup_shutdown_pipe
156
157      server_type.start{
158        @logger.info \
159          "#{self.class}#start: pid=#{$$} port=#{@config[:Port]}"
160        @status = :Running
161        call_callback(:StartCallback)
162
163        shutdown_pipe = @shutdown_pipe
164
165        thgroup = ThreadGroup.new
166        begin
167          while @status == :Running
168            begin
169              sp = shutdown_pipe[0]
170              if svrs = IO.select([sp, *@listeners])
171                if svrs[0].include? sp
172                  # swallow shutdown pipe
173                  buf = String.new
174                  nil while String ===
175                            sp.read_nonblock([sp.nread, 8].max, buf, exception: false)
176                  break
177                end
178                svrs[0].each{|svr|
179                  @tokens.pop          # blocks while no token is there.
180                  if sock = accept_client(svr)
181                    unless config[:DoNotReverseLookup].nil?
182                      sock.do_not_reverse_lookup = !!config[:DoNotReverseLookup]
183                    end
184                    th = start_thread(sock, &block)
185                    th[:WEBrickThread] = true
186                    thgroup.add(th)
187                  else
188                    @tokens.push(nil)
189                  end
190                }
191              end
192            rescue Errno::EBADF, Errno::ENOTSOCK, IOError => ex
193              # if the listening socket was closed in GenericServer#shutdown,
194              # IO::select raise it.
195            rescue StandardError => ex
196              msg = "#{ex.class}: #{ex.message}\n\t#{ex.backtrace[0]}"
197              @logger.error msg
198            rescue Exception => ex
199              @logger.fatal ex
200              raise
201            end
202          end
203        ensure
204          cleanup_shutdown_pipe(shutdown_pipe)
205          cleanup_listener
206          @status = :Shutdown
207          @logger.info "going to shutdown ..."
208          thgroup.list.each{|th| th.join if th[:WEBrickThread] }
209          call_callback(:StopCallback)
210          @logger.info "#{self.class}#start done."
211          @status = :Stop
212        end
213      }
214    end
215
216    ##
217    # Stops the server from accepting new connections.
218
219    def stop
220      if @status == :Running
221        @status = :Shutdown
222      end
223
224      alarm_shutdown_pipe {|f| f.write_nonblock("\0")}
225    end
226
227    ##
228    # Shuts down the server and all listening sockets.  New listeners must be
229    # provided to restart the server.
230
231    def shutdown
232      stop
233
234      alarm_shutdown_pipe(&:close)
235    end
236
237    ##
238    # You must subclass GenericServer and implement \#run which accepts a TCP
239    # client socket
240
241    def run(sock)
242      @logger.fatal "run() must be provided by user."
243    end
244
245    private
246
247    # :stopdoc:
248
249    ##
250    # Accepts a TCP client socket from the TCP server socket +svr+ and returns
251    # the client socket.
252
253    def accept_client(svr)
254      case sock = svr.to_io.accept_nonblock(exception: false)
255      when :wait_readable
256        nil
257      else
258        if svr.respond_to?(:start_immediately)
259          sock = OpenSSL::SSL::SSLSocket.new(sock, ssl_context)
260          sock.sync_close = true
261          # we cannot do OpenSSL::SSL::SSLSocket#accept here because
262          # a slow client can prevent us from accepting connections
263          # from other clients
264        end
265        sock
266      end
267    rescue Errno::ECONNRESET, Errno::ECONNABORTED,
268           Errno::EPROTO, Errno::EINVAL
269      nil
270    rescue StandardError => ex
271      msg = "#{ex.class}: #{ex.message}\n\t#{ex.backtrace[0]}"
272      @logger.error msg
273      nil
274    end
275
276    ##
277    # Starts a server thread for the client socket +sock+ that runs the given
278    # +block+.
279    #
280    # Sets the socket to the <code>:WEBrickSocket</code> thread local variable
281    # in the thread.
282    #
283    # If any errors occur in the block they are logged and handled.
284
285    def start_thread(sock, &block)
286      Thread.start{
287        begin
288          Thread.current[:WEBrickSocket] = sock
289          begin
290            addr = sock.peeraddr
291            @logger.debug "accept: #{addr[3]}:#{addr[1]}"
292          rescue SocketError
293            @logger.debug "accept: <address unknown>"
294            raise
295          end
296          if sock.respond_to?(:sync_close=) && @config[:SSLStartImmediately]
297            WEBrick::Utils.timeout(@config[:RequestTimeout]) do
298              begin
299                sock.accept # OpenSSL::SSL::SSLSocket#accept
300              rescue Errno::ECONNRESET, Errno::ECONNABORTED,
301                     Errno::EPROTO, Errno::EINVAL
302                Thread.exit
303              end
304            end
305          end
306          call_callback(:AcceptCallback, sock)
307          block ? block.call(sock) : run(sock)
308        rescue Errno::ENOTCONN
309          @logger.debug "Errno::ENOTCONN raised"
310        rescue ServerError => ex
311          msg = "#{ex.class}: #{ex.message}\n\t#{ex.backtrace[0]}"
312          @logger.error msg
313        rescue Exception => ex
314          @logger.error ex
315        ensure
316          @tokens.push(nil)
317          Thread.current[:WEBrickSocket] = nil
318          if addr
319            @logger.debug "close: #{addr[3]}:#{addr[1]}"
320          else
321            @logger.debug "close: <address unknown>"
322          end
323          sock.close
324        end
325      }
326    end
327
328    ##
329    # Calls the callback +callback_name+ from the configuration with +args+
330
331    def call_callback(callback_name, *args)
332      @config[callback_name]&.call(*args)
333    end
334
335    def setup_shutdown_pipe
336      return @shutdown_pipe ||= IO.pipe
337    end
338
339    def cleanup_shutdown_pipe(shutdown_pipe)
340      @shutdown_pipe = nil
341      shutdown_pipe&.each(&:close)
342    end
343
344    def alarm_shutdown_pipe
345      _, pipe = @shutdown_pipe # another thread may modify @shutdown_pipe.
346      if pipe
347        if !pipe.closed?
348          begin
349            yield pipe
350          rescue IOError # closed by another thread.
351          end
352        end
353      end
354    end
355
356    def cleanup_listener
357      @listeners.each{|s|
358        if @logger.debug?
359          addr = s.addr
360          @logger.debug("close TCPSocket(#{addr[2]}, #{addr[1]})")
361        end
362        begin
363          s.shutdown
364        rescue Errno::ENOTCONN
365          # when `Errno::ENOTCONN: Socket is not connected' on some platforms,
366          # call #close instead of #shutdown.
367          # (ignore @config[:ShutdownSocketWithoutClose])
368          s.close
369        else
370          unless @config[:ShutdownSocketWithoutClose]
371            s.close
372          end
373        end
374      }
375      @listeners.clear
376    end
377  end    # end of GenericServer
378end
379