class Falcon::Adapters::Rack

Constants

ASYNC_HTTP_REQUEST

Async::HTTP specific metadata:

CONTENT_LENGTH
CONTENT_TYPE
HTTP_HOST

CGI keys <tools.ietf.org/html/rfc3875#section-4.1>:

HTTP_X_FORWARDED_PROTO

Header constants:

PATH_INFO
QUERY_STRING
RACK_ERRORS
RACK_HIJACK
RACK_HIJACK_IO
RACK_INPUT
RACK_IS_HIJACK
RACK_LOGGER
RACK_MULTIPROCESS
RACK_MULTITHREAD
RACK_RUNONCE
RACK_URL_SCHEME
RACK_VERSION

Rack environment variables:

REMOTE_ADDR
REQUEST_METHOD
REQUEST_PATH
REQUEST_URI
SCRIPT_NAME
SERVER_NAME
SERVER_PORT
SERVER_PROTOCOL

Public Class Methods

new(app) click to toggle source

Initialize the rack adaptor middleware. @parameter app [Object] The rack middleware.

# File lib/falcon/adapters/rack.rb, line 77
def initialize(app)
        @app = app
        
        raise ArgumentError, "App must be callable!" unless @app.respond_to?(:call)
end

Public Instance Methods

call(request) click to toggle source

Build a rack `env` from the incoming request and apply it to the rack middleware.

@parameter request [Protocol::HTTP::Request] The incoming request.

# File lib/falcon/adapters/rack.rb, line 139
def call(request)
        request_path, query_string = request.path.split('?', 2)
        server_name, server_port = (request.authority || '').split(':', 2)
        
        env = {
                RACK_VERSION => [2, 0, 0],
                
                ASYNC_HTTP_REQUEST => request,
                
                RACK_INPUT => Input.new(request.body),
                RACK_ERRORS => $stderr,
                RACK_LOGGER => Console.logger,
                
                RACK_MULTITHREAD => true,
                RACK_MULTIPROCESS => true,
                RACK_RUNONCE => false,
                
                # The HTTP request method, such as “GET” or “POST”. This cannot ever be an empty string, and so is always required.
                REQUEST_METHOD => request.method,
                
                # The initial portion of the request URL's “path” that corresponds to the application object, so that the application knows its virtual “location”. This may be an empty string, if the application corresponds to the “root” of the server.
                SCRIPT_NAME => '',
                
                # The remainder of the request URL's “path”, designating the virtual “location” of the request's target within the application. This may be an empty string, if the request URL targets the application root and does not have a trailing slash. This value may be percent-encoded when originating from a URL.
                PATH_INFO => request_path,
                REQUEST_PATH => request_path,
                REQUEST_URI => request.path,

                # The portion of the request URL that follows the ?, if any. May be empty, but is always required!
                QUERY_STRING => query_string || '',
                
                # The server protocol (e.g. HTTP/1.1):
                SERVER_PROTOCOL => request.version,
                
                # The request scheme:
                RACK_URL_SCHEME => request.scheme,
                
                # I'm not sure what sane defaults should be here:
                SERVER_NAME => server_name,
                SERVER_PORT => server_port,
                
                # We support both request and response hijack.
                RACK_IS_HIJACK => true,
        }
        
        self.unwrap_request(request, env)
        
        full_hijack = false
        
        if request.hijack?
                env[RACK_HIJACK] = lambda do
                        wrapper = request.hijack!
                        full_hijack = true
                        
                        # We dup this as it might be taken out of the normal control flow, and the io will be closed shortly after returning from this method.
                        io = wrapper.io.dup
                        wrapper.close
                        
                        # This is implicitly returned:
                        env[RACK_HIJACK_IO] = io
                end
        end
        
        status, headers, body = @app.call(env)
        
        # If there was a full hijack:
        if full_hijack
                raise FullHijack, "The connection was hijacked."
        else
                return Response.wrap(status, headers, body, request)
        end
rescue => exception
        Console.logger.error(self) {exception}
        
        return failure_response(exception)
end
failure_response(exception) click to toggle source

Generate a suitable response for the given exception. @parameter exception [Exception] @returns [Protocol::HTTP::Response]

# File lib/falcon/adapters/rack.rb, line 219
def failure_response(exception)
        Protocol::HTTP::Response.for_exception(exception)
end
unwrap_headers(headers, env) click to toggle source

Unwrap raw HTTP headers into the CGI-style expected by Rack middleware.

Rack separates multiple headers with the same key, into a single field with multiple lines.

@parameter headers [Protocol::HTTP::Headers] The raw HTTP request headers. @parameter env [Hash] The rack request `env`.

# File lib/falcon/adapters/rack.rb, line 89
def unwrap_headers(headers, env)
        headers.each do |key, value|
                http_key = "HTTP_#{key.upcase.tr('-', '_')}"
                
                if current_value = env[http_key]
                        env[http_key] = "#{current_value};#{value}"
                else
                        env[http_key] = value
                end
        end
end
unwrap_request(request, env) click to toggle source

Process the incoming request into a valid rack `env`.

  • Set the `env` and `env` based on the incoming request body.

  • Set the `env` header to the request authority.

  • Set the `env` header to the request scheme.

  • Set `env` to the request remote adress.

@parameter request [Protocol::HTTP::Request] The incoming request. @parameter env [Hash] The rack `env`.

# File lib/falcon/adapters/rack.rb, line 110
def unwrap_request(request, env)
        if content_type = request.headers.delete('content-type')
                env[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[CONTENT_LENGTH] = length.to_s
        end
        
        self.unwrap_headers(request.headers, env)
        
        # HTTP/2 prefers `:authority` over `host`, so we do this for backwards compatibility.
        env[HTTP_HOST] ||= request.authority
        
        # This is the HTTP/1 header for the scheme of the request and is used by Rack.
        # Technically it should use the Forwarded header but this is not common yet.
        # https://tools.ietf.org/html/rfc7239#section-5.4
        # https://github.com/rack/rack/issues/1310
        env[HTTP_X_FORWARDED_PROTO] ||= request.scheme
        
        if remote_address = request.remote_address
                env[REMOTE_ADDR] = remote_address.ip_address if remote_address.ip?
        end
end