class Puma::Server
def handle_request(req, lines)
elsewhere, i.e. the connection has been hijacked by the Rack application.
It'll return +:async+ if the connection remains open but will be handled
that the response wasn't successful.
It'll return +false+ when the connection is closed, this doesn't mean
This is normally an instance of Puma::IOBuffer.
The second parameter +lines+ is a IO-like object unique to this thread.
the response and writes it back to +req.io+.
Takes the request +req+, invokes the Rack application to construct
def handle_request(req, lines) env = req.env client = req.io return false if closed_socket?(client) normalize_env env, req env[PUMA_SOCKET] = client if env[HTTPS_KEY] && client.peercert env[PUMA_PEERCERT] = client.peercert end env[HIJACK_P] = true env[HIJACK] = req body = req.body head = env[REQUEST_METHOD] == HEAD env[RACK_INPUT] = body env[RACK_URL_SCHEME] = default_server_port(env) == PORT_443 ? HTTPS : HTTP if @early_hints env[EARLY_HINTS] = lambda { |headers| begin fast_write client, "HTTP/1.1 103 Early Hints\r\n".freeze headers.each_pair do |k, vs| if vs.respond_to?(:to_s) && !vs.to_s.empty? vs.to_s.split(NEWLINE).each do |v| next if possible_header_injection?(v) fast_write client, "#{k}: #{v}\r\n" end else fast_write client, "#{k}: #{vs}\r\n" end end fast_write client, "\r\n".freeze rescue ConnectionError # noop, if we lost the socket we just won't send the early hints end } end # Fixup any headers with , in the name to have _ now. We emit # headers with , in them during the parse phase to avoid ambiguity # with the - to _ conversion for critical headers. But here for # compatibility, we'll convert them back. This code is written to # avoid allocation in the common case (ie there are no headers # with , in their names), that's why it has the extra conditionals. to_delete = nil to_add = nil env.each do |k,v| if k.start_with?("HTTP_") and k.include?(",") and k != "HTTP_TRANSFER,ENCODING" if to_delete to_delete << k else to_delete = [k] end unless to_add to_add = {} end to_add[k.tr(",", "_")] = v end end if to_delete to_delete.each { |k| env.delete(k) } env.merge! to_add end # A rack extension. If the app writes #call'ables to this # array, we will invoke them when the request is done. # after_reply = env[RACK_AFTER_REPLY] = [] begin begin status, headers, res_body = @app.call(env) return :async if req.hijacked status = status.to_i if status == -1 unless headers.empty? and res_body == [] raise "async response must have empty headers and body" end return :async end rescue ThreadPool::ForceShutdown => e @events.log "Detected force shutdown of a thread, returning 503" @events.unknown_error self, e, "Rack app" status = 503 headers = {} res_body = ["Request was internally terminated early\n"] rescue Exception => e @events.unknown_error self, e, "Rack app", env status, headers, res_body = lowlevel_error(e, env) end content_length = nil no_body = head if res_body.kind_of? Array and res_body.size == 1 content_length = res_body[0].bytesize end cork_socket client line_ending = LINE_END colon = COLON http_11 = if env[HTTP_VERSION] == HTTP_11 allow_chunked = true keep_alive = env.fetch(HTTP_CONNECTION, "").downcase != CLOSE include_keepalive_header = false # An optimization. The most common response is 200, so we can # reply with the proper 200 status without having to compute # the response header. # if status == 200 lines << HTTP_11_200 else lines.append "HTTP/1.1 ", status.to_s, " ", fetch_status_code(status), line_ending no_body ||= status < 200 || STATUS_WITH_NO_ENTITY_BODY[status] end true else allow_chunked = false keep_alive = env.fetch(HTTP_CONNECTION, "").downcase == KEEP_ALIVE include_keepalive_header = keep_alive # Same optimization as above for HTTP/1.1 # if status == 200 lines << HTTP_10_200 else lines.append "HTTP/1.0 ", status.to_s, " ", fetch_status_code(status), line_ending no_body ||= status < 200 || STATUS_WITH_NO_ENTITY_BODY[status] end false end response_hijack = nil headers.each do |k, vs| case k.downcase when CONTENT_LENGTH2 next if possible_header_injection?(vs) content_length = vs next when TRANSFER_ENCODING allow_chunked = false content_length = nil when HIJACK response_hijack = vs next end if vs.respond_to?(:to_s) && !vs.to_s.empty? vs.to_s.split(NEWLINE).each do |v| next if possible_header_injection?(v) lines.append k, colon, v, line_ending end else lines.append k, colon, line_ending end end if include_keepalive_header lines << CONNECTION_KEEP_ALIVE elsif http_11 && !keep_alive lines << CONNECTION_CLOSE end if no_body if content_length and status != 204 lines.append CONTENT_LENGTH_S, content_length.to_s, line_ending end lines << line_ending fast_write client, lines.to_s return keep_alive end if content_length lines.append CONTENT_LENGTH_S, content_length.to_s, line_ending chunked = false elsif !response_hijack and allow_chunked lines << TRANSFER_ENCODING_CHUNKED chunked = true end lines << line_ending fast_write client, lines.to_s if response_hijack response_hijack.call client return :async end begin res_body.each do |part| next if part.bytesize.zero? if chunked fast_write client, part.bytesize.to_s(16) fast_write client, line_ending fast_write client, part fast_write client, line_ending else fast_write client, part end client.flush end if chunked fast_write client, CLOSE_CHUNKED client.flush end rescue SystemCallError, IOError raise ConnectionError, "Connection error detected during write" end ensure uncork_socket client body.close req.tempfile.unlink if req.tempfile res_body.close if res_body.respond_to? :close after_reply.each { |o| o.call } end return keep_alive end