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 collapse
- BODY_LEN_MAX =
Single element array body: smaller bodies are written to io_buffer first, then a single write from io_buffer. Larger sizes are written separately. Also fixes max size of chunked file body read.
1_024 * 256
- IO_BODY_MAX =
File body: smaller bodies are combined with io_buffer, then written to socket. Larger bodies are written separately using ‘copy_stream`
1_024 * 64
- IO_BUFFER_LEN_MAX =
Array body: elements are collected in io_buffer. When io_buffer’s size exceeds value, they are written to the socket.
1_024 * 512
- SOCKET_WRITE_ERR_MSG =
"Socket timeout writing data"
- CUSTOM_STAT =
'CUSTOM'
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::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::IANA_HTTP_METHODS, Const::ILLEGAL_HEADER_KEY_REGEX, Const::ILLEGAL_HEADER_VALUE_REGEX, Const::KEEP_ALIVE, Const::LINE_END, Const::LOCALHOST, Const::LOCALHOST_IPV4, Const::LOCALHOST_IPV6, Const::MAX_BODY, Const::MAX_HEADER, Const::NEWLINE, Const::PATH_INFO, 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::SUPPORTED_HTTP_METHODS, Const::TRANSFER_ENCODING, Const::TRANSFER_ENCODING2, Const::TRANSFER_ENCODING_CHUNKED, Const::UNMASKABLE_HEADERS, Const::UNSPECIFIED_IPV4, Const::UNSPECIFIED_IPV6, Const::WRITE_TIMEOUT
Instance Method Summary collapse
- #default_server_port(env) ⇒ Puma::Const::PORT_443, Puma::Const::PORT_80
-
#handle_request(client, requests) ⇒ Boolean, :async
Takes the request contained in
client
, invokes the Rack application to construct the response and writes it back toclient.io
. -
#prepare_response(status, headers, res_body, requests, client) ⇒ Boolean, :async
Assembles the headers and prepares the body for actually sending the response via ‘#fast_write_response`.
Instance Method Details
#default_server_port(env) ⇒ Puma::Const::PORT_443, Puma::Const::PORT_80
268 269 270 271 272 273 274 |
# File 'lib/puma/request.rb', line 268 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, 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.
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 |
# File 'lib/puma/request.rb', line 50 def handle_request(client, requests) env = client.env io_buffer = client.io_buffer socket = client.io # io may be a MiniSSL::Socket app_body = nil return false if closed_socket?(socket) if client.http_content_length_limit_exceeded return prepare_response(413, {}, ["Payload Too Large"], requests, client) end normalize_env env, client env[PUMA_SOCKET] = socket if env[HTTPS_KEY] && socket.peercert env[PUMA_PEERCERT] = socket.peercert end env[HIJACK_P] = true env[HIJACK] = client env[RACK_INPUT] = client.body env[RACK_URL_SCHEME] ||= default_server_port(env) == PORT_443 ? HTTPS : HTTP if @early_hints env[EARLY_HINTS] = lambda { |headers| begin unless (str = str_early_hints headers).empty? fast_write_str socket, "HTTP/1.1 103 Early Hints\r\n#{str}\r\n" end rescue ConnectionError => e @log_writer.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. # env[RACK_AFTER_REPLY] ||= [] begin if @supported_http_methods == :any || @supported_http_methods.key?(env[REQUEST_METHOD]) status, headers, app_body = @thread_pool.with_force_shutdown do @app.call(env) end else @log_writer.log "Unsupported HTTP method used: #{env[REQUEST_METHOD]}" status, headers, app_body = [501, {}, ["#{env[REQUEST_METHOD]} method is not supported"]] end # app_body needs to always be closed, hold value in case lowlevel_error # is called res_body = app_body # full hijack, app called env['rack.hijack'] 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 @log_writer.unknown_error e, client, "Rack app" @log_writer.log "Detected force shutdown of a thread" status, headers, res_body = lowlevel_error(e, env, 503) rescue Exception => e @log_writer.unknown_error e, client, "Rack app" status, headers, res_body = lowlevel_error(e, env, 500) end prepare_response(status, headers, res_body, requests, client) ensure io_buffer.reset uncork_socket client.io app_body.close if app_body.respond_to? :close client.tempfile&.unlink after_reply = env[RACK_AFTER_REPLY] || [] begin after_reply.each { |o| o.call } rescue StandardError => e @log_writer.debug_error e end unless after_reply.empty? end |
#prepare_response(status, headers, res_body, requests, client) ⇒ Boolean, :async
Assembles the headers and prepares the body for actually sending the response via ‘#fast_write_response`.
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 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 |
# File 'lib/puma/request.rb', line 157 def prepare_response(status, headers, res_body, requests, client) env = client.env socket = client.io io_buffer = client.io_buffer return false if closed_socket?(socket) # Close the connection after a reasonable number of inline requests # if the server is at capacity and the listener has a new connection ready. # This allows Puma to service connections fairly when the number # of concurrent connections exceeds the size of the threadpool. force_keep_alive = requests < @max_fast_inline || @thread_pool.busy_threads < @max_threads || !client.listener.to_io.wait_readable(0) resp_info = str_headers(env, status, headers, res_body, io_buffer, force_keep_alive) close_body = false response_hijack = nil content_length = resp_info[:content_length] keep_alive = resp_info[:keep_alive] if res_body.respond_to?(:each) && !resp_info[:response_hijack] # below converts app_body into body, dependent on app_body's characteristics, and # content_length will be set if it can be determined if !content_length && !resp_info[:transfer_encoding] && status != 204 if res_body.respond_to?(:to_ary) && (array_body = res_body.to_ary) && array_body.is_a?(Array) body = array_body.compact content_length = body.sum(&:bytesize) elsif res_body.is_a?(File) && res_body.respond_to?(:size) body = res_body content_length = body.size elsif res_body.respond_to?(:to_path) && File.readable?(fn = res_body.to_path) body = File.open fn, 'rb' content_length = body.size close_body = true else body = res_body end elsif !res_body.is_a?(::File) && res_body.respond_to?(:to_path) && File.readable?(fn = res_body.to_path) body = File.open fn, 'rb' content_length = body.size close_body = true elsif !res_body.is_a?(::File) && res_body.respond_to?(:filename) && res_body.respond_to?(:bytesize) && File.readable?(fn = res_body.filename) # Sprockets::Asset content_length = res_body.bytesize unless content_length if (body_str = res_body.to_hash[:source]) body = [body_str] else # avoid each and use a File object body = File.open fn, 'rb' close_body = true end else body = res_body end else # partial hijack, from Rack spec: # Servers must ignore the body part of the response tuple when the # rack.hijack response header is present. response_hijack = resp_info[:response_hijack] || res_body end line_ending = LINE_END cork_socket socket if resp_info[:no_body] # 101 (Switching Protocols) doesn't return here or have content_length, # it should be using `response_hijack` unless status == 101 if content_length && status != 204 io_buffer.append CONTENT_LENGTH_S, content_length.to_s, line_ending end io_buffer << LINE_END fast_write_str socket, io_buffer.read_and_reset socket.flush return keep_alive end else if content_length io_buffer.append CONTENT_LENGTH_S, content_length.to_s, line_ending chunked = false elsif !response_hijack && resp_info[:allow_chunked] io_buffer << TRANSFER_ENCODING_CHUNKED chunked = true end end io_buffer << line_ending # partial hijack, we write headers, then hand the socket to the app via # response_hijack.call if response_hijack fast_write_str socket, io_buffer.read_and_reset uncork_socket socket response_hijack.call socket return :async end fast_write_response socket, body, io_buffer, chunked, content_length.to_i body.close if close_body keep_alive end |