Class: Protocol::Rack::Adapter::Generic
- Inherits:
-
Object
- Object
- Protocol::Rack::Adapter::Generic
- Defined in:
- lib/protocol/rack/adapter/generic.rb
Overview
The base adapter class that provides common functionality for all Rack adapters. It handles the conversion between HTTP and Rack environments.
Class Method Summary collapse
-
.extract_protocol(env, response, headers) ⇒ Object
Extract protocol information from the environment and response.
-
.parse_file ⇒ Object
Parses a Rackup file and returns the application.
-
.wrap(app) ⇒ Object
Creates a new adapter instance for the given Rack application.
Instance Method Summary collapse
-
#call(request) ⇒ Object
Build a rack ‘env` from the incoming request and apply it to the rack middleware.
-
#failure_response(exception) ⇒ Object
Generate a suitable response for the given exception.
-
#handle_error(env, status, headers, body, error) ⇒ Object
Handle errors that occur during request processing.
-
#initialize(app) ⇒ Generic
constructor
Initialize the rack adaptor middleware.
-
#logger ⇒ Object
The logger to use for this adapter.
-
#make_environment(request) ⇒ Object
Create a base environment hash for the request.
-
#unwrap_headers(headers, env) ⇒ Object
Unwrap HTTP headers into the CGI-style expected by Rack middleware, and add them to the rack ‘env`.
-
#unwrap_request(request, env) ⇒ Object
Process the incoming request into a valid rack ‘env`.
Constructor Details
#initialize(app) ⇒ Generic
Initialize the rack adaptor middleware.
41 42 43 44 45 |
# File 'lib/protocol/rack/adapter/generic.rb', line 41 def initialize(app) @app = app raise ArgumentError, "App must be callable!" unless @app.respond_to?(:call) end |
Class Method Details
.extract_protocol(env, response, headers) ⇒ Object
Extract protocol information from the environment and response.
210 211 212 213 214 215 216 217 218 219 220 221 222 |
# File 'lib/protocol/rack/adapter/generic.rb', line 210 def self.extract_protocol(env, response, headers) if protocol = response.protocol # This is the newer mechanism for protocol upgrade: if env["rack.protocol"] headers["rack.protocol"] = protocol # Older mechanism for protocol upgrade: elsif env[CGI::HTTP_UPGRADE] headers["upgrade"] = protocol headers["connection"] = "upgrade" end end end |
.parse_file ⇒ Object
Parses a Rackup file and returns the application.
32 33 34 35 |
# File 'lib/protocol/rack/adapter/generic.rb', line 32 def self.parse_file(...) # This is the old interface, which was changed in Rack 3. ::Rack::Builder.parse_file(...).first end |
.wrap(app) ⇒ Object
Creates a new adapter instance for the given Rack application. Wraps the adapter in a Rewindable instance to ensure request body can be read multiple times, which is required for Rack < 3.
24 25 26 |
# File 'lib/protocol/rack/adapter/generic.rb', line 24 def self.wrap(app) Rewindable.new(self.new(app)) end |
Instance Method Details
#call(request) ⇒ Object
Build a rack ‘env` from the incoming request and apply it to the rack middleware.
175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 |
# File 'lib/protocol/rack/adapter/generic.rb', line 175 def call(request) env = self.make_environment(request) status, headers, body = @app.call(env) # The status must always be an integer. unless status.is_a?(Integer) raise ArgumentError, "Status must be an integer!" end # Headers must always be a hash or equivalent. unless headers raise ArgumentError, "Headers must not be nil!" end headers, = self.wrap_headers(headers) return Response.wrap(env, status, headers, , body, request) rescue => error return self.handle_error(env, status, headers, body, error) end |
#failure_response(exception) ⇒ Object
Generate a suitable response for the given exception.
201 202 203 |
# File 'lib/protocol/rack/adapter/generic.rb', line 201 def failure_response(exception) Protocol::HTTP::Response.for_exception(exception) end |
#handle_error(env, status, headers, body, error) ⇒ Object
Handle errors that occur during request processing. Logs the error, closes any response body, invokes ‘rack.response_finished` callbacks, and returns an appropriate failure response.
The ‘rack.response_finished` callbacks are invoked in reverse order of registration, as specified by the Rack specification. If a callback raises an exception, it is caught and logged, but does not prevent other callbacks from being invoked.
148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 |
# File 'lib/protocol/rack/adapter/generic.rb', line 148 def handle_error(env, status, headers, body, error) Console.error(self, "Error occurred during request processing:", error) # Close the response body if it exists and supports closing. body&.close if body.respond_to?(:close) # Invoke `rack.response_finished` callbacks in reverse order of registration. # This ensures that callbacks registered later are invoked first, matching the Rack specification. env&.[](RACK_RESPONSE_FINISHED)&.reverse_each do |callback| begin callback.call(env, status, headers, error) rescue => callback_error # If a callback raises an exception, log it but continue invoking other callbacks. # The Rack specification states that callbacks should not raise exceptions, but we handle # this gracefully to prevent one misbehaving callback from breaking others. Console.error(self, "Error occurred during response finished callback:", callback_error) end end return failure_response(error) end |
#logger ⇒ Object
The logger to use for this adapter.
50 51 52 |
# File 'lib/protocol/rack/adapter/generic.rb', line 50 def logger Console end |
#make_environment(request) ⇒ Object
Create a base environment hash for the request.
132 133 134 135 136 |
# File 'lib/protocol/rack/adapter/generic.rb', line 132 def make_environment(request) { request: request } end |
#unwrap_headers(headers, env) ⇒ Object
Unwrap HTTP headers into the CGI-style expected by Rack middleware, and add them to the rack ‘env`.
e.g. ‘accept-encoding` becomes `HTTP_ACCEPT_ENCODING`.
Headers keys with underscores will generate the same CGI-style header key as headers with dashes.
e.g ‘accept_encoding` becomes `HTTP_ACCEPT_ENCODING` too.
You should not implicitly trust the ‘HTTP_` headers for security purposes, as they are generated by the client.
Multiple headers are combined with a comma, with one exception: ‘HTTP_COOKIE` headers are combined with a semicolon.
68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 |
# File 'lib/protocol/rack/adapter/generic.rb', line 68 def unwrap_headers(headers, env) headers.each do |key, value| http_key = "HTTP_#{key.upcase.tr('-', '_')}" if current_value = env[http_key] if http_key == CGI::HTTP_COOKIE env[http_key] = "#{current_value};#{value}" else env[http_key] = "#{current_value},#{value}" end else env[http_key] = value end end end |
#unwrap_request(request, env) ⇒ Object
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 |
# File 'lib/protocol/rack/adapter/generic.rb', line 93 def unwrap_request(request, env) # The request protocol, either from the upgrade header or the HTTP/2 pseudo header of the same name. if protocol = request.protocol env[RACK_PROTOCOL] = protocol end if content_type = request.headers.delete("content-type") env[CGI::CONTENT_TYPE] = content_type end # In some situations we don't know the content length, e.g. when using chunked encoding, or when decompressing the body. if body = request.body and length = body.length env[CGI::CONTENT_LENGTH] = length.to_s end self.unwrap_headers(request.headers, env) # For the sake of compatibility, we set the `HTTP_UPGRADE` header to the requested protocol. if protocol = request.protocol and request.version.start_with?("HTTP/1") env[CGI::HTTP_UPGRADE] = Array(protocol).join(",") end if request.respond_to?(:hijack?) and request.hijack? env[RACK_IS_HIJACK] = true env[RACK_HIJACK] = proc{request.hijack!.io} end # HTTP/2 prefers `:authority` over `host`, so we do this for backwards compatibility. env[CGI::HTTP_HOST] ||= request. if peer = request.peer env[CGI::REMOTE_ADDR] = peer.ip_address end end |