Protocol::RackSourceProtocolRackAdapterGeneric

class Generic

Definitions

def initialize(app)

Initialize the rack adaptor middleware.

Signature

parameter app Object

The rack middleware.

Implementation

def initialize(app)
	@app = app
	
	raise ArgumentError, "App must be callable!" unless @app.respond_to?(:call)
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'] and env['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)
	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 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.

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
returns Protocol::HTTP::Response

Implementation

def failure_response(exception)
	Protocol::HTTP::Response.for_exception(exception)
end