首先假设使用的是WEBRick Server 1.3.1,并且使用Rails server命令启动Rails服务器,那么实际处理一个Socket请求的代码在:
webrick-1.3.1/lib/webrick/server.rb
中的GenericServer
的start_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.rb
的HTTPRequest
类中:
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绑定在这个方法上。