protocol-rack 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
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