-
-
Notifications
You must be signed in to change notification settings - Fork 2
/
rack2.rb
148 lines (112 loc) · 4.5 KB
/
rack2.rb
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
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
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
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
# frozen_string_literal: true
# Released under the MIT License.
# Copyright, 2022-2023, by Samuel Williams.
require 'console'
require_relative 'generic'
require_relative '../rewindable'
module Protocol
module Rack
module Adapter
class Rack2 < Generic
RACK_VERSION = 'rack.version'
RACK_MULTITHREAD = 'rack.multithread'
RACK_MULTIPROCESS = 'rack.multiprocess'
RACK_RUN_ONCE = 'rack.run_once'
RACK_IS_HIJACK = 'rack.hijack?'
RACK_HIJACK = 'rack.hijack'
def self.wrap(app)
Rewindable.new(self.new(app))
end
def make_environment(request)
request_path, query_string = request.path.split('?', 2)
server_name, server_port = (request.authority || '').split(':', 2)
env = {
RACK_VERSION => [2, 0],
RACK_MULTITHREAD => false,
RACK_MULTIPROCESS => true,
RACK_RUN_ONCE => false,
PROTOCOL_HTTP_REQUEST => request,
RACK_INPUT => Input.new(request.body),
RACK_ERRORS => $stderr,
RACK_LOGGER => self.logger,
# The request protocol, either from the upgrade header or the HTTP/2 pseudo header of the same name.
RACK_PROTOCOL => request.protocol,
# The HTTP request method, such as “GET” or “POST”. This cannot ever be an empty string, and so is always required.
CGI::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.
CGI::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.
CGI::PATH_INFO => request_path,
CGI::REQUEST_PATH => request_path,
CGI::REQUEST_URI => request.path,
# The portion of the request URL that follows the ?, if any. May be empty, but is always required!
CGI::QUERY_STRING => query_string || '',
# The server protocol (e.g. HTTP/1.1):
CGI::SERVER_PROTOCOL => request.version,
# The request scheme:
RACK_URL_SCHEME => request.scheme,
# I'm not sure what sane defaults should be here:
CGI::SERVER_NAME => server_name,
CGI::SERVER_PORT => server_port,
}
self.unwrap_request(request, env)
return env
end
# Build a rack `env` from the incoming request and apply it to the rack middleware.
#
# @parameter request [Protocol::HTTP::Request] The incoming request.
def call(request)
env = self.make_environment(request)
status, headers, body = @app.call(env)
headers, meta = self.wrap_headers(headers)
# Rack 2 spec does not allow only partial hijacking.
# if hijack_body = meta[RACK_HIJACK]
# body = hijack_body
# end
return Response.wrap(env, status, headers, meta, body, request)
rescue => exception
Console.logger.error(self) {exception}
body&.close if body.respond_to?(:close)
return failure_response(exception)
end
# Process the rack response headers into into a {Protocol::HTTP::Headers} instance, along with any extra `rack.` metadata.
# @returns [Tuple(Protocol::HTTP::Headers, Hash)]
def wrap_headers(fields)
headers = ::Protocol::HTTP::Headers.new
meta = {}
fields.each do |key, value|
key = key.downcase
if key.start_with?('rack.')
meta[key] = value
elsif value.is_a?(String)
value.split("\n").each do |value|
headers[key] = value
end
else
headers[key] = value
end
end
return headers, meta
end
def self.make_response(env, response)
# These interfaces should be largely compatible:
headers = response.headers.to_h
if protocol = response.protocol
headers['rack.protocol'] = protocol
# headers['upgrade'] = protocol
end
if body = response.body and body.stream?
if env[RACK_IS_HIJACK]
headers[RACK_HIJACK] = body
body = []
end
end
headers.transform_values! do |value|
value.is_a?(Array) ? value.join("\n") : value
end
[response.status, headers, body]
end
end
end
end
end