Class: Quicsilver::Protocol::RequestParser
- Inherits:
-
FrameParser
- Object
- FrameParser
- Quicsilver::Protocol::RequestParser
- Defined in:
- lib/quicsilver/protocol/request_parser.rb
Constant Summary collapse
- METHOD_CONNECT =
"CONNECT"- VALID_PSEUDO_HEADERS =
Known HTTP/3 request pseudo-headers (RFC 9114 §4.3.1)
%w[:method :scheme :authority :path :protocol].freeze
- VALID_PSEUDO_SET =
VALID_PSEUDO_HEADERS.each_with_object({}) { |h, s| s[h] = true }.freeze
- FORBIDDEN_HEADERS =
Connection-specific headers forbidden in HTTP/3 (RFC 9114 §4.2)
%w[connection transfer-encoding keep-alive upgrade proxy-connection te].freeze
- FORBIDDEN_SET =
FORBIDDEN_HEADERS.each_with_object({}) { |h, s| s[h] = true }.freeze
- HEADERS_CACHE =
Cache for validated header results: payload → headers hash Only used when no custom limits are set (max_header_count, max_header_size)
{}
- HEADERS_CACHE_MAX =
256- PARSE_CACHE =
Class-level parse result cache
{}
- PARSE_CACHE_MAX =
128- PARSE_OID_CACHE =
Object-id fast-path for reparse (integer key = faster hash lookup)
{}
- PARSE_OID_CACHE_MAX =
128
Constants inherited from FrameParser
FrameParser::CONTROL_ONLY_SET, FrameParser::DEFAULT_DECODER, FrameParser::EMPTY_BODY
Instance Attribute Summary
Attributes inherited from FrameParser
#bytes_consumed, #headers, #trailers
Instance Method Summary collapse
-
#initialize(data, **opts) ⇒ RequestParser
constructor
A new instance of RequestParser.
- #parse ⇒ Object
-
#priority ⇒ Object
The parsed priority from the ‘priority` header (RFC 9218).
-
#reparse(data) ⇒ Object
Combined reset + parse for maximum throughput (single method call) Cache values stored as [headers, frames, body_str] for fast index access.
-
#reset(data) ⇒ Object
Reset parser with new data for object reuse (avoids allocation overhead).
- #to_rack_env(stream_info = {}) ⇒ Object
-
#validate_headers! ⇒ Object
Validate pseudo-header semantics per RFC 9114 §4.3.1.
Methods inherited from FrameParser
Constructor Details
#initialize(data, **opts) ⇒ RequestParser
Returns a new instance of RequestParser.
31 32 33 34 35 36 37 38 39 |
# File 'lib/quicsilver/protocol/request_parser.rb', line 31 def initialize(data, **opts) decoder = opts.delete(:decoder) || DEFAULT_DECODER super(decoder: decoder, max_body_size: opts[:max_body_size], max_header_size: opts[:max_header_size], max_header_count: opts[:max_header_count], max_frame_payload_size: opts[:max_frame_payload_size]) @data = data @use_parse_cache = @decoder.equal?(DEFAULT_DECODER) && !@max_body_size && !@max_header_size && !@max_header_count && !@max_frame_payload_size end |
Instance Method Details
#parse ⇒ Object
89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 |
# File 'lib/quicsilver/protocol/request_parser.rb', line 89 def parse # Fast path: full parse result cache for default decoder with no limits if @use_parse_cache cached = PARSE_CACHE[@data] if cached @headers = cached[0] @frames = cached[1] @cached_body_str = cached[2] return end end parse! cache_result if @use_parse_cache end |
#priority ⇒ Object
The parsed priority from the ‘priority` header (RFC 9218). Returns a Priority with defaults if no header present.
12 13 14 |
# File 'lib/quicsilver/protocol/request_parser.rb', line 12 def priority @priority ||= Priority.parse(@headers["priority"]) end |
#reparse(data) ⇒ Object
Combined reset + parse for maximum throughput (single method call) Cache values stored as [headers, frames, body_str] for fast index access
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 |
# File 'lib/quicsilver/protocol/request_parser.rb', line 53 def reparse(data) @data = data # Fastest path: same data object as last time — skip all cache lookups return if data.equal?(@last_data) && @headers if @use_parse_cache oid = data.object_id cached = PARSE_OID_CACHE[oid] unless cached cached = PARSE_CACHE[data] PARSE_OID_CACHE[oid] = cached if cached && PARSE_OID_CACHE.size < PARSE_OID_CACHE_MAX end if cached @headers = cached[0] @frames = cached[1] @cached_body_str = cached[2] @last_data = data return end end @headers = {} @trailers = {} @frames = nil @body = nil @cached_body_str = nil parse! cache_result if @use_parse_cache end |
#reset(data) ⇒ Object
Reset parser with new data for object reuse (avoids allocation overhead)
42 43 44 45 46 47 48 49 |
# File 'lib/quicsilver/protocol/request_parser.rb', line 42 def reset(data) @data = data @headers = {} @trailers = {} @frames = nil @body = nil @cached_body_str = nil end |
#to_rack_env(stream_info = {}) ⇒ Object
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 |
# File 'lib/quicsilver/protocol/request_parser.rb', line 160 def to_rack_env(stream_info = {}) return nil if @headers.empty? method = @headers[":method"] if method == METHOD_CONNECT return nil unless @headers[":authority"] else return nil unless method && @headers[":scheme"] && @headers[":path"] end path_full = @headers[":path"] || "" path, query = path_full.split("?", 2) = @headers[":authority"] || "localhost:4433" host, port = .split(":", 2) port ||= "4433" env = { "REQUEST_METHOD" => method, "PATH_INFO" => path || "", "QUERY_STRING" => query || "", "SERVER_NAME" => host, "SERVER_PORT" => port, "SERVER_PROTOCOL" => "HTTP/3", "rack.version" => [1, 3], "rack.url_scheme" => @headers[":scheme"] || "https", "rack.input" => body, "rack.errors" => $stderr, "rack.multithread" => true, "rack.multiprocess" => false, "rack.run_once" => false, "rack.hijack?" => false, "SCRIPT_NAME" => "", "CONTENT_LENGTH" => body.size.to_s, } if @headers[":authority"] env["HTTP_HOST"] = @headers[":authority"] end @headers.each do |name, value| next if name.start_with?(":") key = name.upcase.tr("-", "_") if key == "CONTENT_TYPE" env["CONTENT_TYPE"] = value elsif key == "CONTENT_LENGTH" env["CONTENT_LENGTH"] = value else env["HTTP_#{key}"] = value end end env end |
#validate_headers! ⇒ Object
Validate pseudo-header semantics per RFC 9114 §4.3.1. Call after parse to check CONNECT rules, required headers, host/:authority consistency.
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 |
# File 'lib/quicsilver/protocol/request_parser.rb', line 125 def validate_headers! return if @headers.empty? method = @headers[":method"] if method == METHOD_CONNECT && @headers[":protocol"] # Extended CONNECT (RFC 9220) — requires :scheme, :path, :authority, :protocol raise Protocol::MessageError, "Extended CONNECT must include :scheme" unless @headers[":scheme"] raise Protocol::MessageError, "Extended CONNECT must include :path" unless @headers[":path"] raise Protocol::MessageError, "Extended CONNECT must include :authority" unless @headers[":authority"] elsif method == METHOD_CONNECT # Regular CONNECT (RFC 9114 §4.4) raise Protocol::MessageError, "CONNECT request must include :authority" unless @headers[":authority"] raise Protocol::MessageError, "CONNECT request must not include :scheme" if @headers[":scheme"] raise Protocol::MessageError, "CONNECT request must not include :path" if @headers[":path"] else raise Protocol::MessageError, "Request missing required pseudo-header :method" unless method raise Protocol::MessageError, "Request missing required pseudo-header :scheme" unless @headers[":scheme"] raise Protocol::MessageError, "Request missing required pseudo-header :path" unless @headers[":path"] # RFC 9114 §4.3.1: schemes with mandatory authority (http/https) require :authority or host scheme = @headers[":scheme"] if %w[http https].include?(scheme) && !@headers[":authority"] && !@headers["host"] raise Protocol::MessageError, "Request with #{scheme} scheme must include :authority or host" end end # Host and :authority consistency (RFC 9114 §4.3.1) if @headers[":authority"] && @headers["host"] unless @headers[":authority"] == @headers["host"] raise Protocol::MessageError, ":authority and host header must be consistent" end end end |