123456789_123456789_123456789_123456789_123456789_

Class: WEBrick::GenericServer

Relationships & Source Files
Extension / Inclusion / Inheritance Descendants
Subclasses:
Inherits: Object
Defined in: lib/webrick/ssl.rb,
lib/webrick/server.rb

Class Method Summary

Instance Attribute Summary

  • #config readonly

    The server configuration.

  • #listeners readonly

    Sockets listening for connections.

  • #logger readonly

    The server logger.

  • #status readonly

    The server status.

  • #tokens readonly

    Tokens control the number of outstanding clients.

Instance Method Summary

Constructor Details

.new(config = {}, default = Config::General) ⇒ GenericServer

Creates a new generic server from #config. The default configuration comes from default.

[ GitHub ]

  
# File 'lib/webrick/server.rb', line 88

def initialize(config={}, default=Config::General)
  @config = default.dup.update(config)
  @status = :Stop
  @config[:Logger] ||= Log::new
  @logger = @config[:Logger]

  @tokens = Thread::SizedQueue.new(@config[:MaxClients])
  @config[:MaxClients].times{ @tokens.push(nil) }

  webrickv = WEBrick::VERSION
  rubyv = "#{RUBY_VERSION} (#{RUBY_RELEASE_DATE}) [#{RUBY_PLATFORM}]"
  @logger.info("WEBrick #{webrickv}")
  @logger.info("ruby #{rubyv}")

  @listeners = []
  @shutdown_pipe = nil
  unless @config[:DoNotListen]
    if @config[:Listen]
      warn(":Listen option is deprecated; use GenericServer#listen", uplevel: 1)
    end
    listen(@config[:BindAddress], @config[:Port])
    if @config[:Port] == 0
      @config[:Port] = @listeners[0].addr[1]
    end
  end
end

Instance Attribute Details

#config (readonly)

The server configuration

[ GitHub ]

  
# File 'lib/webrick/server.rb', line 66

attr_reader :config

#listeners (readonly)

Sockets listening for connections.

[ GitHub ]

  
# File 'lib/webrick/server.rb', line 82

attr_reader :listeners

#logger (readonly)

The server logger. This is independent from the HTTP access log.

[ GitHub ]

  
# File 'lib/webrick/server.rb', line 71

attr_reader :logger

#status (readonly)

The server status. One of :Stop, :Running or :Shutdown

[ GitHub ]

  
# File 'lib/webrick/server.rb', line 61

attr_reader :status

#tokens (readonly)

Tokens control the number of outstanding clients. The :MaxClients configuration sets this.

[ GitHub ]

  
# File 'lib/webrick/server.rb', line 77

attr_reader :tokens

Instance Method Details

#[](key)

Retrieves key from the configuration

[ GitHub ]

  
# File 'lib/webrick/server.rb', line 118

def [](key)
  @config[key]
end

#accept_client(svr) (private)

This method is for internal use only.

Accepts a TCP client socket from the TCP server socket svr and returns the client socket.

[ GitHub ]

  
# File 'lib/webrick/server.rb', line 253

def accept_client(svr)
  case sock = svr.to_io.accept_nonblock(exception: false)
  when :wait_readable
    nil
  else
    if svr.respond_to?(:start_immediately)
      sock = OpenSSL::SSL::SSLSocket.new(sock, ssl_context)
      sock.sync_close = true
      # we cannot do OpenSSL::SSL::SSLSocket#accept here because
      # a slow client can prevent us from accepting connections
      # from other clients
    end
    sock
  end
rescue Errno::ECONNRESET, Errno::ECONNABORTED,
       Errno::EPROTO, Errno::EINVAL
  nil
rescue StandardError => ex
  msg = "#{ex.class}: #{ex.message}\n\t#{ex.backtrace[0]}"
  @logger.error msg
  nil
end

#alarm_shutdown_pipe (private)

This method is for internal use only.
[ GitHub ]

  
# File 'lib/webrick/server.rb', line 344

def alarm_shutdown_pipe
  _, pipe = @shutdown_pipe # another thread may modify @shutdown_pipe.
  if pipe
    if !pipe.closed?
      begin
        yield pipe
      rescue IOError # closed by another thread.
      end
    end
  end
end

#call_callback(callback_name, *args) (private)

This method is for internal use only.

Calls the callback callback_name from the configuration with args

[ GitHub ]

  
# File 'lib/webrick/server.rb', line 331

def call_callback(callback_name, *args)
  @config[callback_name]&.call(*args)
end

#cleanup_listener (private)

This method is for internal use only.
[ GitHub ]

  
# File 'lib/webrick/server.rb', line 356

def cleanup_listener
  @listeners.each{|s|
    if @logger.debug?
      addr = s.addr
      @logger.debug("close TCPSocket(#{addr[2]}, #{addr[1]})")
    end
    begin
      s.shutdown
    rescue Errno::ENOTCONN
      # when `Errno::ENOTCONN: Socket is not connected' on some platforms,
      # call #close instead of #shutdown.
      # (ignore @config[:ShutdownSocketWithoutClose])
      s.close
    else
      unless @config[:ShutdownSocketWithoutClose]
        s.close
      end
    end
  }
  @listeners.clear
end

#cleanup_shutdown_pipe(shutdown_pipe) (private)

This method is for internal use only.
[ GitHub ]

  
# File 'lib/webrick/server.rb', line 339

def cleanup_shutdown_pipe(shutdown_pipe)
  @shutdown_pipe = nil
  shutdown_pipe&.each(&:close)
end

#listen(address, port)

This method is for internal use only.

Updates listen to enable SSL when the SSL configuration is active.

See additional method definition at file lib/webrick/ssl.rb line 164.

[ GitHub ]

  
# File 'lib/webrick/server.rb', line 126

def listen(address, port) # :nodoc:
  listeners = Utils::create_listeners(address, port)
  if @config[:SSLEnable]
    listeners.collect!{|svr|
      ssvr = ::OpenSSL::SSL::SSLServer.new(svr, ssl_context)
      ssvr.start_immediately = @config[:SSLStartImmediately]
      ssvr
    }
  end
  @listeners += listeners
  setup_shutdown_pipe
end

#run(sock)

You must subclass GenericServer and implement #run which accepts a TCP client socket

[ GitHub ]

  
# File 'lib/webrick/server.rb', line 241

def run(sock)
  @logger.fatal "run() must be provided by user."
end

#setup_shutdown_pipe (private)

This method is for internal use only.
[ GitHub ]

  
# File 'lib/webrick/server.rb', line 335

def setup_shutdown_pipe
  return @shutdown_pipe ||= IO.pipe
end

#setup_ssl_context(config)

This method is for internal use only.

Sets up an SSL context for #config

[ GitHub ]

  
# File 'lib/webrick/ssl.rb', line 180

def setup_ssl_context(config) # :nodoc:
  unless config[:SSLCertificate]
    cn = config[:SSLCertName]
    comment = config[:SSLCertComment]
    cert, key = Utils::create_self_signed_cert(2048, cn, comment)
    config[:SSLCertificate] = cert
    config[:SSLPrivateKey] = key
  end
  ctx = OpenSSL::SSL::SSLContext.new
  ctx.key = config[:SSLPrivateKey]
  ctx.cert = config[:SSLCertificate]
  ctx.client_ca = config[:SSLClientCA]
  ctx.extra_chain_cert = config[:SSLExtraChainCert]
  ctx.ca_file = config[:SSLCACertificateFile]
  ctx.ca_path = config[:SSLCACertificatePath]
  ctx.cert_store = config[:SSLCertificateStore]
  ctx.tmp_dh_callback = config[:SSLTmpDhCallback]
  ctx.verify_mode = config[:SSLVerifyClient]
  ctx.verify_depth = config[:SSLVerifyDepth]
  ctx.verify_callback = config[:SSLVerifyCallback]
  ctx.servername_cb = config[:SSLServerNameCallback] || proc { |args| ssl_servername_callback(*args) }
  ctx.timeout = config[:SSLTimeout]
  ctx.options = config[:SSLOptions]
  ctx.ciphers = config[:SSLCiphers]
  ctx
end

#shutdown

Shuts down the server and all listening sockets. New listeners must be provided to restart the server.

[ GitHub ]

  
# File 'lib/webrick/server.rb', line 231

def shutdown
  stop

  alarm_shutdown_pipe(&:close)
end

#ssl_context

This method is for internal use only.

SSL context for the server when run in SSL mode

[ GitHub ]

  
# File 'lib/webrick/ssl.rb', line 149

def ssl_context # :nodoc:
  @ssl_context ||= begin
    if @config[:SSLEnable]
      ssl_context = setup_ssl_context(@config)
      @logger.info("\n" + @config[:SSLCertificate].to_text)
      ssl_context
    end
  end
end

#ssl_servername_callback(sslsocket, hostname = nil)

ServerNameIndication callback

[ GitHub ]

  
# File 'lib/webrick/ssl.rb', line 210

def ssl_servername_callback(sslsocket, hostname = nil)
  # default
end

#start(&block)

Starts the server and runs the block for each connection. This method does not return until the server is stopped from a signal handler or another thread using #stop or #shutdown.

If the block raises a subclass of StandardError the exception is logged and ignored. If an IOError or Errno::EBADF exception is raised the exception is ignored. If an Exception subclass is raised the exception is logged and re-raised which stops the server.

To completely shut down a server call #shutdown from ensure:

server = WEBrick::GenericServer.new
# or WEBrick::HTTPServer.new

begin
  server.start
ensure
  server.shutdown
end

Raises:

[ GitHub ]

  
# File 'lib/webrick/server.rb', line 151

def start(&block)
  raise ServerError, "already started." if @status != :Stop
  server_type = @config[:ServerType] || SimpleServer

  setup_shutdown_pipe

  server_type.start{
    @logger.info \
      "#{self.class}#start: pid=#{$$} port=#{@config[:Port]}"
    @status = :Running
    call_callback(:StartCallback)

    shutdown_pipe = @shutdown_pipe

    thgroup = ThreadGroup.new
    begin
      while @status == :Running
        begin
          sp = shutdown_pipe[0]
          if svrs = IO.select([sp, *@listeners])
            if svrs[0].include? sp
              # swallow shutdown pipe
              buf = String.new
              nil while String ===
                        sp.read_nonblock([sp.nread, 8].max, buf, exception: false)
              break
            end
            svrs[0].each{|svr|
              @tokens.pop          # blocks while no token is there.
              if sock = accept_client(svr)
                unless config[:DoNotReverseLookup].nil?
                  sock.do_not_reverse_lookup = !!config[:DoNotReverseLookup]
                end
                th = start_thread(sock, &block)
                th[:WEBrickThread] = true
                thgroup.add(th)
              else
                @tokens.push(nil)
              end
            }
          end
        rescue Errno::EBADF, Errno::ENOTSOCK, IOError => ex
          # if the listening socket was closed in GenericServer#shutdown,
          # IO::select raise it.
        rescue StandardError => ex
          msg = "#{ex.class}: #{ex.message}\n\t#{ex.backtrace[0]}"
          @logger.error msg
        rescue Exception => ex
          @logger.fatal ex
          raise
        end
      end
    ensure
      cleanup_shutdown_pipe(shutdown_pipe)
      cleanup_listener
      @status = :Shutdown
      @logger.info "going to shutdown ..."
      thgroup.list.each{|th| th.join if th[:WEBrickThread] }
      call_callback(:StopCallback)
      @logger.info "#{self.class}#start done."
      @status = :Stop
    end
  }
end

#start_thread(sock, &block) (private)

This method is for internal use only.

Starts a server thread for the client socket sock that runs the given block.

Sets the socket to the :WEBrickSocket thread local variable in the thread.

If any errors occur in the block they are logged and handled.

[ GitHub ]

  
# File 'lib/webrick/server.rb', line 285

def start_thread(sock, &block)
  Thread.start{
    begin
      Thread.current[:WEBrickSocket] = sock
      begin
        addr = sock.peeraddr
        @logger.debug "accept: #{addr[3]}:#{addr[1]}"
      rescue SocketError
        @logger.debug "accept: <address unknown>"
        raise
      end
      if sock.respond_to?(:sync_close=) && @config[:SSLStartImmediately]
        WEBrick::Utils.timeout(@config[:RequestTimeout]) do
          begin
            sock.accept # OpenSSL::SSL::SSLSocket#accept
          rescue Errno::ECONNRESET, Errno::ECONNABORTED,
                 Errno::EPROTO, Errno::EINVAL
            Thread.exit
          end
        end
      end
      call_callback(:AcceptCallback, sock)
      block ? block.call(sock) : run(sock)
    rescue Errno::ENOTCONN
      @logger.debug "Errno::ENOTCONN raised"
    rescue ServerError => ex
      msg = "#{ex.class}: #{ex.message}\n\t#{ex.backtrace[0]}"
      @logger.error msg
    rescue Exception => ex
      @logger.error ex
    ensure
      @tokens.push(nil)
      Thread.current[:WEBrickSocket] = nil
      if addr
        @logger.debug "close: #{addr[3]}:#{addr[1]}"
      else
        @logger.debug "close: <address unknown>"
      end
      sock.close
    end
  }
end

#stop

Stops the server from accepting new connections.

[ GitHub ]

  
# File 'lib/webrick/server.rb', line 219

def stop
  if @status == :Running
    @status = :Shutdown
  end

  alarm_shutdown_pipe {|f| f.write_nonblock("\0")}
end