class Generic
The base adapter class that provides common functionality for all Rack adapters.
It handles the conversion between Protocol::HTTP
and Rack environments.
Definitions
def self.wrap(app)
Creates a new adapter instance for the given Rack application.
Wraps the adapter in a class Protocol::Rack::Rewindable
instance to ensure request body can be read multiple times, which is required for Rack < 3.
Signature
-
parameter
app
Interface(:call)
A Rack application.
-
returns
Rewindable
A rewindable adapter instance.
Implementation
def self.wrap(app)
Rewindable.new(self.new(app))
end
def self.parse_file(...)
Parses a Rackup file and returns the application.
Signature
-
parameter
path
String
The path to the Rackup file.
-
returns
Interface(:call)
The Rack application.
Implementation
def self.parse_file(...)
# This is the old interface, which was changed in Rack 3.
::Rack::Builder.parse_file(...).first
end
def initialize(app)
Initialize the rack adaptor middleware.
Signature
-
parameter
app
Interface(:call)
The rack middleware.
-
raises
ArgumentError
If the app does not respond to
call
.
Implementation
def initialize(app)
@app = app
raise ArgumentError, "App must be callable!" unless @app.respond_to?(:call)
end
def logger
The logger to use for this adapter.
Signature
-
returns
Console
The console logger.
Implementation
def logger
Console
end
def unwrap_headers(headers, env)
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.
Signature
-
parameter
headers
Protocol::HTTP::Headers
The raw HTTP request headers.
-
parameter
env
Hash
The rack request
env
.
Implementation
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
def unwrap_request(request, env)
Process the incoming request into a valid rack env
.
- Set the
env['CONTENT_TYPE']
andenv['CONTENT_LENGTH']
based on the incoming request body. - Set the
env['HTTP_HOST']
header to the request authority. - Set the
env['HTTP_X_FORWARDED_PROTO']
header to the request scheme. - Set
env['REMOTE_ADDR']
to the request remote adress.
Signature
-
parameter
request
Protocol::HTTP::Request
The incoming request.
-
parameter
env
Hash
The rack
env
.
Implementation
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.dup}
end
# HTTP/2 prefers `:authority` over `host`, so we do this for backwards compatibility.
env[CGI::HTTP_HOST] ||= request.authority
if peer = request.peer
env[CGI::REMOTE_ADDR] = peer.ip_address
end
end
def make_environment(request)
Create a base environment hash for the request.
Signature
-
parameter
request
Protocol::HTTP::Request
The incoming request.
-
returns
Hash
The base environment hash.
Implementation
def make_environment(request)
{
request: request
}
end
def call(request)
Build a rack env
from the incoming request and apply it to the rack middleware.
Signature
-
parameter
request
Protocol::HTTP::Request
The incoming request.
-
returns
Protocol::HTTP::Response
The HTTP response.
-
raises
ArgumentError
If the status is not an integer or headers are nil.
Implementation
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, meta = self.wrap_headers(headers)
return Response.wrap(env, status, headers, meta, body, request)
rescue => exception
Console.error(self, exception)
body&.close if body.respond_to?(:close)
env&.[](RACK_RESPONSE_FINISHED)&.each do |callback|
callback.call(env, status, headers, exception)
end
return failure_response(exception)
end
def failure_response(exception)
Generate a suitable response for the given exception.
Signature
-
parameter
exception
Exception
The exception that occurred.
-
returns
Protocol::HTTP::Response
A response representing the error.
Implementation
def failure_response(exception)
Protocol::HTTP::Response.for_exception(exception)
end
def self.extract_protocol(env, response, headers)
Extract protocol information from the environment and response.
Signature
-
parameter
env
Hash
The rack environment.
-
parameter
response
Protocol::HTTP::Response
The HTTP response.
-
parameter
headers
Hash
The response headers to modify.
Implementation
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