falcon 0.41.0 → 0.42.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 9ea41a48b0da7b6ba65df7bd8f3c63c06a8deb498119079f8ca03367bc78cfa5
4
- data.tar.gz: 79e5693fc78e56b829d88b6addffe4d255fa09629cf201356192eb70563e1036
3
+ metadata.gz: 41752345deec82e8434e7b59548a97dab31902b6038893db4a6fa65eda46479d
4
+ data.tar.gz: 7aaaca95cfdd733b9ae68ad49db16f9007266f9e42211c156376001f88cc3b1d
5
5
  SHA512:
6
- metadata.gz: d97ae8f6aebe79fa10c410ca4ba038a731b6ace354b2bcc2bd8ec0d5a958fefed7c89d5bd47141fbb0e2f8fbbac869476e6bc1c552a8034ddc9b5835e9278d4c
7
- data.tar.gz: '080b0ac4a57575b3177024248fa538d7019f3812eed267a3fd5ca9b7de7cc9388759d55c51a6b0de6b2aa71788b69f2bbc08e08488415f3cfc7e54d3707f8a14'
6
+ metadata.gz: 0f57c05d31769f54de00add9cdfaacbcd2471b49c64c846d552a4257e108cdd58480c3b90369d5412d5bc618ac3fb731df90d691c8f254521097f3402999d684
7
+ data.tar.gz: 7bd53d3c8a4a1619fbee1cd436e64f78065fd2de09c9f8fc2f00819a5c1fbf395151d1f893c4be730140d5105266aad963c89be3a543f10f0e66781e413f285f
checksums.yaml.gz.sig CHANGED
Binary file
data/lib/.DS_Store ADDED
Binary file
data/lib/falcon/server.rb CHANGED
@@ -28,9 +28,7 @@ require 'protocol/http/content_encoding'
28
28
  require 'async/http/cache'
29
29
 
30
30
  require_relative 'middleware/verbose'
31
-
32
- require_relative 'adapters/rewindable'
33
- require_relative 'adapters/rack'
31
+ require 'protocol/rack'
34
32
 
35
33
  module Falcon
36
34
  # A server listening on a specific endpoint, hosting a specific middleware.
@@ -50,8 +48,7 @@ module Falcon
50
48
  end
51
49
 
52
50
  use ::Protocol::HTTP::ContentEncoding
53
- use Adapters::Rewindable
54
- use Adapters::Rack
51
+ use ::Protocol::Rack::Adapter
55
52
 
56
53
  run rack_app
57
54
  end
@@ -21,5 +21,5 @@
21
21
  # THE SOFTWARE.
22
22
 
23
23
  module Falcon
24
- VERSION = "0.41.0"
24
+ VERSION = "0.42.0"
25
25
  end
@@ -27,8 +27,7 @@ module Rack
27
27
  # Run the specified app using the given options:
28
28
  # @parameter app [Object] The rack middleware.
29
29
  def self.run(app, **options)
30
- app = ::Falcon::Adapters::Rack.new(app)
31
- app = ::Falcon::Adapters::Rewindable.new(app)
30
+ app = ::Protocol::Rack::Adapter.new(app)
32
31
 
33
32
  Sync do |task|
34
33
  endpoint = endpoint_for(**options)
data.tar.gz.sig CHANGED
Binary file
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: falcon
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.41.0
4
+ version: 0.42.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Samuel Williams
@@ -54,7 +54,7 @@ cert_chain:
54
54
  Q2K9NVun/S785AP05vKkXZEFYxqG6EW012U4oLcFl5MySFajYXRYbuUpH6AY+HP8
55
55
  voD0MPg1DssDLKwXyt1eKD/+Fq0bFWhwVM/1XiAXL7lyYUyOq24KHgQ2Csg=
56
56
  -----END CERTIFICATE-----
57
- date: 2022-08-07 00:00:00.000000000 Z
57
+ date: 2022-08-18 00:00:00.000000000 Z
58
58
  dependencies:
59
59
  - !ruby/object:Gem::Dependency
60
60
  name: async
@@ -197,19 +197,19 @@ dependencies:
197
197
  - !ruby/object:Gem::Version
198
198
  version: 0.2.0
199
199
  - !ruby/object:Gem::Dependency
200
- name: rack
200
+ name: protocol-rack
201
201
  requirement: !ruby/object:Gem::Requirement
202
202
  requirements:
203
- - - ">="
203
+ - - "~>"
204
204
  - !ruby/object:Gem::Version
205
- version: '1.0'
205
+ version: 0.1.0
206
206
  type: :runtime
207
207
  prerelease: false
208
208
  version_requirements: !ruby/object:Gem::Requirement
209
209
  requirements:
210
- - - ">="
210
+ - - "~>"
211
211
  - !ruby/object:Gem::Version
212
- version: '1.0'
212
+ version: 0.1.0
213
213
  - !ruby/object:Gem::Dependency
214
214
  name: samovar
215
215
  requirement: !ruby/object:Gem::Requirement
@@ -319,12 +319,8 @@ files:
319
319
  - bake/falcon/supervisor.rb
320
320
  - bin/falcon
321
321
  - bin/falcon-host
322
+ - lib/.DS_Store
322
323
  - lib/falcon.rb
323
- - lib/falcon/adapters/input.rb
324
- - lib/falcon/adapters/output.rb
325
- - lib/falcon/adapters/rack.rb
326
- - lib/falcon/adapters/response.rb
327
- - lib/falcon/adapters/rewindable.rb
328
324
  - lib/falcon/command.rb
329
325
  - lib/falcon/command/host.rb
330
326
  - lib/falcon/command/paths.rb
metadata.gz.sig CHANGED
Binary file
@@ -1,152 +0,0 @@
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/rewindable'
25
-
26
- module Falcon
27
- module Adapters
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
- @finished = @body.nil?
42
- end
43
-
44
- # The input body.
45
- # @attribute [Protocol::HTTP::Body::Readable]
46
- attr :body
47
-
48
- # Enumerate chunks of the request body.
49
- # @yields {|chunk| ...}
50
- # @parameter chunk [String]
51
- def each(&block)
52
- return to_enum unless block_given?
53
-
54
- while chunk = gets
55
- yield chunk
56
- end
57
- end
58
-
59
- # Rewind the input stream back to the start.
60
- #
61
- # `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.
62
- #
63
- # @returns [Boolean] Whether the body could be rewound.
64
- def rewind
65
- if @body and @body.respond_to? :rewind
66
- # If the body is not rewindable, this will fail.
67
- @body.rewind
68
- @buffer = nil
69
- @finished = false
70
-
71
- return true
72
- end
73
-
74
- return false
75
- end
76
-
77
- # Read data from the input stream.
78
- #
79
- # `read` behaves like `IO#read`. Its signature is `read(length = nil, buffer = nil)`. If given, length must be a non-negative `Integer` (>= 0) or `nil`, and buffer must be a `String` and may not be nil. If `length` is given and not `nil`, then this method reads at most `length` bytes from the input stream. If `length` is not given or `nil`, then this method reads all data. When the end is reached, this method returns `nil` if `length` is given and not `nil`, or an empty `String` if `length` is not given or is `nil`. If `buffer` is given, then the read data will be placed into the `buffer` instead of a newly created `String` object.
80
- #
81
- # @parameter length [Integer] the amount of data to read
82
- # @parameter buffer [String] the buffer which will receive the data
83
- # @returns a buffer containing the data
84
- def read(length = nil, buffer = nil)
85
- buffer ||= Async::IO::Buffer.new
86
- buffer.clear
87
-
88
- until buffer.bytesize == length
89
- @buffer = read_next if @buffer.nil?
90
- break if @buffer.nil?
91
-
92
- remaining_length = length - buffer.bytesize if length
93
-
94
- if remaining_length && remaining_length < @buffer.bytesize
95
- # We know that we are not going to reuse the original buffer.
96
- # But byteslice will generate a hidden copy. So let's freeze it first:
97
- @buffer.freeze
98
-
99
- buffer << @buffer.byteslice(0, remaining_length)
100
- @buffer = @buffer.byteslice(remaining_length, @buffer.bytesize)
101
- else
102
- buffer << @buffer
103
- @buffer = nil
104
- end
105
- end
106
-
107
- return nil if buffer.empty? && length && length > 0
108
-
109
- return buffer
110
- end
111
-
112
- # Has the input stream been read completely?
113
- # @returns [Boolean]
114
- def eof?
115
- @finished and @buffer.nil?
116
- end
117
-
118
- # Read the next chunk of data from the input stream.
119
- #
120
- # `gets` must be called without arguments and return a `String`, or `nil` when the input stream has no more data.
121
- #
122
- # @returns [String | Nil] The next chunk from the body.
123
- def gets
124
- if @buffer.nil?
125
- return read_next
126
- else
127
- buffer = @buffer
128
- @buffer = nil
129
- return buffer
130
- end
131
- end
132
-
133
- # Close and discard the remainder of the input stream.
134
- def close
135
- @body&.close
136
- end
137
-
138
- private
139
-
140
- def read_next
141
- return nil if @finished
142
-
143
- if chunk = @body.read
144
- return chunk
145
- else
146
- @finished = true
147
- return nil
148
- end
149
- end
150
- end
151
- end
152
- end
@@ -1,138 +0,0 @@
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 Falcon
27
- module Adapters
28
- # Wraps the rack response body.
29
- #
30
- # 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. If the body is replaced by a middleware after action, the original body must be closed first, if it responds to `close`. If the body responds to `to_path`, it must return a String identifying the location of a file whose contents are identical to that produced by calling `each`; this may be used by the server as an alternative, possibly more efficient way to transport the response. The body commonly is an `Array` of strings, the application instance itself, or a `File`-like object.
31
- class Output < ::Protocol::HTTP::Body::Readable
32
- CONTENT_LENGTH = 'content-length'.freeze
33
-
34
- # Wraps an array into a buffered body.
35
- # @parameter status [Integer] The response status.
36
- # @parameter headers [Protocol::HTTP::Headers] The response headers.
37
- # @parameter body [Object] The `rack` response body.
38
- def self.wrap(status, headers, body, request = nil)
39
- # In no circumstance do we want this header propagating out:
40
- if length = headers.delete(CONTENT_LENGTH)
41
- # We don't really trust the user to provide the right length to the transport.
42
- length = Integer(length)
43
- end
44
-
45
- # If we have an Async::HTTP body, we return it directly:
46
- if body.is_a?(::Protocol::HTTP::Body::Readable)
47
- # warn "Returning #{body.class} as body is falcon-specific and may be removed in the future!"
48
- return body
49
- end
50
-
51
- # Otherwise, we have a more typical response body:
52
- if status == 200 and body.respond_to?(:to_path)
53
- begin
54
- # Don't mangle partial responses (206)
55
- return ::Protocol::HTTP::Body::File.open(body.to_path).tap do
56
- body.close if body.respond_to?(:close) # Close the original body.
57
- end
58
- rescue Errno::ENOENT
59
- # If the file is not available, ignore.
60
- end
61
- end
62
-
63
- # If we have a streaming body, we hijack the connection:
64
- unless body.respond_to?(:each)
65
- return Async::HTTP::Body::Hijack.new(body, request&.body)
66
- end
67
-
68
- if body.is_a?(Array)
69
- length ||= body.sum(&:bytesize)
70
- return self.new(body, length)
71
- else
72
- return self.new(body, length)
73
- end
74
- end
75
-
76
- # Initialize the output wrapper.
77
- # @parameter body [Object] The rack response body.
78
- # @parameter length [Integer] The rack response length.
79
- def initialize(body, length)
80
- @length = length
81
- @body = body
82
-
83
- @chunks = nil
84
- end
85
-
86
- # The rack response body.
87
- attr :body
88
-
89
- # The content length of the rack response body.
90
- attr :length
91
-
92
- # Whether the body is empty.
93
- def empty?
94
- @length == 0 or (@body.respond_to?(:empty?) and @body.empty?)
95
- end
96
-
97
- # Whether the body can be read immediately.
98
- def ready?
99
- body.is_a?(Array) or body.respond_to?(:to_ary)
100
- end
101
-
102
- # Close the response body.
103
- def close(error = nil)
104
- if @body and @body.respond_to?(:close)
105
- @body.close
106
- end
107
-
108
- @body = nil
109
- @chunks = nil
110
-
111
- super
112
- end
113
-
114
- # Enumerate the response body.
115
- # @yields {|chunk| ...}
116
- # @parameter chunk [String]
117
- def each(&block)
118
- @body.each(&block)
119
- ensure
120
- self.close($!)
121
- end
122
-
123
- # Read the next chunk from the response body.
124
- # @returns [String | Nil]
125
- def read
126
- @chunks ||= @body.to_enum(:each)
127
-
128
- return @chunks.next
129
- rescue StopIteration
130
- return nil
131
- end
132
-
133
- def inspect
134
- "\#<#{self.class} length=#{@length.inspect} body=#{@body.class}>"
135
- end
136
- end
137
- end
138
- end
@@ -1,224 +0,0 @@
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 Falcon
31
- module Adapters
32
- class Rack
33
- # CGI keys <https://tools.ietf.org/html/rfc3875#section-4.1>:
34
-
35
- HTTP_HOST = 'HTTP_HOST'
36
- PATH_INFO = 'PATH_INFO'
37
- REQUEST_METHOD = 'REQUEST_METHOD'
38
- REQUEST_PATH = 'REQUEST_PATH'
39
- REQUEST_URI = 'REQUEST_URI'
40
- SCRIPT_NAME = 'SCRIPT_NAME'
41
- QUERY_STRING = 'QUERY_STRING'
42
- SERVER_PROTOCOL = 'SERVER_PROTOCOL'
43
- SERVER_NAME = 'SERVER_NAME'
44
- SERVER_PORT = 'SERVER_PORT'
45
- REMOTE_ADDR = 'REMOTE_ADDR'
46
- CONTENT_TYPE = 'CONTENT_TYPE'
47
- CONTENT_LENGTH = 'CONTENT_LENGTH'
48
-
49
- # Rack environment variables:
50
-
51
- RACK_VERSION = 'rack.version'
52
- RACK_ERRORS = 'rack.errors'
53
- RACK_LOGGER = 'rack.logger'
54
- RACK_INPUT = 'rack.input'
55
- RACK_MULTITHREAD = 'rack.multithread'
56
- RACK_MULTIPROCESS = 'rack.multiprocess'
57
- RACK_RUNONCE = 'rack.run_once'
58
- RACK_URL_SCHEME = 'rack.url_scheme'
59
- RACK_HIJACK = 'rack.hijack'
60
- RACK_IS_HIJACK = 'rack.hijack?'
61
- RACK_HIJACK_IO = 'rack.hijack_io'
62
-
63
- # Raised back up through the middleware when the underlying connection is hijacked.
64
- class FullHijack < StandardError
65
- end
66
-
67
- # Async::HTTP specific metadata:
68
-
69
- ASYNC_HTTP_REQUEST = "async.http.request"
70
-
71
- # Header constants:
72
-
73
- HTTP_X_FORWARDED_PROTO = 'HTTP_X_FORWARDED_PROTO'
74
-
75
- # Initialize the rack adaptor middleware.
76
- # @parameter app [Object] The rack middleware.
77
- def initialize(app)
78
- @app = app
79
-
80
- raise ArgumentError, "App must be callable!" unless @app.respond_to?(:call)
81
- end
82
-
83
- # Unwrap raw HTTP headers into the CGI-style expected by Rack middleware.
84
- #
85
- # Rack separates multiple headers with the same key, into a single field with multiple lines.
86
- #
87
- # @parameter headers [Protocol::HTTP::Headers] The raw HTTP request headers.
88
- # @parameter env [Hash] The rack request `env`.
89
- def unwrap_headers(headers, env)
90
- headers.each do |key, value|
91
- http_key = "HTTP_#{key.upcase.tr('-', '_')}"
92
-
93
- if current_value = env[http_key]
94
- env[http_key] = "#{current_value};#{value}"
95
- else
96
- env[http_key] = value
97
- end
98
- end
99
- end
100
-
101
- # Process the incoming request into a valid rack `env`.
102
- #
103
- # - Set the `env['CONTENT_TYPE']` and `env['CONTENT_LENGTH']` based on the incoming request body.
104
- # - Set the `env['HTTP_HOST']` header to the request authority.
105
- # - Set the `env['HTTP_X_FORWARDED_PROTO']` header to the request scheme.
106
- # - Set `env['REMOTE_ADDR']` to the request remote adress.
107
- #
108
- # @parameter request [Protocol::HTTP::Request] The incoming request.
109
- # @parameter env [Hash] The rack `env`.
110
- def unwrap_request(request, env)
111
- if content_type = request.headers.delete('content-type')
112
- env[CONTENT_TYPE] = content_type
113
- end
114
-
115
- # In some situations we don't know the content length, e.g. when using chunked encoding, or when decompressing the body.
116
- if body = request.body and length = body.length
117
- env[CONTENT_LENGTH] = length.to_s
118
- end
119
-
120
- self.unwrap_headers(request.headers, env)
121
-
122
- # HTTP/2 prefers `:authority` over `host`, so we do this for backwards compatibility.
123
- env[HTTP_HOST] ||= request.authority
124
-
125
- # This is the HTTP/1 header for the scheme of the request and is used by Rack.
126
- # Technically it should use the Forwarded header but this is not common yet.
127
- # https://tools.ietf.org/html/rfc7239#section-5.4
128
- # https://github.com/rack/rack/issues/1310
129
- env[HTTP_X_FORWARDED_PROTO] ||= request.scheme
130
-
131
- if remote_address = request.remote_address
132
- env[REMOTE_ADDR] = remote_address.ip_address if remote_address.ip?
133
- end
134
- end
135
-
136
- # Build a rack `env` from the incoming request and apply it to the rack middleware.
137
- #
138
- # @parameter request [Protocol::HTTP::Request] The incoming request.
139
- def call(request)
140
- request_path, query_string = request.path.split('?', 2)
141
- server_name, server_port = (request.authority || '').split(':', 2)
142
-
143
- env = {
144
- RACK_VERSION => [2, 0, 0],
145
-
146
- ASYNC_HTTP_REQUEST => request,
147
-
148
- RACK_INPUT => Input.new(request.body),
149
- RACK_ERRORS => $stderr,
150
- RACK_LOGGER => Console.logger,
151
-
152
- RACK_MULTITHREAD => true,
153
- RACK_MULTIPROCESS => true,
154
- RACK_RUNONCE => false,
155
-
156
- # The HTTP request method, such as “GET” or “POST”. This cannot ever be an empty string, and so is always required.
157
- REQUEST_METHOD => request.method,
158
-
159
- # 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.
160
- SCRIPT_NAME => '',
161
-
162
- # 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.
163
- PATH_INFO => request_path,
164
- REQUEST_PATH => request_path,
165
- REQUEST_URI => request.path,
166
-
167
- # The portion of the request URL that follows the ?, if any. May be empty, but is always required!
168
- QUERY_STRING => query_string || '',
169
-
170
- # The server protocol (e.g. HTTP/1.1):
171
- SERVER_PROTOCOL => request.version,
172
-
173
- # The request scheme:
174
- RACK_URL_SCHEME => request.scheme,
175
-
176
- # I'm not sure what sane defaults should be here:
177
- SERVER_NAME => server_name,
178
- SERVER_PORT => server_port,
179
-
180
- # We support both request and response hijack.
181
- RACK_IS_HIJACK => true,
182
- }
183
-
184
- self.unwrap_request(request, env)
185
-
186
- full_hijack = false
187
-
188
- if request.hijack?
189
- env[RACK_HIJACK] = lambda do
190
- wrapper = request.hijack!
191
- full_hijack = true
192
-
193
- # We dup this as it might be taken out of the normal control flow, and the io will be closed shortly after returning from this method.
194
- io = wrapper.io.dup
195
- wrapper.close
196
-
197
- # This is implicitly returned:
198
- env[RACK_HIJACK_IO] = io
199
- end
200
- end
201
-
202
- status, headers, body = @app.call(env)
203
-
204
- # If there was a full hijack:
205
- if full_hijack
206
- raise FullHijack, "The connection was hijacked."
207
- else
208
- return Response.wrap(status, headers, body, request)
209
- end
210
- rescue => exception
211
- Console.logger.error(self) {exception}
212
-
213
- return failure_response(exception)
214
- end
215
-
216
- # Generate a suitable response for the given exception.
217
- # @parameter exception [Exception]
218
- # @returns [Protocol::HTTP::Response]
219
- def failure_response(exception)
220
- Protocol::HTTP::Response.for_exception(exception)
221
- end
222
- end
223
- end
224
- end
@@ -1,113 +0,0 @@
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 'output'
24
- require_relative '../version'
25
- require_relative '../middleware/proxy'
26
-
27
- require 'async/http/body/hijack'
28
- require 'time'
29
-
30
- module Falcon
31
- module Adapters
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
- IGNORE_HEADERS = Middleware::Proxy::HOP_HEADERS
45
-
46
- # Process the rack response headers into into a {Protocol::HTTP::Headers} instance, along with any extra `rack.` metadata.
47
- # @returns [Tuple(Protocol::HTTP::Headers, Hash)]
48
- def self.wrap_headers(fields)
49
- headers = ::Protocol::HTTP::Headers.new
50
- meta = {}
51
-
52
- fields.each do |key, value|
53
- key = key.downcase
54
-
55
- if key.start_with?('rack.')
56
- meta[key] = value
57
- else
58
- value.to_s.split("\n").each do |part|
59
- headers.add(key, part)
60
- end
61
- end
62
- end
63
-
64
- return headers, meta
65
- end
66
-
67
- # Wrap a rack response.
68
- # @parameter status [Integer] The rack response status.
69
- # @parameter headers [Duck(:each)] The rack response headers.
70
- # @parameter body [Duck(:each, :close) | Nil] The rack response body.
71
- # @parameter request [Protocol::HTTP::Request] The original request.
72
- def self.wrap(status, headers, body, request = nil)
73
- headers, meta = wrap_headers(headers)
74
-
75
- if block = meta['rack.hijack']
76
- body = Async::HTTP::Body::Hijack.wrap(request, &block)
77
- else
78
- ignored = headers.extract(IGNORE_HEADERS)
79
-
80
- unless ignored.empty?
81
- Console.logger.warn("Ignoring protocol-level headers: #{ignored.inspect}")
82
- end
83
-
84
- body = Output.wrap(status, headers, body, request)
85
- end
86
-
87
- if request&.head?
88
- # 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.
89
- body = ::Protocol::HTTP::Body::Head.for(body)
90
- end
91
-
92
- protocol = meta['rack.protocol']
93
-
94
- # https://tools.ietf.org/html/rfc7231#section-7.4.2
95
- # headers.add('server', "falcon/#{Falcon::VERSION}")
96
-
97
- # https://tools.ietf.org/html/rfc7231#section-7.1.1.2
98
- # headers.add('date', Time.now.httpdate)
99
-
100
- return self.new(status, headers, body, protocol)
101
- end
102
-
103
- # Initialize the response wrapper.
104
- # @parameter status [Integer] The response status.
105
- # @parameter headers [Protocol::HTTP::Headers] The response headers.
106
- # @parameter body [Protocol::HTTP::Body] The response body.
107
- # @parameter protocol [String] The response protocol for upgraded requests.
108
- def initialize(status, headers, body, protocol = nil)
109
- super(nil, status, headers, body, protocol)
110
- end
111
- end
112
- end
113
- end
@@ -1,74 +0,0 @@
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
-
25
- module Falcon
26
- module Adapters
27
- # Content-type driven input buffering, specific to the needs of `rack`.
28
- class Rewindable < ::Protocol::HTTP::Middleware
29
- # Media types that require buffering.
30
- BUFFERED_MEDIA_TYPES = %r{
31
- application/x-www-form-urlencoded|
32
- multipart/form-data|
33
- multipart/related|
34
- multipart/mixed
35
- }x
36
-
37
- POST = 'POST'.freeze
38
-
39
- # Initialize the rewindable middleware.
40
- # @parameter app [Protocol::HTTP::Middleware] The middleware to wrap.
41
- def initialize(app)
42
- super(app)
43
- end
44
-
45
- # Determine whether the request needs a rewindable body.
46
- # @parameter request [Protocol::HTTP::Request]
47
- # @returns [Boolean]
48
- def needs_rewind?(request)
49
- content_type = request.headers['content-type']
50
-
51
- if request.method == POST and content_type.nil?
52
- return true
53
- end
54
-
55
- if BUFFERED_MEDIA_TYPES =~ content_type
56
- return true
57
- end
58
-
59
- return false
60
- end
61
-
62
- # Wrap the request body in a rewindable buffer if required.
63
- # @parameter request [Protocol::HTTP::Request]
64
- # @returns [Protocol::HTTP::Response] the response.
65
- def call(request)
66
- if body = request.body and needs_rewind?(request)
67
- request.body = Async::HTTP::Body::Rewindable.new(body)
68
- end
69
-
70
- return super
71
- end
72
- end
73
- end
74
- end