protocol-rack 0.1.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- checksums.yaml.gz.sig +0 -0
- data/lib/protocol/rack/adapter/generic.rb +126 -0
- data/lib/protocol/rack/adapter/rack2.rb +112 -0
- data/lib/protocol/rack/adapter/rack3.rb +96 -0
- data/lib/protocol/rack/adapter.rb +44 -0
- data/lib/protocol/rack/body/enumerable.rb +119 -0
- data/lib/protocol/rack/body/input_wrapper.rb +59 -0
- data/lib/protocol/rack/body/streaming.rb +72 -0
- data/lib/protocol/rack/body.rb +58 -0
- data/lib/protocol/rack/constants.rb +62 -0
- data/lib/protocol/rack/input.rb +125 -0
- data/lib/protocol/rack/request.rb +72 -0
- data/lib/protocol/rack/response.rb +118 -0
- data/lib/protocol/rack/rewindable.rb +79 -0
- data/lib/protocol/rack/version.rb +27 -0
- data/lib/protocol/rack.rb +26 -0
- data.tar.gz.sig +1 -0
- metadata +114 -0
- metadata.gz.sig +0 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA256:
|
3
|
+
metadata.gz: 336b02d7bf414f258948e6b3c865ff4ede58f534d6d883b9115d6e958fc40716
|
4
|
+
data.tar.gz: a83c9865d89da8d67873c7b91ddee8ae07e4eaf57191854834c12c0c0a7aee6a
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: 696e7b6094fdc826d28b5e13a05069fab7bcdb090e65add3c515a57ea3a452af43fd87b68ca22c2ad7869d73d065247c5f48656abfb0523ae66a451dcba026d3
|
7
|
+
data.tar.gz: 6d3143e5bea719225288bbb74e74d924c1f405a89ff358cea1679c71c5ec680a43b64ebf6bc8862b5ee6cf890f0241ab5fafddedf0c80a9c3b132574ca3d7fc9
|
checksums.yaml.gz.sig
ADDED
Binary file
|
@@ -0,0 +1,126 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
# Copyright, 2017, by Samuel G. D. Williams. <http://www.codeotaku.com>
|
4
|
+
#
|
5
|
+
# Permission is hereby granted, free of charge, to any person obtaining a copy
|
6
|
+
# of this software and associated documentation files (the "Software"), to deal
|
7
|
+
# in the Software without restriction, including without limitation the rights
|
8
|
+
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
9
|
+
# copies of the Software, and to permit persons to whom the Software is
|
10
|
+
# furnished to do so, subject to the following conditions:
|
11
|
+
#
|
12
|
+
# The above copyright notice and this permission notice shall be included in
|
13
|
+
# all copies or substantial portions of the Software.
|
14
|
+
#
|
15
|
+
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
16
|
+
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
17
|
+
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
18
|
+
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
19
|
+
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
20
|
+
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
21
|
+
# THE SOFTWARE.
|
22
|
+
|
23
|
+
require 'console'
|
24
|
+
|
25
|
+
require_relative '../constants'
|
26
|
+
require_relative '../input'
|
27
|
+
require_relative '../response'
|
28
|
+
|
29
|
+
module Protocol
|
30
|
+
module Rack
|
31
|
+
module Adapter
|
32
|
+
class Generic
|
33
|
+
def self.wrap(app)
|
34
|
+
self.new(app)
|
35
|
+
end
|
36
|
+
|
37
|
+
# Initialize the rack adaptor middleware.
|
38
|
+
# @parameter app [Object] The rack middleware.
|
39
|
+
def initialize(app)
|
40
|
+
@app = app
|
41
|
+
|
42
|
+
raise ArgumentError, "App must be callable!" unless @app.respond_to?(:call)
|
43
|
+
end
|
44
|
+
|
45
|
+
def logger
|
46
|
+
Console.logger
|
47
|
+
end
|
48
|
+
|
49
|
+
# Unwrap raw HTTP headers into the CGI-style expected by Rack middleware.
|
50
|
+
#
|
51
|
+
# Rack separates multiple headers with the same key, into a single field with multiple lines.
|
52
|
+
#
|
53
|
+
# @parameter headers [Protocol::HTTP::Headers] The raw HTTP request headers.
|
54
|
+
# @parameter env [Hash] The rack request `env`.
|
55
|
+
def unwrap_headers(headers, env)
|
56
|
+
headers.each do |key, value|
|
57
|
+
http_key = "HTTP_#{key.upcase.tr('-', '_')}"
|
58
|
+
|
59
|
+
if current_value = env[http_key]
|
60
|
+
env[http_key] = "#{current_value};#{value}"
|
61
|
+
else
|
62
|
+
env[http_key] = value
|
63
|
+
end
|
64
|
+
end
|
65
|
+
end
|
66
|
+
|
67
|
+
# Process the incoming request into a valid rack `env`.
|
68
|
+
#
|
69
|
+
# - Set the `env['CONTENT_TYPE']` and `env['CONTENT_LENGTH']` based on the incoming request body.
|
70
|
+
# - Set the `env['HTTP_HOST']` header to the request authority.
|
71
|
+
# - Set the `env['HTTP_X_FORWARDED_PROTO']` header to the request scheme.
|
72
|
+
# - Set `env['REMOTE_ADDR']` to the request remote adress.
|
73
|
+
#
|
74
|
+
# @parameter request [Protocol::HTTP::Request] The incoming request.
|
75
|
+
# @parameter env [Hash] The rack `env`.
|
76
|
+
def unwrap_request(request, env)
|
77
|
+
if content_type = request.headers.delete('content-type')
|
78
|
+
env[CGI::CONTENT_TYPE] = content_type
|
79
|
+
end
|
80
|
+
|
81
|
+
# In some situations we don't know the content length, e.g. when using chunked encoding, or when decompressing the body.
|
82
|
+
if body = request.body and length = body.length
|
83
|
+
env[CGI::CONTENT_LENGTH] = length.to_s
|
84
|
+
end
|
85
|
+
|
86
|
+
self.unwrap_headers(request.headers, env)
|
87
|
+
|
88
|
+
# HTTP/2 prefers `:authority` over `host`, so we do this for backwards compatibility.
|
89
|
+
env[CGI::HTTP_HOST] ||= request.authority
|
90
|
+
|
91
|
+
if request.respond_to?(:remote_address)
|
92
|
+
if remote_address = request.remote_address
|
93
|
+
env[CGI::REMOTE_ADDR] = remote_address.ip_address if remote_address.ip?
|
94
|
+
end
|
95
|
+
end
|
96
|
+
end
|
97
|
+
|
98
|
+
# Build a rack `env` from the incoming request and apply it to the rack middleware.
|
99
|
+
#
|
100
|
+
# @parameter request [Protocol::HTTP::Request] The incoming request.
|
101
|
+
def call(request)
|
102
|
+
env = self.make_environment(request)
|
103
|
+
|
104
|
+
status, headers, body = @app.call(env)
|
105
|
+
|
106
|
+
return Response.wrap(status, headers, body, request)
|
107
|
+
rescue => exception
|
108
|
+
Console.logger.error(self) {exception}
|
109
|
+
|
110
|
+
body&.close if body.respond_to?(:close)
|
111
|
+
|
112
|
+
return failure_response(exception)
|
113
|
+
end
|
114
|
+
|
115
|
+
private
|
116
|
+
|
117
|
+
# Generate a suitable response for the given exception.
|
118
|
+
# @parameter exception [Exception]
|
119
|
+
# @returns [Protocol::HTTP::Response]
|
120
|
+
def failure_response(exception)
|
121
|
+
Protocol::HTTP::Response.for_exception(exception)
|
122
|
+
end
|
123
|
+
end
|
124
|
+
end
|
125
|
+
end
|
126
|
+
end
|
@@ -0,0 +1,112 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
# Copyright, 2017, by Samuel G. D. Williams. <http://www.codeotaku.com>
|
4
|
+
#
|
5
|
+
# Permission is hereby granted, free of charge, to any person obtaining a copy
|
6
|
+
# of this software and associated documentation files (the "Software"), to deal
|
7
|
+
# in the Software without restriction, including without limitation the rights
|
8
|
+
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
9
|
+
# copies of the Software, and to permit persons to whom the Software is
|
10
|
+
# furnished to do so, subject to the following conditions:
|
11
|
+
#
|
12
|
+
# The above copyright notice and this permission notice shall be included in
|
13
|
+
# all copies or substantial portions of the Software.
|
14
|
+
#
|
15
|
+
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
16
|
+
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
17
|
+
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
18
|
+
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
19
|
+
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
20
|
+
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
21
|
+
# THE SOFTWARE.
|
22
|
+
|
23
|
+
require 'console'
|
24
|
+
|
25
|
+
require_relative 'generic'
|
26
|
+
require_relative '../rewindable'
|
27
|
+
|
28
|
+
module Protocol
|
29
|
+
module Rack
|
30
|
+
module Adapter
|
31
|
+
class Rack2 < Generic
|
32
|
+
RACK_VERSION = 'rack.version'
|
33
|
+
RACK_MULTITHREAD = 'rack.multithread'
|
34
|
+
RACK_MULTIPROCESS = 'rack.multiprocess'
|
35
|
+
RACK_RUN_ONCE = 'rack.run_once'
|
36
|
+
|
37
|
+
RACK_IS_HIJACK = 'rack.hijack?'
|
38
|
+
RACK_HIJACK = 'rack.hijack'
|
39
|
+
|
40
|
+
def self.wrap(app)
|
41
|
+
Rewindable.new(self.new(app))
|
42
|
+
end
|
43
|
+
|
44
|
+
def make_environment(request)
|
45
|
+
request_path, query_string = request.path.split('?', 2)
|
46
|
+
server_name, server_port = (request.authority || '').split(':', 2)
|
47
|
+
|
48
|
+
env = {
|
49
|
+
RACK_VERSION => [2, 0],
|
50
|
+
RACK_MULTITHREAD => false,
|
51
|
+
RACK_MULTIPROCESS => true,
|
52
|
+
RACK_RUN_ONCE => false,
|
53
|
+
|
54
|
+
PROTOCOL_HTTP_REQUEST => request,
|
55
|
+
|
56
|
+
RACK_INPUT => Input.new(request.body),
|
57
|
+
RACK_ERRORS => $stderr,
|
58
|
+
RACK_LOGGER => self.logger,
|
59
|
+
|
60
|
+
# The request protocol, either from the upgrade header or the HTTP/2 pseudo header of the same name.
|
61
|
+
RACK_PROTOCOL => request.protocol,
|
62
|
+
|
63
|
+
# The HTTP request method, such as “GET” or “POST”. This cannot ever be an empty string, and so is always required.
|
64
|
+
CGI::REQUEST_METHOD => request.method,
|
65
|
+
|
66
|
+
# 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.
|
67
|
+
CGI::SCRIPT_NAME => '',
|
68
|
+
|
69
|
+
# 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.
|
70
|
+
CGI::PATH_INFO => request_path,
|
71
|
+
CGI::REQUEST_PATH => request_path,
|
72
|
+
CGI::REQUEST_URI => request.path,
|
73
|
+
|
74
|
+
# The portion of the request URL that follows the ?, if any. May be empty, but is always required!
|
75
|
+
CGI::QUERY_STRING => query_string || '',
|
76
|
+
|
77
|
+
# The server protocol (e.g. HTTP/1.1):
|
78
|
+
CGI::SERVER_PROTOCOL => request.version,
|
79
|
+
|
80
|
+
# The request scheme:
|
81
|
+
RACK_URL_SCHEME => request.scheme,
|
82
|
+
|
83
|
+
# I'm not sure what sane defaults should be here:
|
84
|
+
CGI::SERVER_NAME => server_name,
|
85
|
+
CGI::SERVER_PORT => server_port,
|
86
|
+
}
|
87
|
+
|
88
|
+
self.unwrap_request(request, env)
|
89
|
+
|
90
|
+
return env
|
91
|
+
end
|
92
|
+
|
93
|
+
def self.make_response(env, response)
|
94
|
+
# These interfaces should be largely compatible:
|
95
|
+
headers = response.headers.to_h
|
96
|
+
if protocol = response.protocol
|
97
|
+
headers['rack.protocol'] = protocol
|
98
|
+
end
|
99
|
+
|
100
|
+
if body = response.body and body.stream?
|
101
|
+
if env[RACK_IS_HIJACK]
|
102
|
+
headers[RACK_HIJACK] = body
|
103
|
+
body = []
|
104
|
+
end
|
105
|
+
end
|
106
|
+
|
107
|
+
[response.status, headers, body]
|
108
|
+
end
|
109
|
+
end
|
110
|
+
end
|
111
|
+
end
|
112
|
+
end
|
@@ -0,0 +1,96 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
# Copyright, 2017, by Samuel G. D. Williams. <http://www.codeotaku.com>
|
4
|
+
#
|
5
|
+
# Permission is hereby granted, free of charge, to any person obtaining a copy
|
6
|
+
# of this software and associated documentation files (the "Software"), to deal
|
7
|
+
# in the Software without restriction, including without limitation the rights
|
8
|
+
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
9
|
+
# copies of the Software, and to permit persons to whom the Software is
|
10
|
+
# furnished to do so, subject to the following conditions:
|
11
|
+
#
|
12
|
+
# The above copyright notice and this permission notice shall be included in
|
13
|
+
# all copies or substantial portions of the Software.
|
14
|
+
#
|
15
|
+
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
16
|
+
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
17
|
+
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
18
|
+
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
19
|
+
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
20
|
+
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
21
|
+
# THE SOFTWARE.
|
22
|
+
|
23
|
+
require 'console'
|
24
|
+
|
25
|
+
require_relative 'generic'
|
26
|
+
|
27
|
+
module Protocol
|
28
|
+
module Rack
|
29
|
+
module Adapter
|
30
|
+
class Rack3 < Generic
|
31
|
+
def self.wrap(app)
|
32
|
+
self.new(app)
|
33
|
+
end
|
34
|
+
|
35
|
+
def make_environment(request)
|
36
|
+
request_path, query_string = request.path.split('?', 2)
|
37
|
+
server_name, server_port = (request.authority || '').split(':', 2)
|
38
|
+
|
39
|
+
env = {
|
40
|
+
PROTOCOL_HTTP_REQUEST => request,
|
41
|
+
|
42
|
+
RACK_INPUT => Input.new(request.body),
|
43
|
+
RACK_ERRORS => $stderr,
|
44
|
+
RACK_LOGGER => self.logger,
|
45
|
+
|
46
|
+
# The request protocol, either from the upgrade header or the HTTP/2 pseudo header of the same name.
|
47
|
+
RACK_PROTOCOL => request.protocol,
|
48
|
+
|
49
|
+
# The HTTP request method, such as “GET” or “POST”. This cannot ever be an empty string, and so is always required.
|
50
|
+
CGI::REQUEST_METHOD => request.method,
|
51
|
+
|
52
|
+
# 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.
|
53
|
+
CGI::SCRIPT_NAME => '',
|
54
|
+
|
55
|
+
# 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.
|
56
|
+
CGI::PATH_INFO => request_path,
|
57
|
+
CGI::REQUEST_PATH => request_path,
|
58
|
+
CGI::REQUEST_URI => request.path,
|
59
|
+
|
60
|
+
# The portion of the request URL that follows the ?, if any. May be empty, but is always required!
|
61
|
+
CGI::QUERY_STRING => query_string || '',
|
62
|
+
|
63
|
+
# The server protocol (e.g. HTTP/1.1):
|
64
|
+
CGI::SERVER_PROTOCOL => request.version,
|
65
|
+
|
66
|
+
# The request scheme:
|
67
|
+
RACK_URL_SCHEME => request.scheme,
|
68
|
+
|
69
|
+
# I'm not sure what sane defaults should be here:
|
70
|
+
CGI::SERVER_NAME => server_name,
|
71
|
+
CGI::SERVER_PORT => server_port,
|
72
|
+
}
|
73
|
+
|
74
|
+
self.unwrap_request(request, env)
|
75
|
+
|
76
|
+
return env
|
77
|
+
end
|
78
|
+
|
79
|
+
def self.make_response(env, response)
|
80
|
+
# These interfaces should be largely compatible:
|
81
|
+
headers = response.headers.to_h
|
82
|
+
if protocol = response.protocol
|
83
|
+
headers['rack.protocol'] = protocol
|
84
|
+
end
|
85
|
+
|
86
|
+
if body = response.body and body.stream?
|
87
|
+
# Force streaming response:
|
88
|
+
body = proc{|stream| body.call(stream)}
|
89
|
+
end
|
90
|
+
|
91
|
+
[response.status, headers, body]
|
92
|
+
end
|
93
|
+
end
|
94
|
+
end
|
95
|
+
end
|
96
|
+
end
|
@@ -0,0 +1,44 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
# Copyright, 2017, by Samuel G. D. Williams. <http://www.codeotaku.com>
|
4
|
+
#
|
5
|
+
# Permission is hereby granted, free of charge, to any person obtaining a copy
|
6
|
+
# of this software and associated documentation files (the "Software"), to deal
|
7
|
+
# in the Software without restriction, including without limitation the rights
|
8
|
+
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
9
|
+
# copies of the Software, and to permit persons to whom the Software is
|
10
|
+
# furnished to do so, subject to the following conditions:
|
11
|
+
#
|
12
|
+
# The above copyright notice and this permission notice shall be included in
|
13
|
+
# all copies or substantial portions of the Software.
|
14
|
+
#
|
15
|
+
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
16
|
+
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
17
|
+
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
18
|
+
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
19
|
+
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
20
|
+
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
21
|
+
# THE SOFTWARE.
|
22
|
+
|
23
|
+
require_relative 'adapter/rack2'
|
24
|
+
require_relative 'adapter/rack3'
|
25
|
+
|
26
|
+
module Protocol
|
27
|
+
module Rack
|
28
|
+
module Adapter
|
29
|
+
if ::Rack::RELEASE >= "3"
|
30
|
+
IMPLEMENTATION = Rack3
|
31
|
+
else
|
32
|
+
IMPLEMENTATION = Rack2
|
33
|
+
end
|
34
|
+
|
35
|
+
def self.new(app)
|
36
|
+
IMPLEMENTATION.wrap(app)
|
37
|
+
end
|
38
|
+
|
39
|
+
def self.make_response(env, response)
|
40
|
+
IMPLEMENTATION.make_response(env, response)
|
41
|
+
end
|
42
|
+
end
|
43
|
+
end
|
44
|
+
end
|
@@ -0,0 +1,119 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
# Copyright, 2018, by Samuel G. D. Williams. <http://www.codeotaku.com>
|
4
|
+
#
|
5
|
+
# Permission is hereby granted, free of charge, to any person obtaining a copy
|
6
|
+
# of this software and associated documentation files (the "Software"), to deal
|
7
|
+
# in the Software without restriction, including without limitation the rights
|
8
|
+
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
9
|
+
# copies of the Software, and to permit persons to whom the Software is
|
10
|
+
# furnished to do so, subject to the following conditions:
|
11
|
+
#
|
12
|
+
# The above copyright notice and this permission notice shall be included in
|
13
|
+
# all copies or substantial portions of the Software.
|
14
|
+
#
|
15
|
+
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
16
|
+
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
17
|
+
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
18
|
+
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
19
|
+
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
20
|
+
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
21
|
+
# THE SOFTWARE.
|
22
|
+
|
23
|
+
require 'protocol/http/body/readable'
|
24
|
+
require 'protocol/http/body/file'
|
25
|
+
|
26
|
+
module Protocol
|
27
|
+
module Rack
|
28
|
+
module Body
|
29
|
+
# Wraps the rack response body.
|
30
|
+
#
|
31
|
+
# The `rack` body must respond to `each` and must only yield `String` values. If the body responds to `close`, it will be called after iteration.
|
32
|
+
class Enumerable < ::Protocol::HTTP::Body::Readable
|
33
|
+
CONTENT_LENGTH = 'content-length'.freeze
|
34
|
+
|
35
|
+
# Wraps an array into a buffered body.
|
36
|
+
# @parameter body [Object] The `rack` response body.
|
37
|
+
def self.wrap(body, length = nil)
|
38
|
+
if body.is_a?(Array)
|
39
|
+
length ||= body.sum(&:bytesize)
|
40
|
+
return self.new(body, length)
|
41
|
+
else
|
42
|
+
return self.new(body, length)
|
43
|
+
end
|
44
|
+
end
|
45
|
+
|
46
|
+
# Initialize the output wrapper.
|
47
|
+
# @parameter body [Object] The rack response body.
|
48
|
+
# @parameter length [Integer] The rack response length.
|
49
|
+
def initialize(body, length)
|
50
|
+
@length = length
|
51
|
+
@body = body
|
52
|
+
|
53
|
+
@chunks = nil
|
54
|
+
end
|
55
|
+
|
56
|
+
# The rack response body.
|
57
|
+
attr :body
|
58
|
+
|
59
|
+
# The content length of the rack response body.
|
60
|
+
attr :length
|
61
|
+
|
62
|
+
# Whether the body is empty.
|
63
|
+
def empty?
|
64
|
+
@length == 0 or (@body.respond_to?(:empty?) and @body.empty?)
|
65
|
+
end
|
66
|
+
|
67
|
+
# Whether the body can be read immediately.
|
68
|
+
def ready?
|
69
|
+
body.is_a?(Array) or body.respond_to?(:to_ary)
|
70
|
+
end
|
71
|
+
|
72
|
+
# Close the response body.
|
73
|
+
def close(error = nil)
|
74
|
+
if @body and @body.respond_to?(:close)
|
75
|
+
@body.close
|
76
|
+
end
|
77
|
+
|
78
|
+
@body = nil
|
79
|
+
@chunks = nil
|
80
|
+
|
81
|
+
super
|
82
|
+
end
|
83
|
+
|
84
|
+
# Enumerate the response body.
|
85
|
+
# @yields {|chunk| ...}
|
86
|
+
# @parameter chunk [String]
|
87
|
+
def each(&block)
|
88
|
+
@body.each(&block)
|
89
|
+
ensure
|
90
|
+
self.close($!)
|
91
|
+
end
|
92
|
+
|
93
|
+
def stream?
|
94
|
+
!@body.respond_to?(:each)
|
95
|
+
end
|
96
|
+
|
97
|
+
def call(stream)
|
98
|
+
@body.call(stream)
|
99
|
+
ensure
|
100
|
+
self.close($!)
|
101
|
+
end
|
102
|
+
|
103
|
+
# Read the next chunk from the response body.
|
104
|
+
# @returns [String | Nil]
|
105
|
+
def read
|
106
|
+
@chunks ||= @body.to_enum(:each)
|
107
|
+
|
108
|
+
return @chunks.next
|
109
|
+
rescue StopIteration
|
110
|
+
return nil
|
111
|
+
end
|
112
|
+
|
113
|
+
def inspect
|
114
|
+
"\#<#{self.class} length=#{@length.inspect} body=#{@body.class}>"
|
115
|
+
end
|
116
|
+
end
|
117
|
+
end
|
118
|
+
end
|
119
|
+
end
|
@@ -0,0 +1,59 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
# Copyright, 2018, by Samuel G. D. Williams. <http://www.codeotaku.com>
|
4
|
+
#
|
5
|
+
# Permission is hereby granted, free of charge, to any person obtaining a copy
|
6
|
+
# of this software and associated documentation files (the "Software"), to deal
|
7
|
+
# in the Software without restriction, including without limitation the rights
|
8
|
+
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
9
|
+
# copies of the Software, and to permit persons to whom the Software is
|
10
|
+
# furnished to do so, subject to the following conditions:
|
11
|
+
#
|
12
|
+
# The above copyright notice and this permission notice shall be included in
|
13
|
+
# all copies or substantial portions of the Software.
|
14
|
+
#
|
15
|
+
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
16
|
+
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
17
|
+
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
18
|
+
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
19
|
+
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
20
|
+
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
21
|
+
# THE SOFTWARE.
|
22
|
+
|
23
|
+
require 'protocol/http/body/readable'
|
24
|
+
require 'protocol/http/body/stream'
|
25
|
+
|
26
|
+
module Protocol
|
27
|
+
module Rack
|
28
|
+
module Body
|
29
|
+
# Used for wrapping a generic `rack.input` object into a readable body.
|
30
|
+
class InputWrapper < Protocol::HTTP::Body::Readable
|
31
|
+
BLOCK_SIZE = 1024*4
|
32
|
+
|
33
|
+
def initialize(io, block_size: BLOCK_SIZE)
|
34
|
+
@io = io
|
35
|
+
@block_size = block_size
|
36
|
+
|
37
|
+
super()
|
38
|
+
end
|
39
|
+
|
40
|
+
def close(error = nil)
|
41
|
+
if @io
|
42
|
+
@io.close
|
43
|
+
@io = nil
|
44
|
+
end
|
45
|
+
end
|
46
|
+
|
47
|
+
# def join
|
48
|
+
# @io.read.tap do |buffer|
|
49
|
+
# buffer.force_encoding(Encoding::BINARY)
|
50
|
+
# end
|
51
|
+
# end
|
52
|
+
|
53
|
+
def read
|
54
|
+
@io&.read(@block_size)
|
55
|
+
end
|
56
|
+
end
|
57
|
+
end
|
58
|
+
end
|
59
|
+
end
|
@@ -0,0 +1,72 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
# Copyright, 2018, by Samuel G. D. Williams. <http://www.codeotaku.com>
|
4
|
+
#
|
5
|
+
# Permission is hereby granted, free of charge, to any person obtaining a copy
|
6
|
+
# of this software and associated documentation files (the "Software"), to deal
|
7
|
+
# in the Software without restriction, including without limitation the rights
|
8
|
+
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
9
|
+
# copies of the Software, and to permit persons to whom the Software is
|
10
|
+
# furnished to do so, subject to the following conditions:
|
11
|
+
#
|
12
|
+
# The above copyright notice and this permission notice shall be included in
|
13
|
+
# all copies or substantial portions of the Software.
|
14
|
+
#
|
15
|
+
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
16
|
+
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
17
|
+
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
18
|
+
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
19
|
+
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
20
|
+
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
21
|
+
# THE SOFTWARE.
|
22
|
+
|
23
|
+
require 'protocol/http/body/readable'
|
24
|
+
require 'protocol/http/body/stream'
|
25
|
+
|
26
|
+
module Protocol
|
27
|
+
module Rack
|
28
|
+
module Body
|
29
|
+
# Wraps a streaming response body into a compatible Protocol::HTTP body.
|
30
|
+
class Streaming < ::Protocol::HTTP::Body::Readable
|
31
|
+
def initialize(block, input = nil)
|
32
|
+
@block = block
|
33
|
+
@input = input
|
34
|
+
end
|
35
|
+
|
36
|
+
attr :block
|
37
|
+
|
38
|
+
def each(&block)
|
39
|
+
stream = ::Protocol::HTTP::Body::Stream.new(@input, Output.new(block))
|
40
|
+
|
41
|
+
@block.call(stream)
|
42
|
+
end
|
43
|
+
|
44
|
+
def stream?
|
45
|
+
true
|
46
|
+
end
|
47
|
+
|
48
|
+
def call(stream)
|
49
|
+
@block.call(stream)
|
50
|
+
end
|
51
|
+
|
52
|
+
class Output
|
53
|
+
def initialize(block)
|
54
|
+
@block = block
|
55
|
+
end
|
56
|
+
|
57
|
+
def write(chunk)
|
58
|
+
@block.call(chunk)
|
59
|
+
end
|
60
|
+
|
61
|
+
def close
|
62
|
+
@block = nil
|
63
|
+
end
|
64
|
+
|
65
|
+
def empty?
|
66
|
+
true
|
67
|
+
end
|
68
|
+
end
|
69
|
+
end
|
70
|
+
end
|
71
|
+
end
|
72
|
+
end
|
@@ -0,0 +1,58 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
# Copyright, 2018, by Samuel G. D. Williams. <http://www.codeotaku.com>
|
4
|
+
#
|
5
|
+
# Permission is hereby granted, free of charge, to any person obtaining a copy
|
6
|
+
# of this software and associated documentation files (the "Software"), to deal
|
7
|
+
# in the Software without restriction, including without limitation the rights
|
8
|
+
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
9
|
+
# copies of the Software, and to permit persons to whom the Software is
|
10
|
+
# furnished to do so, subject to the following conditions:
|
11
|
+
#
|
12
|
+
# The above copyright notice and this permission notice shall be included in
|
13
|
+
# all copies or substantial portions of the Software.
|
14
|
+
#
|
15
|
+
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
16
|
+
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
17
|
+
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
18
|
+
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
19
|
+
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
20
|
+
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
21
|
+
# THE SOFTWARE.
|
22
|
+
|
23
|
+
require_relative 'body/streaming'
|
24
|
+
require_relative 'body/enumerable'
|
25
|
+
|
26
|
+
module Protocol
|
27
|
+
module Rack
|
28
|
+
module Body
|
29
|
+
CONTENT_LENGTH = 'content-length'
|
30
|
+
|
31
|
+
def self.wrap(status, headers, body, input = nil)
|
32
|
+
# In no circumstance do we want this header propagating out:
|
33
|
+
if length = headers.delete(CONTENT_LENGTH)
|
34
|
+
# We don't really trust the user to provide the right length to the transport.
|
35
|
+
length = Integer(length)
|
36
|
+
end
|
37
|
+
|
38
|
+
# If we have an Async::HTTP body, we return it directly:
|
39
|
+
if body.is_a?(::Protocol::HTTP::Body::Readable)
|
40
|
+
return body
|
41
|
+
elsif status == 200 and body.respond_to?(:to_path)
|
42
|
+
begin
|
43
|
+
# Don't mangle partial responses (206)
|
44
|
+
return ::Protocol::HTTP::Body::File.open(body.to_path).tap do
|
45
|
+
body.close if body.respond_to?(:close) # Close the original body.
|
46
|
+
end
|
47
|
+
rescue Errno::ENOENT
|
48
|
+
# If the file is not available, ignore.
|
49
|
+
end
|
50
|
+
elsif body.respond_to?(:each)
|
51
|
+
body = Body::Enumerable.wrap(body, length)
|
52
|
+
else
|
53
|
+
body = Body::Streaming.new(body, input)
|
54
|
+
end
|
55
|
+
end
|
56
|
+
end
|
57
|
+
end
|
58
|
+
end
|
@@ -0,0 +1,62 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
# Copyright, 2017, by Samuel G. D. Williams. <http://www.codeotaku.com>
|
4
|
+
#
|
5
|
+
# Permission is hereby granted, free of charge, to any person obtaining a copy
|
6
|
+
# of this software and associated documentation files (the "Software"), to deal
|
7
|
+
# in the Software without restriction, including without limitation the rights
|
8
|
+
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
9
|
+
# copies of the Software, and to permit persons to whom the Software is
|
10
|
+
# furnished to do so, subject to the following conditions:
|
11
|
+
#
|
12
|
+
# The above copyright notice and this permission notice shall be included in
|
13
|
+
# all copies or substantial portions of the Software.
|
14
|
+
#
|
15
|
+
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
16
|
+
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
17
|
+
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
18
|
+
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
19
|
+
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
20
|
+
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
21
|
+
# THE SOFTWARE.
|
22
|
+
|
23
|
+
require 'rack'
|
24
|
+
|
25
|
+
require_relative 'input'
|
26
|
+
require_relative 'response'
|
27
|
+
|
28
|
+
require 'console'
|
29
|
+
|
30
|
+
module Protocol
|
31
|
+
module Rack
|
32
|
+
# Used for injecting the raw request in the the rack environment.
|
33
|
+
PROTOCOL_HTTP_REQUEST = "protocol.http.request"
|
34
|
+
|
35
|
+
# CGI keys <https://tools.ietf.org/html/rfc3875#section-4.1>:
|
36
|
+
module CGI
|
37
|
+
HTTP_HOST = 'HTTP_HOST'
|
38
|
+
PATH_INFO = 'PATH_INFO'
|
39
|
+
REQUEST_METHOD = 'REQUEST_METHOD'
|
40
|
+
REQUEST_PATH = 'REQUEST_PATH'
|
41
|
+
REQUEST_URI = 'REQUEST_URI'
|
42
|
+
SCRIPT_NAME = 'SCRIPT_NAME'
|
43
|
+
QUERY_STRING = 'QUERY_STRING'
|
44
|
+
SERVER_PROTOCOL = 'SERVER_PROTOCOL'
|
45
|
+
SERVER_NAME = 'SERVER_NAME'
|
46
|
+
SERVER_PORT = 'SERVER_PORT'
|
47
|
+
REMOTE_ADDR = 'REMOTE_ADDR'
|
48
|
+
CONTENT_TYPE = 'CONTENT_TYPE'
|
49
|
+
CONTENT_LENGTH = 'CONTENT_LENGTH'
|
50
|
+
|
51
|
+
# Header constants:
|
52
|
+
HTTP_X_FORWARDED_PROTO = 'HTTP_X_FORWARDED_PROTO'
|
53
|
+
end
|
54
|
+
|
55
|
+
# Rack environment variables:
|
56
|
+
RACK_ERRORS = 'rack.errors'
|
57
|
+
RACK_LOGGER = 'rack.logger'
|
58
|
+
RACK_INPUT = 'rack.input'
|
59
|
+
RACK_URL_SCHEME = 'rack.url_scheme'
|
60
|
+
RACK_PROTOCOL = 'rack.protocol'
|
61
|
+
end
|
62
|
+
end
|
@@ -0,0 +1,125 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
# Copyright, 2018, by Samuel G. D. Williams. <http://www.codeotaku.com>
|
4
|
+
#
|
5
|
+
# Permission is hereby granted, free of charge, to any person obtaining a copy
|
6
|
+
# of this software and associated documentation files (the "Software"), to deal
|
7
|
+
# in the Software without restriction, including without limitation the rights
|
8
|
+
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
9
|
+
# copies of the Software, and to permit persons to whom the Software is
|
10
|
+
# furnished to do so, subject to the following conditions:
|
11
|
+
#
|
12
|
+
# The above copyright notice and this permission notice shall be included in
|
13
|
+
# all copies or substantial portions of the Software.
|
14
|
+
#
|
15
|
+
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
16
|
+
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
17
|
+
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
18
|
+
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
19
|
+
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
20
|
+
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
21
|
+
# THE SOFTWARE.
|
22
|
+
|
23
|
+
require 'async/io/buffer'
|
24
|
+
require 'protocol/http/body/stream'
|
25
|
+
|
26
|
+
module Protocol
|
27
|
+
module Rack
|
28
|
+
# Wraps a streaming input body into the interface required by `rack.input`.
|
29
|
+
#
|
30
|
+
# The input stream is an `IO`-like object which contains the raw HTTP POST data. When applicable, its external encoding must be `ASCII-8BIT` and it must be opened in binary mode, for Ruby 1.9 compatibility. The input stream must respond to `gets`, `each`, `read` and `rewind`.
|
31
|
+
#
|
32
|
+
# This implementation is not always rewindable, to avoid buffering the input when handling large uploads. See {Rewindable} for more details.
|
33
|
+
class Input
|
34
|
+
# Initialize the input wrapper.
|
35
|
+
# @parameter body [Protocol::HTTP::Body::Readable]
|
36
|
+
def initialize(body)
|
37
|
+
@body = body
|
38
|
+
|
39
|
+
# Will hold remaining data in `#read`.
|
40
|
+
@buffer = nil
|
41
|
+
end
|
42
|
+
|
43
|
+
# The input body.
|
44
|
+
# @attribute [Protocol::HTTP::Body::Readable]
|
45
|
+
attr :body
|
46
|
+
|
47
|
+
# Enumerate chunks of the request body.
|
48
|
+
# @yields {|chunk| ...}
|
49
|
+
# @parameter chunk [String]
|
50
|
+
def each(&block)
|
51
|
+
return to_enum unless block_given?
|
52
|
+
|
53
|
+
while chunk = gets
|
54
|
+
yield chunk
|
55
|
+
end
|
56
|
+
end
|
57
|
+
|
58
|
+
include Protocol::HTTP::Body::Stream::Reader
|
59
|
+
|
60
|
+
# Read the next chunk of data from the input stream.
|
61
|
+
#
|
62
|
+
# `gets` must be called without arguments and return a `String`, or `nil` when the input stream has no more data.
|
63
|
+
#
|
64
|
+
# @returns [String | Nil] The next chunk from the body.
|
65
|
+
def gets
|
66
|
+
if @buffer.nil?
|
67
|
+
return read_next
|
68
|
+
else
|
69
|
+
buffer = @buffer
|
70
|
+
@buffer = nil
|
71
|
+
return buffer
|
72
|
+
end
|
73
|
+
end
|
74
|
+
|
75
|
+
# Close the input and output bodies.
|
76
|
+
def close(error = nil)
|
77
|
+
if @body
|
78
|
+
@body.close(error)
|
79
|
+
@body = nil
|
80
|
+
end
|
81
|
+
|
82
|
+
return nil
|
83
|
+
end
|
84
|
+
|
85
|
+
# Rewind the input stream back to the start.
|
86
|
+
#
|
87
|
+
# `rewind` must be called without arguments. It rewinds the input stream back to the beginning. It must not raise Errno::ESPIPE: that is, it may not be a pipe or a socket. Therefore, handler developers must buffer the input data into some rewindable object if the underlying input stream is not rewindable.
|
88
|
+
#
|
89
|
+
# @returns [Boolean] Whether the body could be rewound.
|
90
|
+
def rewind
|
91
|
+
if @body and @body.respond_to?(:rewind)
|
92
|
+
# If the body is not rewindable, this will fail.
|
93
|
+
@body.rewind
|
94
|
+
@buffer = nil
|
95
|
+
@finished = false
|
96
|
+
|
97
|
+
return true
|
98
|
+
end
|
99
|
+
|
100
|
+
return false
|
101
|
+
end
|
102
|
+
|
103
|
+
# Whether the stream has been closed.
|
104
|
+
def closed?
|
105
|
+
@body.nil?
|
106
|
+
end
|
107
|
+
|
108
|
+
# Whether there are any output chunks remaining?
|
109
|
+
def empty?
|
110
|
+
@output.empty?
|
111
|
+
end
|
112
|
+
|
113
|
+
private
|
114
|
+
|
115
|
+
def read_next
|
116
|
+
if @body
|
117
|
+
@body.read
|
118
|
+
else
|
119
|
+
@body = nil
|
120
|
+
raise IOError, "Stream is not readable, input has been closed!"
|
121
|
+
end
|
122
|
+
end
|
123
|
+
end
|
124
|
+
end
|
125
|
+
end
|
@@ -0,0 +1,72 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
# Copyright, 2018, by Samuel G. D. Williams. <http://www.codeotaku.com>
|
4
|
+
#
|
5
|
+
# Permission is hereby granted, free of charge, to any person obtaining a copy
|
6
|
+
# of this software and associated documentation files (the "Software"), to deal
|
7
|
+
# in the Software without restriction, including without limitation the rights
|
8
|
+
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
9
|
+
# copies of the Software, and to permit persons to whom the Software is
|
10
|
+
# furnished to do so, subject to the following conditions:
|
11
|
+
#
|
12
|
+
# The above copyright notice and this permission notice shall be included in
|
13
|
+
# all copies or substantial portions of the Software.
|
14
|
+
#
|
15
|
+
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
16
|
+
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
17
|
+
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
18
|
+
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
19
|
+
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
20
|
+
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
21
|
+
# THE SOFTWARE.
|
22
|
+
|
23
|
+
require 'protocol/http/request'
|
24
|
+
|
25
|
+
require_relative 'body/input_wrapper'
|
26
|
+
|
27
|
+
module Protocol
|
28
|
+
module Rack
|
29
|
+
class Request < ::Protocol::HTTP::Request
|
30
|
+
def self.[](env)
|
31
|
+
env['protocol.http.request'] ||= new(env)
|
32
|
+
end
|
33
|
+
|
34
|
+
def initialize(env)
|
35
|
+
@env = env
|
36
|
+
|
37
|
+
super(
|
38
|
+
@env['rack.url_scheme'],
|
39
|
+
@env['HTTP_HOST'],
|
40
|
+
@env['REQUEST_METHOD'],
|
41
|
+
@env['PATH_INFO'],
|
42
|
+
@env['SERVER_PROTOCOL'],
|
43
|
+
self.class.headers(@env),
|
44
|
+
Body::InputWrapper.new(@env['rack.input']),
|
45
|
+
self.class.protocol(@env)
|
46
|
+
)
|
47
|
+
end
|
48
|
+
|
49
|
+
HTTP_UPGRADE = 'HTTP_UPGRADE'
|
50
|
+
|
51
|
+
def self.protocol(env)
|
52
|
+
if protocols = env['rack.protocol']
|
53
|
+
return Array(protocols)
|
54
|
+
elsif protocols = env[HTTP_UPGRADE]
|
55
|
+
return protocols.split(/\s*,\s*/)
|
56
|
+
end
|
57
|
+
end
|
58
|
+
|
59
|
+
def self.headers(env)
|
60
|
+
headers = ::Protocol::HTTP::Headers.new
|
61
|
+
env.each do |key, value|
|
62
|
+
if key.start_with?('HTTP_')
|
63
|
+
next if key == 'HTTP_HOST'
|
64
|
+
headers[key[5..-1].gsub('_', '-').downcase] = value
|
65
|
+
end
|
66
|
+
end
|
67
|
+
|
68
|
+
return headers
|
69
|
+
end
|
70
|
+
end
|
71
|
+
end
|
72
|
+
end
|
@@ -0,0 +1,118 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
# Copyright, 2018, by Samuel G. D. Williams. <http://www.codeotaku.com>
|
4
|
+
#
|
5
|
+
# Permission is hereby granted, free of charge, to any person obtaining a copy
|
6
|
+
# of this software and associated documentation files (the "Software"), to deal
|
7
|
+
# in the Software without restriction, including without limitation the rights
|
8
|
+
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
9
|
+
# copies of the Software, and to permit persons to whom the Software is
|
10
|
+
# furnished to do so, subject to the following conditions:
|
11
|
+
#
|
12
|
+
# The above copyright notice and this permission notice shall be included in
|
13
|
+
# all copies or substantial portions of the Software.
|
14
|
+
#
|
15
|
+
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
16
|
+
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
17
|
+
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
18
|
+
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
19
|
+
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
20
|
+
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
21
|
+
# THE SOFTWARE.
|
22
|
+
|
23
|
+
require_relative 'body'
|
24
|
+
require_relative 'constants'
|
25
|
+
# require 'time'
|
26
|
+
|
27
|
+
require 'protocol/http/response'
|
28
|
+
require 'protocol/http/headers'
|
29
|
+
|
30
|
+
module Protocol
|
31
|
+
module Rack
|
32
|
+
# A wrapper for a `Rack` response.
|
33
|
+
#
|
34
|
+
# A Rack response consisting of `[status, headers, body]` includes various rack-specific elements, including:
|
35
|
+
#
|
36
|
+
# - A `headers['rack.hijack']` callback which bypasses normal response handling.
|
37
|
+
# - Potentially invalid content length.
|
38
|
+
# - Potentially invalid body when processing a `HEAD` request.
|
39
|
+
# - Newline-separated header values.
|
40
|
+
# - Other `rack.` specific header key/value pairs.
|
41
|
+
#
|
42
|
+
# This wrapper takes those issues into account and adapts the rack response tuple into a {Protocol::HTTP::Response}.
|
43
|
+
class Response < ::Protocol::HTTP::Response
|
44
|
+
# HTTP hop headers which *should* not be passed through the proxy.
|
45
|
+
HOP_HEADERS = [
|
46
|
+
'connection',
|
47
|
+
'keep-alive',
|
48
|
+
'public',
|
49
|
+
'proxy-authenticate',
|
50
|
+
'transfer-encoding',
|
51
|
+
'upgrade',
|
52
|
+
]
|
53
|
+
|
54
|
+
# Process the rack response headers into into a {Protocol::HTTP::Headers} instance, along with any extra `rack.` metadata.
|
55
|
+
# @returns [Tuple(Protocol::HTTP::Headers, Hash)]
|
56
|
+
def self.wrap_headers(fields)
|
57
|
+
headers = ::Protocol::HTTP::Headers.new
|
58
|
+
meta = {}
|
59
|
+
|
60
|
+
fields.each do |key, value|
|
61
|
+
key = key.downcase
|
62
|
+
|
63
|
+
if key.start_with?('rack.')
|
64
|
+
meta[key] = value
|
65
|
+
elsif value.is_a?(Array)
|
66
|
+
value.each do |value|
|
67
|
+
headers[key] = value
|
68
|
+
end
|
69
|
+
else
|
70
|
+
headers[key] = value
|
71
|
+
end
|
72
|
+
end
|
73
|
+
|
74
|
+
return headers, meta
|
75
|
+
end
|
76
|
+
|
77
|
+
# Wrap a rack response.
|
78
|
+
# @parameter status [Integer] The rack response status.
|
79
|
+
# @parameter headers [Duck(:each)] The rack response headers.
|
80
|
+
# @parameter body [Duck(:each, :close) | Nil] The rack response body.
|
81
|
+
# @parameter request [Protocol::HTTP::Request] The original request.
|
82
|
+
def self.wrap(status, headers, body, request = nil)
|
83
|
+
headers, meta = wrap_headers(headers)
|
84
|
+
|
85
|
+
ignored = headers.extract(HOP_HEADERS)
|
86
|
+
unless ignored.empty?
|
87
|
+
Console.logger.warn(self, "Ignoring protocol-level headers: #{ignored.inspect}")
|
88
|
+
end
|
89
|
+
|
90
|
+
body = Body.wrap(status, headers, body, request&.body)
|
91
|
+
|
92
|
+
if request&.head?
|
93
|
+
# I thought about doing this in Output.wrap, but decided the semantics are too tricky. Specifically, the various ways a rack response body can be wrapped, and the need to invoke #close at the right point.
|
94
|
+
body = ::Protocol::HTTP::Body::Head.for(body)
|
95
|
+
end
|
96
|
+
|
97
|
+
protocol = meta[RACK_PROTOCOL]
|
98
|
+
|
99
|
+
# https://tools.ietf.org/html/rfc7231#section-7.4.2
|
100
|
+
# headers.add('server', "falcon/#{Falcon::VERSION}")
|
101
|
+
|
102
|
+
# https://tools.ietf.org/html/rfc7231#section-7.1.1.2
|
103
|
+
# headers.add('date', Time.now.httpdate)
|
104
|
+
|
105
|
+
return self.new(status, headers, body, protocol)
|
106
|
+
end
|
107
|
+
|
108
|
+
# Initialize the response wrapper.
|
109
|
+
# @parameter status [Integer] The response status.
|
110
|
+
# @parameter headers [Protocol::HTTP::Headers] The response headers.
|
111
|
+
# @parameter body [Protocol::HTTP::Body] The response body.
|
112
|
+
# @parameter protocol [String] The response protocol for upgraded requests.
|
113
|
+
def initialize(status, headers, body, protocol = nil)
|
114
|
+
super(nil, status, headers, body, protocol)
|
115
|
+
end
|
116
|
+
end
|
117
|
+
end
|
118
|
+
end
|
@@ -0,0 +1,79 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
# Copyright, 2018, by Samuel G. D. Williams. <http://www.codeotaku.com>
|
4
|
+
#
|
5
|
+
# Permission is hereby granted, free of charge, to any person obtaining a copy
|
6
|
+
# of this software and associated documentation files (the "Software"), to deal
|
7
|
+
# in the Software without restriction, including without limitation the rights
|
8
|
+
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
9
|
+
# copies of the Software, and to permit persons to whom the Software is
|
10
|
+
# furnished to do so, subject to the following conditions:
|
11
|
+
#
|
12
|
+
# The above copyright notice and this permission notice shall be included in
|
13
|
+
# all copies or substantial portions of the Software.
|
14
|
+
#
|
15
|
+
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
16
|
+
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
17
|
+
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
18
|
+
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
19
|
+
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
20
|
+
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
21
|
+
# THE SOFTWARE.
|
22
|
+
|
23
|
+
require 'protocol/http/body/rewindable'
|
24
|
+
require 'protocol/http/middleware'
|
25
|
+
|
26
|
+
module Protocol
|
27
|
+
module Rack
|
28
|
+
# Content-type driven input buffering, specific to the needs of `rack`.
|
29
|
+
class Rewindable < ::Protocol::HTTP::Middleware
|
30
|
+
# Media types that require buffering.
|
31
|
+
BUFFERED_MEDIA_TYPES = %r{
|
32
|
+
application/x-www-form-urlencoded|
|
33
|
+
multipart/form-data|
|
34
|
+
multipart/related|
|
35
|
+
multipart/mixed
|
36
|
+
}x
|
37
|
+
|
38
|
+
POST = 'POST'
|
39
|
+
|
40
|
+
# Initialize the rewindable middleware.
|
41
|
+
# @parameter app [Protocol::HTTP::Middleware] The middleware to wrap.
|
42
|
+
def initialize(app)
|
43
|
+
super(app)
|
44
|
+
end
|
45
|
+
|
46
|
+
# Determine whether the request needs a rewindable body.
|
47
|
+
# @parameter request [Protocol::HTTP::Request]
|
48
|
+
# @returns [Boolean]
|
49
|
+
def needs_rewind?(request)
|
50
|
+
content_type = request.headers['content-type']
|
51
|
+
|
52
|
+
if request.method == POST and content_type.nil?
|
53
|
+
return true
|
54
|
+
end
|
55
|
+
|
56
|
+
if BUFFERED_MEDIA_TYPES =~ content_type
|
57
|
+
return true
|
58
|
+
end
|
59
|
+
|
60
|
+
return false
|
61
|
+
end
|
62
|
+
|
63
|
+
def make_environment(request)
|
64
|
+
@delegate.make_environment(request)
|
65
|
+
end
|
66
|
+
|
67
|
+
# Wrap the request body in a rewindable buffer if required.
|
68
|
+
# @parameter request [Protocol::HTTP::Request]
|
69
|
+
# @returns [Protocol::HTTP::Response] the response.
|
70
|
+
def call(request)
|
71
|
+
if body = request.body and needs_rewind?(request)
|
72
|
+
request.body = Protocol::HTTP::Body::Rewindable.new(body)
|
73
|
+
end
|
74
|
+
|
75
|
+
return super
|
76
|
+
end
|
77
|
+
end
|
78
|
+
end
|
79
|
+
end
|
@@ -0,0 +1,27 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
# Copyright, 2022, by Samuel G. D. Williams. <http://www.codeotaku.com>
|
4
|
+
#
|
5
|
+
# Permission is hereby granted, free of charge, to any person obtaining a copy
|
6
|
+
# of this software and associated documentation files (the "Software"), to deal
|
7
|
+
# in the Software without restriction, including without limitation the rights
|
8
|
+
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
9
|
+
# copies of the Software, and to permit persons to whom the Software is
|
10
|
+
# furnished to do so, subject to the following conditions:
|
11
|
+
#
|
12
|
+
# The above copyright notice and this permission notice shall be included in
|
13
|
+
# all copies or substantial portions of the Software.
|
14
|
+
#
|
15
|
+
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
16
|
+
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
17
|
+
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
18
|
+
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
19
|
+
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
20
|
+
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
21
|
+
# THE SOFTWARE.
|
22
|
+
|
23
|
+
module Protocol
|
24
|
+
module Rack
|
25
|
+
VERSION = "0.1.0"
|
26
|
+
end
|
27
|
+
end
|
@@ -0,0 +1,26 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
# Copyright, 2018, by Samuel G. D. Williams. <http://www.codeotaku.com>
|
4
|
+
#
|
5
|
+
# Permission is hereby granted, free of charge, to any person obtaining a copy
|
6
|
+
# of this software and associated documentation files (the "Software"), to deal
|
7
|
+
# in the Software without restriction, including without limitation the rights
|
8
|
+
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
9
|
+
# copies of the Software, and to permit persons to whom the Software is
|
10
|
+
# furnished to do so, subject to the following conditions:
|
11
|
+
#
|
12
|
+
# The above copyright notice and this permission notice shall be included in
|
13
|
+
# all copies or substantial portions of the Software.
|
14
|
+
#
|
15
|
+
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
16
|
+
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
17
|
+
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
18
|
+
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
19
|
+
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
20
|
+
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
21
|
+
# THE SOFTWARE.
|
22
|
+
|
23
|
+
require_relative 'rack/version'
|
24
|
+
require_relative 'rack/adapter'
|
25
|
+
require_relative 'rack/request'
|
26
|
+
require_relative 'rack/response'
|
data.tar.gz.sig
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
-#Ϡ�|���n���8������a���nOKצ�7�/z�9�ı5�f�f��Q�����=Ø�H�e6&R==����T*EMJ��\:���xm�+P$Tx0����X�F�'�B����l�,����+�h���9��cm��2����#��uYX}?�����q����z����������P�7vyX�5��B���ɰCȢ�E�7��9!ZZv�S3T�q��7�+�҂,���=R��"�F��zAlh�e���S���k���-�.&�};H��R�Y-(�F�֝:�������B��M�I����/��QA��Ŵ��گ"��Q`�r�h���ȸ�N��q(�S'y�Y�P!��^!���'d�
|
metadata
ADDED
@@ -0,0 +1,114 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: protocol-rack
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.1.0
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Samuel Williams
|
8
|
+
autorequire:
|
9
|
+
bindir: bin
|
10
|
+
cert_chain:
|
11
|
+
- |
|
12
|
+
-----BEGIN CERTIFICATE-----
|
13
|
+
MIIE2DCCA0CgAwIBAgIBATANBgkqhkiG9w0BAQsFADBhMRgwFgYDVQQDDA9zYW11
|
14
|
+
ZWwud2lsbGlhbXMxHTAbBgoJkiaJk/IsZAEZFg1vcmlvbnRyYW5zZmVyMRIwEAYK
|
15
|
+
CZImiZPyLGQBGRYCY28xEjAQBgoJkiaJk/IsZAEZFgJuejAeFw0yMjA4MDYwNDUz
|
16
|
+
MjRaFw0zMjA4MDMwNDUzMjRaMGExGDAWBgNVBAMMD3NhbXVlbC53aWxsaWFtczEd
|
17
|
+
MBsGCgmSJomT8ixkARkWDW9yaW9udHJhbnNmZXIxEjAQBgoJkiaJk/IsZAEZFgJj
|
18
|
+
bzESMBAGCgmSJomT8ixkARkWAm56MIIBojANBgkqhkiG9w0BAQEFAAOCAY8AMIIB
|
19
|
+
igKCAYEAomvSopQXQ24+9DBB6I6jxRI2auu3VVb4nOjmmHq7XWM4u3HL+pni63X2
|
20
|
+
9qZdoq9xt7H+RPbwL28LDpDNflYQXoOhoVhQ37Pjn9YDjl8/4/9xa9+NUpl9XDIW
|
21
|
+
sGkaOY0eqsQm1pEWkHJr3zn/fxoKPZPfaJOglovdxf7dgsHz67Xgd/ka+Wo1YqoE
|
22
|
+
e5AUKRwUuvaUaumAKgPH+4E4oiLXI4T1Ff5Q7xxv6yXvHuYtlMHhYfgNn8iiW8WN
|
23
|
+
XibYXPNP7NtieSQqwR/xM6IRSoyXKuS+ZNGDPUUGk8RoiV/xvVN4LrVm9upSc0ss
|
24
|
+
RZ6qwOQmXCo/lLcDUxJAgG95cPw//sI00tZan75VgsGzSWAOdjQpFM0l4dxvKwHn
|
25
|
+
tUeT3ZsAgt0JnGqNm2Bkz81kG4A2hSyFZTFA8vZGhp+hz+8Q573tAR89y9YJBdYM
|
26
|
+
zp0FM4zwMNEUwgfRzv1tEVVUEXmoFCyhzonUUw4nE4CFu/sE3ffhjKcXcY//qiSW
|
27
|
+
xm4erY3XAgMBAAGjgZowgZcwCQYDVR0TBAIwADALBgNVHQ8EBAMCBLAwHQYDVR0O
|
28
|
+
BBYEFO9t7XWuFf2SKLmuijgqR4sGDlRsMC4GA1UdEQQnMCWBI3NhbXVlbC53aWxs
|
29
|
+
aWFtc0BvcmlvbnRyYW5zZmVyLmNvLm56MC4GA1UdEgQnMCWBI3NhbXVlbC53aWxs
|
30
|
+
aWFtc0BvcmlvbnRyYW5zZmVyLmNvLm56MA0GCSqGSIb3DQEBCwUAA4IBgQB5sxkE
|
31
|
+
cBsSYwK6fYpM+hA5B5yZY2+L0Z+27jF1pWGgbhPH8/FjjBLVn+VFok3CDpRqwXCl
|
32
|
+
xCO40JEkKdznNy2avOMra6PFiQyOE74kCtv7P+Fdc+FhgqI5lMon6tt9rNeXmnW/
|
33
|
+
c1NaMRdxy999hmRGzUSFjozcCwxpy/LwabxtdXwXgSay4mQ32EDjqR1TixS1+smp
|
34
|
+
8C/NCWgpIfzpHGJsjvmH2wAfKtTTqB9CVKLCWEnCHyCaRVuKkrKjqhYCdmMBqCws
|
35
|
+
JkxfQWC+jBVeG9ZtPhQgZpfhvh+6hMhraUYRQ6XGyvBqEUe+yo6DKIT3MtGE2+CP
|
36
|
+
eX9i9ZWBydWb8/rvmwmX2kkcBbX0hZS1rcR593hGc61JR6lvkGYQ2MYskBveyaxt
|
37
|
+
Q2K9NVun/S785AP05vKkXZEFYxqG6EW012U4oLcFl5MySFajYXRYbuUpH6AY+HP8
|
38
|
+
voD0MPg1DssDLKwXyt1eKD/+Fq0bFWhwVM/1XiAXL7lyYUyOq24KHgQ2Csg=
|
39
|
+
-----END CERTIFICATE-----
|
40
|
+
date: 2022-08-16 00:00:00.000000000 Z
|
41
|
+
dependencies:
|
42
|
+
- !ruby/object:Gem::Dependency
|
43
|
+
name: protocol-http
|
44
|
+
requirement: !ruby/object:Gem::Requirement
|
45
|
+
requirements:
|
46
|
+
- - "~>"
|
47
|
+
- !ruby/object:Gem::Version
|
48
|
+
version: 0.23.4
|
49
|
+
type: :runtime
|
50
|
+
prerelease: false
|
51
|
+
version_requirements: !ruby/object:Gem::Requirement
|
52
|
+
requirements:
|
53
|
+
- - "~>"
|
54
|
+
- !ruby/object:Gem::Version
|
55
|
+
version: 0.23.4
|
56
|
+
- !ruby/object:Gem::Dependency
|
57
|
+
name: rack
|
58
|
+
requirement: !ruby/object:Gem::Requirement
|
59
|
+
requirements:
|
60
|
+
- - ">="
|
61
|
+
- !ruby/object:Gem::Version
|
62
|
+
version: '1.0'
|
63
|
+
type: :runtime
|
64
|
+
prerelease: false
|
65
|
+
version_requirements: !ruby/object:Gem::Requirement
|
66
|
+
requirements:
|
67
|
+
- - ">="
|
68
|
+
- !ruby/object:Gem::Version
|
69
|
+
version: '1.0'
|
70
|
+
description:
|
71
|
+
email:
|
72
|
+
executables: []
|
73
|
+
extensions: []
|
74
|
+
extra_rdoc_files: []
|
75
|
+
files:
|
76
|
+
- lib/protocol/rack.rb
|
77
|
+
- lib/protocol/rack/adapter.rb
|
78
|
+
- lib/protocol/rack/adapter/generic.rb
|
79
|
+
- lib/protocol/rack/adapter/rack2.rb
|
80
|
+
- lib/protocol/rack/adapter/rack3.rb
|
81
|
+
- lib/protocol/rack/body.rb
|
82
|
+
- lib/protocol/rack/body/enumerable.rb
|
83
|
+
- lib/protocol/rack/body/input_wrapper.rb
|
84
|
+
- lib/protocol/rack/body/streaming.rb
|
85
|
+
- lib/protocol/rack/constants.rb
|
86
|
+
- lib/protocol/rack/input.rb
|
87
|
+
- lib/protocol/rack/request.rb
|
88
|
+
- lib/protocol/rack/response.rb
|
89
|
+
- lib/protocol/rack/rewindable.rb
|
90
|
+
- lib/protocol/rack/version.rb
|
91
|
+
homepage: https://github.com/socketry/protocol-rack
|
92
|
+
licenses:
|
93
|
+
- MIT
|
94
|
+
metadata: {}
|
95
|
+
post_install_message:
|
96
|
+
rdoc_options: []
|
97
|
+
require_paths:
|
98
|
+
- lib
|
99
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
100
|
+
requirements:
|
101
|
+
- - ">="
|
102
|
+
- !ruby/object:Gem::Version
|
103
|
+
version: '2.5'
|
104
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
105
|
+
requirements:
|
106
|
+
- - ">="
|
107
|
+
- !ruby/object:Gem::Version
|
108
|
+
version: '0'
|
109
|
+
requirements: []
|
110
|
+
rubygems_version: 3.3.7
|
111
|
+
signing_key:
|
112
|
+
specification_version: 4
|
113
|
+
summary: An implementation of the Rack protocol/specification.
|
114
|
+
test_files: []
|
metadata.gz.sig
ADDED
Binary file
|