class WEBrick::GenericServer

def [](key)

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

def accept_client(svr)

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

def alarm_shutdown_pipe

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

def call_callback(callback_name, *args)

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

def cleanup_listener

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

def cleanup_shutdown_pipe(shutdown_pipe)

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

def initialize(config={}, default=Config::General)

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]
    raise ArgumentError, "Port must be an integer" unless @config[:Port].to_s == @config[:Port].to_i.to_s
    @config[:Port] = @config[:Port].to_i
    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

def listen(address, port)

def listen(address, port)
  @listeners += Utils::create_listeners(address, port)
end

def listen(address, port) # :nodoc:

:nodoc:
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

def run(sock)

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

def setup_shutdown_pipe

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

def setup_ssl_context(config) # :nodoc:

:nodoc:
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

def shutdown

def shutdown
  stop
  alarm_shutdown_pipe(&:close)
end

def ssl_context # :nodoc:

:nodoc:
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

def ssl_servername_callback(sslsocket, hostname = nil)

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

def start(&block)

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

def start_thread(sock, &block)

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

def stop

def stop
  if @status == :Running
    @status = :Shutdown
  end
  alarm_shutdown_pipe {|f| f.write_nonblock("\0")}
end