Skip to content

Latest commit

 

History

History
304 lines (291 loc) · 10.2 KB

01.rack_server.md

File metadata and controls

304 lines (291 loc) · 10.2 KB

An Http Request Through Rails

01. Rack Server

首先假设使用的是WEBRick Server 1.3.1,并且使用Rails server命令启动Rails服务器,那么实际处理一个Socket请求的代码在: webrick-1.3.1/lib/webrick/server.rb中的GenericServerstart_thread方法:

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

中的run(sock)语句。 start_thread方法调用自start方法:

def start(&block)
  raise ServerError, "already started." if @status != :Stop
  server_type = @config[:ServerType] || SimpleServer
  server_type.start{
    @logger.info \
      "#{self.class}#start: pid=#{$$} port=#{@config[:Port]}"
    call_callback(:StartCallback)
    thgroup = ThreadGroup.new
    @status = :Running
    while @status == :Running
      begin
        if svrs = IO.select(@listeners, nil, nil, 2.0)
          svrs[0].each{|svr|
            @tokens.pop          # blocks while no token is there.
            if sock = accept_client(svr)
              sock.do_not_reverse_lookup = config[:DoNotReverseLookup]
              th = start_thread(sock, &block)
              th[:WEBrickThread] = true
              thgroup.add(th)
            else
              @tokens.push(nil)
            end
          }
        end
      rescue Errno::EBADF, IOError => ex
        # if the listening socket was closed in GenericServer#shutdown,
        # IO::select raise it.
      rescue Exception => ex
        msg = "#{ex.class}: #{ex.message}\n\t#{ex.backtrace[0]}"
        @logger.error msg
      end
    end
    @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

此时已经用设置的listeners通过IO.select方法获取到了IO对象。将IO对象传入accept_client方法获取到Socket对象:

def accept_client(svr)
  sock = nil
  begin
    sock = svr.accept
    sock.sync = true
    Utils::set_non_blocking(sock)
    Utils::set_close_on_exec(sock)
  rescue Errno::ECONNRESET, Errno::ECONNABORTED,
         Errno::EPROTO, Errno::EINVAL => ex
  rescue Exception => ex
    msg = "#{ex.class}: #{ex.message}\n\t#{ex.backtrace[0]}"
    @logger.error msg
  end
  return sock
end

这里通过调用svr.accept可以获取到Socket对象。 接着就是调用前面的start_thread方法先启动一个新的Thread,然后调用了run方法处理Socket请求,这里的run方法定义在GenericServer的子类HTTPServer中,位于webrick-1.3.1/lib/webrick/httpserver.rb中。

def run(sock)
  while true
    res = HTTPResponse.new(@config)
    req = HTTPRequest.new(@config)
    server = self
    begin
      timeout = @config[:RequestTimeout]
      while timeout > 0
        break if IO.select([sock], nil, nil, 0.5)
        timeout = 0 if @status != :Running
        timeout -= 0.5
      end
      raise HTTPStatus::EOFError if timeout <= 0
      raise HTTPStatus::EOFError if sock.eof?
      req.parse(sock)
      res.request_method = req.request_method
      res.request_uri = req.request_uri
      res.request_http_version = req.http_version
      res.keep_alive = req.keep_alive?
      server = lookup_server(req) || self
      if callback = server[:RequestCallback]
        callback.call(req, res)
      elsif callback = server[:RequestHandler]
        msg = ":RequestHandler is deprecated, please use :RequestCallback"
        @logger.warn(msg)
        callback.call(req, res)
      end
      server.service(req, res)
    rescue HTTPStatus::EOFError, HTTPStatus::RequestTimeout => ex
      res.set_error(ex)
    rescue HTTPStatus::Error => ex
      @logger.error(ex.message)
      res.set_error(ex)
    rescue HTTPStatus::Status => ex
      res.status = ex.code
    rescue StandardError => ex
      @logger.error(ex)
      res.set_error(ex, true)
    ensure
      if req.request_line
        if req.keep_alive? && res.keep_alive?
          req.fixup()
        end
        res.send_response(sock)
        server.access_log(@config, req, res)
      end
    end
    break if @http_version < "1.1"
    break unless req.keep_alive?
    break unless res.keep_alive?
  end
end

这里先将请求封装成HTTPResponse对象和HTTPRequest对象,调用lookup_server方法:

##
# Finds the appropriate virtual host to handle +req+
def lookup_server(req)
  @virtual_hosts.find{|s|
    (s[:BindAddress].nil? || req.addr[3] == s[:BindAddress]) &&
    (s[:Port].nil?        || req.port == s[:Port])           &&
    ((s[:ServerName].nil?  || req.host == s[:ServerName]) ||
     (!s[:ServerAlias].nil? && s[:ServerAlias].find{|h| h === req.host}))
  }
end

找到一个符合要求可以处理请求的server,或是HTTPServer对象自身,然后调用它的service方法:

##
# Services +req+ and fills in +res+
def service(req, res)
  if req.unparsed_uri == "*"
    if req.request_method == "OPTIONS"
      do_OPTIONS(req, res)
      raise HTTPStatus::OK
    end
    raise HTTPStatus::NotFound, "`#{req.unparsed_uri}' not found."
  end
  servlet, options, script_name, path_info = search_servlet(req.path)
  raise HTTPStatus::NotFound, "`#{req.path}' not found." unless servlet
  req.script_name = script_name
  req.path_info = path_info
  si = servlet.get_instance(self, *options)
  @logger.debug(format("%s is invoked.", si.class.name))
  si.service(req, res)
end

在这个方法中,通过search_servlet找到请求地址对应的Rack::Handler::WEBrick类:

##
# Finds a servlet for +path+
def search_servlet(path)
  script_name, path_info = @mount_tab.scan(path)
  servlet, options = @mount_tab[script_name]
  if servlet
    [ servlet, options, script_name, path_info ]
  end
end

获取Rack::Handler类的方法是扫描WEBrick::HTTPServer::MountTable类对象。获取之后调用servlet.get_instance创建Rack::Handler::WEBrick的实例,然后调用它的service方法,该方法位于rack-1.4.5/lib/rack/handler/webrick.rb中:

def service(req, res)
  env = req.meta_vars
  env.delete_if { |k, v| v.nil? }
  rack_input = StringIO.new(req.body.to_s)
  rack_input.set_encoding(Encoding::BINARY) if rack_input.respond_to?(:set_encoding)
  env.update({"rack.version" => Rack::VERSION,
               "rack.input" => rack_input,
               "rack.errors" => $stderr,
               "rack.multithread" => true,
               "rack.multiprocess" => false,
               "rack.run_once" => false,
               "rack.url_scheme" => ["yes", "on", "1"].include?(ENV["HTTPS"]) ? "https" : "http"
             })
  env["HTTP_VERSION"] ||= env["SERVER_PROTOCOL"]
  env["QUERY_STRING"] ||= ""
  unless env["PATH_INFO"] == ""
    path, n = req.request_uri.path, env["SCRIPT_NAME"].length
    env["PATH_INFO"] = path[n, path.length-n]
  end
  env["REQUEST_PATH"] ||= [env["SCRIPT_NAME"], env["PATH_INFO"]].join
  status, headers, body = @app.call(env)
  begin
    res.status = status.to_i
    headers.each { |k, vs|
      if k.downcase == "set-cookie"
        res.cookies.concat vs.split("\n")
      else
        # Since WEBrick won't accept repeated headers,
        # merge the values per RFC 1945 section 4.2.
        res[k] = vs.split("\n").join(", ")
      end
    }
    body.each { |part|
      res.body << part
    }
  ensure
    body.close  if body.respond_to? :close
  end
end

一开始的req.meta_vars是初始化的rails env环境,代码定义在webrick-1.3.1/lib/webrick/httprequest.rbHTTPRequest类中:

def meta_vars
  meta = Hash.new
  cl = self["Content-Length"]
  ct = self["Content-Type"]
  meta["CONTENT_LENGTH"]    = cl if cl.to_i > 0
  meta["CONTENT_TYPE"]      = ct.dup if ct
  meta["GATEWAY_INTERFACE"] = "CGI/1.1"
  meta["PATH_INFO"]         = @path_info ? @path_info.dup : ""
 #meta["PATH_TRANSLATED"]   = nil      # no plan to be provided
  meta["QUERY_STRING"]      = @query_string ? @query_string.dup : ""
  meta["REMOTE_ADDR"]       = @peeraddr[3]
  meta["REMOTE_HOST"]       = @peeraddr[2]
 #meta["REMOTE_IDENT"]      = nil      # no plan to be provided
  meta["REMOTE_USER"]       = @user
  meta["REQUEST_METHOD"]    = @request_method.dup
  meta["REQUEST_URI"]       = @request_uri.to_s
  meta["SCRIPT_NAME"]       = @script_name.dup
  meta["SERVER_NAME"]       = @host
  meta["SERVER_PORT"]       = @port.to_s
  meta["SERVER_PROTOCOL"]   = "HTTP/" + @config[:HTTPVersion].to_s
  meta["SERVER_SOFTWARE"]   = @config[:ServerSoftware].dup
  self.each{|key, val|
    next if /^content-type$/i =~ key
    next if /^content-length$/i =~ key
    name = "HTTP_" + key
    name.gsub!(/-/o, "_")
    name.upcase!
    meta[name] = val
  }
  meta
end

需要注意的是,这里的self.each把所有http header中内容进行遍历:

def each
  if @header
    @header.each{|k, v|
      value = @header[k]
      yield(k, value.empty? ? nil : value.join(", "))
    }
  end
end

然后在它们的Key前加上HTTP_字样,大写化后也加入rails env的内容。 回到service方法,这里对rails env进行filter,再加入封装了请求内容的StringIO对象,再将Rack服务器的默认信息,请求的元信息写入rails env。然后在这里,第一次调用middleware。

调用完成后,将返回码赋值给response对象,遍历从middleware返回的headers,其中将set-cookie单独赋值给res.cookies,并去除回车。其余部分则将回车替换成逗号,拷贝到response对象中去。对body对象调用close方法,这个非常重要因为有很多hook绑定在这个方法上。