Class: Protocol::Rack::Adapter::Generic

Inherits:
Object
  • Object
show all
Defined in:
lib/protocol/rack/adapter/generic.rb

Direct Known Subclasses

Rack2, Rack3

Class Method Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(app) ⇒ Generic

Initialize the rack adaptor middleware.

Raises:

  • (ArgumentError)


27
28
29
30
31
# File 'lib/protocol/rack/adapter/generic.rb', line 27

def initialize(app)
	@app = app
	
	raise ArgumentError, "App must be callable!" unless @app.respond_to?(:call)
end

Class Method Details

.parse_fileObject



20
21
22
23
# File 'lib/protocol/rack/adapter/generic.rb', line 20

def self.parse_file(...)
	# This is the old interface, which was changed in Rack 3.
	::Rack::Builder.parse_file(...).first
end

.wrap(app) ⇒ Object



16
17
18
# File 'lib/protocol/rack/adapter/generic.rb', line 16

def self.wrap(app)
	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.



105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
# File 'lib/protocol/rack/adapter/generic.rb', line 105

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

#failure_response(exception) ⇒ Object

Generate a suitable response for the given exception.



138
139
140
# File 'lib/protocol/rack/adapter/generic.rb', line 138

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

#loggerObject



33
34
35
# File 'lib/protocol/rack/adapter/generic.rb', line 33

def logger
	Console
end

#make_environment(request) ⇒ Object



96
97
98
99
100
# File 'lib/protocol/rack/adapter/generic.rb', line 96

def make_environment(request)
	{
		request: request
	}
end

#unwrap_headers(headers, env) ⇒ Object

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.



43
44
45
46
47
48
49
50
51
52
53
# File 'lib/protocol/rack/adapter/generic.rb', line 43

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) ⇒ Object

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.



64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
# File 'lib/protocol/rack/adapter/generic.rb', line 64

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 request.respond_to?(:remote_address)
		if remote_address = request.remote_address
			env[CGI::REMOTE_ADDR] = remote_address.ip_address if remote_address.ip?
		end
	end
end