Class: PumaPlus::RackEnv
- Inherits:
-
Object
- Object
- PumaPlus::RackEnv
- Defined in:
- lib/puma_plus/rack_env.rb
Overview
Assembles the Rack env from a REQUEST frame.
Go emits the complete, final env in CGI form, so this class performs zero string transformation -- no dash/underscore mangling, no Host splitting, no case folding. All of that lives in internal/frontend/env.go. What remains here is a hash dup plus byteslice merges, which is the cheapest per-request env construction available without a C extension.
Compare puma, which builds the env in five layers (Binder's @proto_env, the Ragel C parser writing straight into the hash, normalize_env, req_env_post_parse, then handle_request). Moving parsing to Go collapses that to one pass.
Constant Summary collapse
- COMMON_KEYS =
Keys that appear on essentially every request, pre-frozen so the common path never allocates a key string. Same optimization as puma's 35 pre-interned header names in ext/puma_http11/puma_http11.c:78-118.
%w[ REQUEST_METHOD PATH_INFO SCRIPT_NAME QUERY_STRING SERVER_PROTOCOL SERVER_NAME SERVER_PORT REMOTE_ADDR SERVER_SOFTWARE CONTENT_LENGTH CONTENT_TYPE rack.url_scheme HTTP_HOST HTTP_ACCEPT HTTP_ACCEPT_CHARSET HTTP_ACCEPT_ENCODING HTTP_ACCEPT_LANGUAGE HTTP_AUTHORIZATION HTTP_CACHE_CONTROL HTTP_CONNECTION HTTP_COOKIE HTTP_IF_MODIFIED_SINCE HTTP_IF_NONE_MATCH HTTP_ORIGIN HTTP_PRAGMA HTTP_REFERER HTTP_USER_AGENT HTTP_X_FORWARDED_FOR HTTP_X_FORWARDED_PROTO HTTP_X_REQUEST_ID HTTP_X_REQUEST_START ].each_with_object({}) { |k, h| h[k.b.freeze] = k.freeze }.freeze
Instance Method Summary collapse
-
#build(pairs, meta) ⇒ Object
Build an env from a decoded env kv blob.
-
#initialize(errors: $stderr, multithread: true, multiprocess: false) ⇒ RackEnv
constructor
Uncommon keys are deduplicated into this cache on first sight, so a long tail of custom headers costs one allocation per distinct name for the lifetime of the process rather than one per request.
Constructor Details
#initialize(errors: $stderr, multithread: true, multiprocess: false) ⇒ RackEnv
Uncommon keys are deduplicated into this cache on first sight, so a long tail of custom headers costs one allocation per distinct name for the lifetime of the process rather than one per request. multithread and multiprocess describe THIS process, and are computed by the caller from the real configuration rather than assumed.
Rack 3 dropped these from the spec, along with rack.version and rack.run_once. They are kept because apps still read them -- sizing a connection pool is the usual reason -- and because puma still sets them (puma/lib/puma/binder.rb:33-35). rack.version is NOT kept: Rack 3 removed it and any value would be a claim about a spec version that no longer numbers itself that way.
Hardcoding them, as this used to, meant a single-threaded worker still announced rack.multithread, and an app sizing a pool from it over-allocated by exactly the factor it was trying to compute.
50 51 52 53 54 55 56 57 58 59 |
# File 'lib/puma_plus/rack_env.rb', line 50 def initialize(errors: $stderr, multithread: true, multiprocess: false) @key_cache = Hash.new { |h, k| h[k] = -k.dup.force_encoding(Encoding::UTF_8) } @prototype = { "rack.errors" => errors, "rack.hijack?" => true, "rack.multithread" => multithread, "rack.multiprocess" => multiprocess, "rack.run_once" => false }.freeze end |
Instance Method Details
#build(pairs, meta) ⇒ Object
Build an env from a decoded env kv blob.
pairs is the array of [key, value] byteslices from Wire.decode_kv. Values
are handed through as-is: they are byteslices of the single frame payload,
so the whole env costs one payload string plus N slice headers.
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 |
# File 'lib/puma_plus/rack_env.rb', line 66 def build(pairs, ) env = @prototype.dup pairs.each do |key, value| env[COMMON_KEYS[key] || @key_cache[key]] = value end # Timing, in the three shapes callers expect. # # puma.request_queue_time is ours and authoritative: nanoseconds measured # entirely inside Go from a monotonic clock, so no cross-process skew. # # puma.request_body_wait keeps puma's exact key and millisecond unit # (lib/puma/client.rb:746) so middleware written against puma works # unchanged. env["puma.request_queue_time"] = [:queue_ns] / 1_000_000_000.0 env["puma.request_body_wait"] = [:body_wait_ns] / 1_000_000.0 # X-Request-Start in the exact `t=<unix micros>` format New Relic, Scout # and Skylight already parse, injected only if absent so an upstream load # balancer's value always wins. Existing APM gems light up with no changes. env["HTTP_X_REQUEST_START"] ||= "t=#{[:runnable_wall_us]}" env end |