Module: Puma::Request
Overview
The methods here are included in Server, but are separated into this file. All the methods here pertain to passing the request to the app, then writing the response back to the client.
None of the methods here are called externally, with the exception of #handle_request, which is called in Server#process_client.
Constant Summary
Constants included from Const
Const::BANNED_HEADER_KEY, Const::CGI_VER, Const::CHUNKED, Const::CHUNK_SIZE, Const::CLOSE, Const::CLOSE_CHUNKED, Const::CODE_NAME, Const::COLON, Const::CONNECTION_CLOSE, Const::CONNECTION_KEEP_ALIVE, Const::CONTENT_LENGTH, Const::CONTENT_LENGTH2, Const::CONTENT_LENGTH_S, Const::CONTINUE, Const::DQUOTE, Const::EARLY_HINTS, Const::ERROR_RESPONSE, Const::FAST_TRACK_KA_TIMEOUT, Const::FIRST_DATA_TIMEOUT, Const::GATEWAY_INTERFACE, Const::HALT_COMMAND, Const::HEAD, Const::HIJACK, Const::HIJACK_IO, Const::HIJACK_P, Const::HTTP, Const::HTTPS, Const::HTTPS_KEY, Const::HTTP_10_200, Const::HTTP_11, Const::HTTP_11_100, Const::HTTP_11_200, Const::HTTP_CONNECTION, Const::HTTP_EXPECT, Const::HTTP_HEADER_DELIMITER, Const::HTTP_HOST, Const::HTTP_VERSION, Const::HTTP_X_FORWARDED_FOR, Const::HTTP_X_FORWARDED_PROTO, Const::HTTP_X_FORWARDED_SCHEME, Const::HTTP_X_FORWARDED_SSL, Const::ILLEGAL_HEADER_KEY_REGEX, Const::ILLEGAL_HEADER_VALUE_REGEX, Const::KEEP_ALIVE, Const::LINE_END, Const::LOCALHOST, Const::LOCALHOST_IP, Const::MAX_BODY, Const::MAX_FAST_INLINE, Const::MAX_HEADER, Const::NEWLINE, Const::PATH_INFO, Const::PERSISTENT_TIMEOUT, Const::PORT_443, Const::PORT_80, Const::PROXY_PROTOCOL_V1_REGEX, Const::PUMA_CONFIG, Const::PUMA_PEERCERT, Const::PUMA_SERVER_STRING, Const::PUMA_SOCKET, Const::PUMA_TMP_BASE, Const::PUMA_VERSION, Const::QUERY_STRING, Const::RACK_AFTER_REPLY, Const::RACK_INPUT, Const::RACK_URL_SCHEME, Const::REMOTE_ADDR, Const::REQUEST_METHOD, Const::REQUEST_PATH, Const::REQUEST_URI, Const::RESTART_COMMAND, Const::SERVER_NAME, Const::SERVER_PORT, Const::SERVER_PROTOCOL, Const::SERVER_SOFTWARE, Const::STOP_COMMAND, Const::TRANSFER_ENCODING, Const::TRANSFER_ENCODING2, Const::TRANSFER_ENCODING_CHUNKED, Const::WRITE_TIMEOUT
Instance Method Summary collapse
- #default_server_port(env) ⇒ Puma::Const::PORT_443, Puma::Const::PORT_80
-
#handle_request(client, lines, requests) ⇒ Boolean, :async
Takes the request contained in
client
, invokes the Rack application to construct the response and writes it back toclient.io
. -
#normalize_env(env, client) ⇒ Object
Given a Hash
env
for the request read fromclient
, add and fixup keys to comply with Rack's env guidelines.
Instance Method Details
#default_server_port(env) ⇒ Puma::Const::PORT_443, Puma::Const::PORT_80
194 195 196 197 198 199 200 |
# File 'lib/puma/request.rb', line 194 def default_server_port(env) if ['on', HTTPS].include?(env[HTTPS_KEY]) || env[HTTP_X_FORWARDED_PROTO].to_s[0...5] == HTTPS || env[HTTP_X_FORWARDED_SCHEME] == HTTPS || env[HTTP_X_FORWARDED_SSL] == "on" PORT_443 else PORT_80 end end |
#handle_request(client, lines, requests) ⇒ Boolean, :async
Takes the request contained in client
, invokes the Rack application to construct the response and writes it back to client.io
.
It'll return false
when the connection is closed, this doesn't mean that the response wasn't successful.
It'll return :async
if the connection remains open but will be handled elsewhere, i.e. the connection has been hijacked by the Rack application.
Finally, it'll return true
on keep-alive connections.
32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 |
# File 'lib/puma/request.rb', line 32 def handle_request(client, lines, requests) env = client.env io = client.io # io may be a MiniSSL::Socket return false if closed_socket?(io) normalize_env env, client env[PUMA_SOCKET] = io if env[HTTPS_KEY] && io.peercert env[PUMA_PEERCERT] = io.peercert end env[HIJACK_P] = true env[HIJACK] = client body = client.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 io, str_early_hints(headers) rescue ConnectionError => e @events.debug_error e # noop, if we lost the socket we just won't send the early hints end } end req_env_post_parse env # 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 = @thread_pool.with_force_shutdown do @app.call(env) end return :async if client.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.unknown_error e, client, "Rack app" @events.log "Detected force shutdown of a thread" status, headers, res_body = lowlevel_error(e, env, 503) rescue Exception => e @events.unknown_error e, client, "Rack app" status, headers, res_body = lowlevel_error(e, env, 500) end res_info = {} res_info[:content_length] = nil res_info[:no_body] = head res_info[:content_length] = if res_body.kind_of? Array and res_body.size == 1 res_body[0].bytesize else nil end cork_socket io str_headers(env, status, headers, res_info, lines, requests, client) line_ending = LINE_END content_length = res_info[:content_length] response_hijack = res_info[:response_hijack] if res_info[:no_body] if content_length and status != 204 lines.append CONTENT_LENGTH_S, content_length.to_s, line_ending end lines << LINE_END fast_write io, lines.to_s return res_info[:keep_alive] end if content_length lines.append CONTENT_LENGTH_S, content_length.to_s, line_ending chunked = false elsif !response_hijack and res_info[:allow_chunked] lines << TRANSFER_ENCODING_CHUNKED chunked = true end lines << line_ending fast_write io, lines.to_s if response_hijack response_hijack.call io return :async end begin res_body.each do |part| next if part.bytesize.zero? if chunked fast_write io, (part.bytesize.to_s(16) << line_ending) fast_write io, part # part may have different encoding fast_write io, line_ending else fast_write io, part end io.flush end if chunked fast_write io, CLOSE_CHUNKED io.flush end rescue SystemCallError, IOError raise ConnectionError, "Connection error detected during write" end ensure begin uncork_socket io body.close client.tempfile.unlink if client.tempfile ensure # Whatever happens, we MUST call `close` on the response body. # Otherwise Rack::BodyProxy callbacks may not fire and lead to various state leaks res_body.close if res_body.respond_to? :close end begin after_reply.each { |o| o.call } rescue StandardError => e @log_writer.debug_error e end end res_info[:keep_alive] end |
#normalize_env(env, client) ⇒ Object
make private in 6.0.0
Given a Hash env
for the request read from client
, add and fixup keys to comply with Rack's env guidelines.
242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 |
# File 'lib/puma/request.rb', line 242 def normalize_env(env, client) if host = env[HTTP_HOST] # host can be a hostname, ipv4 or bracketed ipv6. Followed by an optional port. if colon = host.rindex("]:") # IPV6 with port env[SERVER_NAME] = host[0, colon+1] env[SERVER_PORT] = host[colon+2, host.bytesize] elsif !host.start_with?("[") && colon = host.index(":") # not hostname or IPV4 with port env[SERVER_NAME] = host[0, colon] env[SERVER_PORT] = host[colon+1, host.bytesize] else env[SERVER_NAME] = host env[SERVER_PORT] = default_server_port(env) end else env[SERVER_NAME] = LOCALHOST env[SERVER_PORT] = default_server_port(env) end unless env[REQUEST_PATH] # it might be a dumbass full host request header uri = URI.parse(env[REQUEST_URI]) env[REQUEST_PATH] = uri.path raise "No REQUEST PATH" unless env[REQUEST_PATH] # A nil env value will cause a LintError (and fatal errors elsewhere), # so only set the env value if there actually is a value. env[QUERY_STRING] = uri.query if uri.query end env[PATH_INFO] = env[REQUEST_PATH] # From https://www.ietf.org/rfc/rfc3875 : # "Script authors should be aware that the REMOTE_ADDR and # REMOTE_HOST meta-variables (see sections 4.1.8 and 4.1.9) # may not identify the ultimate source of the request. # They identify the client for the immediate request to the # server; that client may be a proxy, gateway, or other # intermediary acting on behalf of the actual source client." # unless env.key?(REMOTE_ADDR) begin addr = client.peerip rescue Errno::ENOTCONN # Client disconnects can result in an inability to get the # peeraddr from the socket; default to localhost. addr = LOCALHOST_IP end # Set unix socket addrs to localhost addr = LOCALHOST_IP if addr.empty? env[REMOTE_ADDR] = addr end end |