falcon 0.36.4
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/.editorconfig +5 -0
- data/.github/FUNDING.yml +3 -0
- data/.github/workflows/development.yml +60 -0
- data/.gitignore +14 -0
- data/.rspec +3 -0
- data/Gemfile +17 -0
- data/README.md +316 -0
- data/bake/falcon/supervisor.rb +8 -0
- data/bin/falcon +32 -0
- data/bin/falcon-host +28 -0
- data/examples/beer/config.ru +57 -0
- data/examples/beer/falcon.rb +8 -0
- data/examples/benchmark/config.ru +39 -0
- data/examples/benchmark/falcon.rb +6 -0
- data/examples/csv/config.ru +31 -0
- data/examples/google/falcon.rb +14 -0
- data/examples/hello/config.ru +22 -0
- data/examples/hello/falcon.rb +24 -0
- data/examples/hello/preload.rb +7 -0
- data/examples/internet/config.ru +54 -0
- data/examples/memory/allocations.rb +39 -0
- data/examples/memory/config.ru +14 -0
- data/examples/push/client.rb +29 -0
- data/examples/push/config.ru +28 -0
- data/examples/push/index.html +14 -0
- data/examples/push/script.js +2 -0
- data/examples/push/style.css +4 -0
- data/examples/redis/Gemfile +9 -0
- data/examples/redis/config.ru +28 -0
- data/examples/sequel/Gemfile +4 -0
- data/examples/sequel/config.ru +8 -0
- data/examples/sequel/data.sqlite3 +0 -0
- data/examples/server/standalone.rb +27 -0
- data/examples/sinatra/Gemfile +7 -0
- data/examples/sinatra/Gemfile.lock +53 -0
- data/examples/sinatra/config.ru +16 -0
- data/examples/trailers/config.ru +34 -0
- data/examples/trailers/falcon.rb +8 -0
- data/falcon.gemspec +45 -0
- data/gems/rack1.gemfile +4 -0
- data/gems/rack3.gemfile +4 -0
- data/lib/falcon.rb +23 -0
- data/lib/falcon/adapters/early_hints.rb +49 -0
- data/lib/falcon/adapters/input.rb +131 -0
- data/lib/falcon/adapters/output.rb +101 -0
- data/lib/falcon/adapters/rack.rb +202 -0
- data/lib/falcon/adapters/response.rb +91 -0
- data/lib/falcon/adapters/rewindable.rb +67 -0
- data/lib/falcon/command.rb +31 -0
- data/lib/falcon/command/host.rb +69 -0
- data/lib/falcon/command/paths.rb +47 -0
- data/lib/falcon/command/proxy.rb +71 -0
- data/lib/falcon/command/redirect.rb +76 -0
- data/lib/falcon/command/serve.rb +151 -0
- data/lib/falcon/command/supervisor.rb +78 -0
- data/lib/falcon/command/top.rb +94 -0
- data/lib/falcon/command/virtual.rb +89 -0
- data/lib/falcon/configuration.rb +147 -0
- data/lib/falcon/configuration/application.rb +46 -0
- data/lib/falcon/configuration/lets_encrypt_tls.rb +30 -0
- data/lib/falcon/configuration/proxy.rb +27 -0
- data/lib/falcon/configuration/rack.rb +38 -0
- data/lib/falcon/configuration/self_signed_tls.rb +47 -0
- data/lib/falcon/configuration/supervisor.rb +37 -0
- data/lib/falcon/configuration/tls.rb +70 -0
- data/lib/falcon/controller/host.rb +60 -0
- data/lib/falcon/controller/proxy.rb +109 -0
- data/lib/falcon/controller/redirect.rb +69 -0
- data/lib/falcon/controller/serve.rb +111 -0
- data/lib/falcon/controller/virtual.rb +100 -0
- data/lib/falcon/endpoint.rb +52 -0
- data/lib/falcon/extensions/openssl.rb +33 -0
- data/lib/falcon/middleware/proxy.rb +145 -0
- data/lib/falcon/middleware/redirect.rb +66 -0
- data/lib/falcon/proxy_endpoint.rb +71 -0
- data/lib/falcon/server.rb +54 -0
- data/lib/falcon/service/application.rb +93 -0
- data/lib/falcon/service/generic.rb +60 -0
- data/lib/falcon/service/proxy.rb +60 -0
- data/lib/falcon/service/supervisor.rb +104 -0
- data/lib/falcon/services.rb +78 -0
- data/lib/falcon/tls.rb +46 -0
- data/lib/falcon/verbose.rb +59 -0
- data/lib/falcon/version.rb +25 -0
- data/lib/rack/handler/falcon.rb +40 -0
- data/logo-square.afdesign +0 -0
- data/logo.afdesign +0 -0
- data/logo.svg +107 -0
- data/server.rb +21 -0
- data/tasks/benchmark.rake +103 -0
- metadata +386 -0
@@ -0,0 +1,101 @@
|
|
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
|
+
# The Body must respond to each and must only yield String values. The Body itself should not be an instance of String, as this will break in Ruby 1.9. 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.
|
30
|
+
class Output < ::Protocol::HTTP::Body::Readable
|
31
|
+
CONTENT_LENGTH = 'content-length'.freeze
|
32
|
+
|
33
|
+
# Wraps an array into a buffered body.
|
34
|
+
def self.wrap(status, headers, body)
|
35
|
+
# In no circumstance do we want this header propagating out:
|
36
|
+
if length = headers.delete(CONTENT_LENGTH)
|
37
|
+
# We don't really trust the user to provide the right length to the transport.
|
38
|
+
length = Integer(length)
|
39
|
+
end
|
40
|
+
|
41
|
+
if body.is_a?(::Protocol::HTTP::Body::Readable)
|
42
|
+
return body
|
43
|
+
elsif status == 200 and body.respond_to?(:to_path)
|
44
|
+
# Don't mangle partial responsese (206)
|
45
|
+
return ::Protocol::HTTP::Body::File.open(body.to_path)
|
46
|
+
elsif body.is_a?(Array)
|
47
|
+
length ||= body.sum(&:bytesize)
|
48
|
+
return self.new(body, length)
|
49
|
+
else
|
50
|
+
return self.new(body, length)
|
51
|
+
end
|
52
|
+
end
|
53
|
+
|
54
|
+
def initialize(body, length)
|
55
|
+
@length = length
|
56
|
+
@body = body
|
57
|
+
|
58
|
+
@chunks = nil
|
59
|
+
end
|
60
|
+
|
61
|
+
# The rack response body.
|
62
|
+
attr :body
|
63
|
+
|
64
|
+
# The content length of the rack response body.
|
65
|
+
attr :length
|
66
|
+
|
67
|
+
def empty?
|
68
|
+
@length == 0 or (@body.respond_to?(:empty?) and @body.empty?)
|
69
|
+
end
|
70
|
+
|
71
|
+
def close(error = nil)
|
72
|
+
if @body and @body.respond_to?(:close)
|
73
|
+
@body.close
|
74
|
+
end
|
75
|
+
|
76
|
+
@body = nil
|
77
|
+
@chunks = nil
|
78
|
+
|
79
|
+
super
|
80
|
+
end
|
81
|
+
|
82
|
+
def each(&block)
|
83
|
+
@body.each(&block)
|
84
|
+
ensure
|
85
|
+
self.close($!)
|
86
|
+
end
|
87
|
+
|
88
|
+
def read
|
89
|
+
@chunks ||= @body.to_enum(:each)
|
90
|
+
|
91
|
+
return @chunks.next
|
92
|
+
rescue StopIteration
|
93
|
+
return nil
|
94
|
+
end
|
95
|
+
|
96
|
+
def inspect
|
97
|
+
"\#<#{self.class} length=#{@length.inspect} body=#{@body.class}>"
|
98
|
+
end
|
99
|
+
end
|
100
|
+
end
|
101
|
+
end
|
@@ -0,0 +1,202 @@
|
|
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
|
+
require_relative 'early_hints'
|
28
|
+
|
29
|
+
require 'async/logger'
|
30
|
+
|
31
|
+
module Falcon
|
32
|
+
module Adapters
|
33
|
+
class Rack
|
34
|
+
# CGI keys (https://tools.ietf.org/html/rfc3875#section-4.1)
|
35
|
+
HTTP_HOST = 'HTTP_HOST'.freeze
|
36
|
+
PATH_INFO = 'PATH_INFO'.freeze
|
37
|
+
REQUEST_METHOD = 'REQUEST_METHOD'.freeze
|
38
|
+
REQUEST_PATH = 'REQUEST_PATH'.freeze
|
39
|
+
REQUEST_URI = 'REQUEST_URI'.freeze
|
40
|
+
SCRIPT_NAME = 'SCRIPT_NAME'.freeze
|
41
|
+
QUERY_STRING = 'QUERY_STRING'.freeze
|
42
|
+
SERVER_PROTOCOL = 'SERVER_PROTOCOL'.freeze
|
43
|
+
SERVER_NAME = 'SERVER_NAME'.freeze
|
44
|
+
SERVER_PORT = 'SERVER_PORT'.freeze
|
45
|
+
REMOTE_ADDR = 'REMOTE_ADDR'.freeze
|
46
|
+
CONTENT_TYPE = 'CONTENT_TYPE'.freeze
|
47
|
+
CONTENT_LENGTH = 'CONTENT_LENGTH'.freeze
|
48
|
+
|
49
|
+
# Rack environment variables
|
50
|
+
RACK_VERSION = 'rack.version'.freeze
|
51
|
+
RACK_ERRORS = 'rack.errors'.freeze
|
52
|
+
RACK_LOGGER = 'rack.logger'.freeze
|
53
|
+
RACK_INPUT = 'rack.input'.freeze
|
54
|
+
RACK_MULTITHREAD = 'rack.multithread'.freeze
|
55
|
+
RACK_MULTIPROCESS = 'rack.multiprocess'.freeze
|
56
|
+
RACK_RUNONCE = 'rack.run_once'.freeze
|
57
|
+
RACK_URL_SCHEME = 'rack.url_scheme'.freeze
|
58
|
+
RACK_HIJACK = 'rack.hijack'.freeze
|
59
|
+
RACK_IS_HIJACK = 'rack.hijack?'.freeze
|
60
|
+
RACK_HIJACK_IO = 'rack.hijack_io'.freeze
|
61
|
+
RACK_EARLY_HINTS = "rack.early_hints".freeze
|
62
|
+
|
63
|
+
ASYNC_HTTP_REQUEST = "async.http.request".freeze
|
64
|
+
|
65
|
+
# Header constants
|
66
|
+
HTTP_X_FORWARDED_PROTO = 'HTTP_X_FORWARDED_PROTO'.freeze
|
67
|
+
|
68
|
+
def initialize(app, logger = Async.logger)
|
69
|
+
@app = app
|
70
|
+
|
71
|
+
raise ArgumentError, "App must be callable!" unless @app.respond_to?(:call)
|
72
|
+
|
73
|
+
@logger = logger
|
74
|
+
end
|
75
|
+
|
76
|
+
# Rack separates multiple headers with the same key, into a single field with multiple "lines".
|
77
|
+
def unwrap_headers(headers, env)
|
78
|
+
headers.each do |key, value|
|
79
|
+
http_key = "HTTP_#{key.upcase.tr('-', '_')}"
|
80
|
+
|
81
|
+
if current_value = env[http_key]
|
82
|
+
env[http_key] = "#{current_value};#{value}"
|
83
|
+
else
|
84
|
+
env[http_key] = value
|
85
|
+
end
|
86
|
+
end
|
87
|
+
end
|
88
|
+
|
89
|
+
# Process the incoming request into a valid rack env.
|
90
|
+
def unwrap_request(request, env)
|
91
|
+
if content_type = request.headers.delete('content-type')
|
92
|
+
env[CONTENT_TYPE] = content_type
|
93
|
+
end
|
94
|
+
|
95
|
+
# In some situations we don't know the content length, e.g. when using chunked encoding, or when decompressing the body.
|
96
|
+
if body = request.body and length = body.length
|
97
|
+
env[CONTENT_LENGTH] = length.to_s
|
98
|
+
end
|
99
|
+
|
100
|
+
self.unwrap_headers(request.headers, env)
|
101
|
+
|
102
|
+
# HTTP/2 prefers `:authority` over `host`, so we do this for backwards compatibility.
|
103
|
+
env[HTTP_HOST] ||= request.authority
|
104
|
+
|
105
|
+
# This is the HTTP/1 header for the scheme of the request and is used by Rack.
|
106
|
+
# Technically it should use the Forwarded header but this is not common yet.
|
107
|
+
# https://tools.ietf.org/html/rfc7239#section-5.4
|
108
|
+
# https://github.com/rack/rack/issues/1310
|
109
|
+
env[HTTP_X_FORWARDED_PROTO] ||= request.scheme
|
110
|
+
|
111
|
+
if remote_address = request.remote_address
|
112
|
+
env[REMOTE_ADDR] = remote_address.ip_address if remote_address.ip?
|
113
|
+
end
|
114
|
+
end
|
115
|
+
|
116
|
+
def call(request)
|
117
|
+
request_path, query_string = request.path.split('?', 2)
|
118
|
+
server_name, server_port = (request.authority || '').split(':', 2)
|
119
|
+
|
120
|
+
env = {
|
121
|
+
RACK_VERSION => [2, 0, 0],
|
122
|
+
|
123
|
+
ASYNC_HTTP_REQUEST => request,
|
124
|
+
|
125
|
+
RACK_INPUT => Input.new(request.body),
|
126
|
+
RACK_ERRORS => $stderr,
|
127
|
+
RACK_LOGGER => Async.logger,
|
128
|
+
|
129
|
+
RACK_MULTITHREAD => true,
|
130
|
+
RACK_MULTIPROCESS => true,
|
131
|
+
RACK_RUNONCE => false,
|
132
|
+
|
133
|
+
# The HTTP request method, such as “GET” or “POST”. This cannot ever be an empty string, and so is always required.
|
134
|
+
REQUEST_METHOD => request.method,
|
135
|
+
|
136
|
+
# 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.
|
137
|
+
SCRIPT_NAME => '',
|
138
|
+
|
139
|
+
# 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.
|
140
|
+
PATH_INFO => request_path,
|
141
|
+
REQUEST_PATH => request_path,
|
142
|
+
REQUEST_URI => request.path,
|
143
|
+
|
144
|
+
# The portion of the request URL that follows the ?, if any. May be empty, but is always required!
|
145
|
+
QUERY_STRING => query_string || '',
|
146
|
+
|
147
|
+
# The server protocol (e.g. HTTP/1.1):
|
148
|
+
SERVER_PROTOCOL => request.version,
|
149
|
+
|
150
|
+
# The request scheme:
|
151
|
+
RACK_URL_SCHEME => request.scheme,
|
152
|
+
|
153
|
+
# I'm not sure what sane defaults should be here:
|
154
|
+
SERVER_NAME => server_name || '',
|
155
|
+
SERVER_PORT => server_port || '',
|
156
|
+
|
157
|
+
# We support both request and response hijack.
|
158
|
+
RACK_IS_HIJACK => true,
|
159
|
+
}
|
160
|
+
|
161
|
+
self.unwrap_request(request, env)
|
162
|
+
|
163
|
+
if request.push?
|
164
|
+
env[RACK_EARLY_HINTS] = EarlyHints.new(request)
|
165
|
+
end
|
166
|
+
|
167
|
+
full_hijack = false
|
168
|
+
|
169
|
+
if request.hijack?
|
170
|
+
env[RACK_HIJACK] = lambda do
|
171
|
+
wrapper = request.hijack!
|
172
|
+
full_hijack = true
|
173
|
+
|
174
|
+
# 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.
|
175
|
+
io = wrapper.io.dup
|
176
|
+
wrapper.close
|
177
|
+
|
178
|
+
# This is implicitly returned:
|
179
|
+
env[RACK_HIJACK_IO] = io
|
180
|
+
end
|
181
|
+
end
|
182
|
+
|
183
|
+
status, headers, body = @app.call(env)
|
184
|
+
|
185
|
+
# If there was a full hijack:
|
186
|
+
if full_hijack
|
187
|
+
return nil
|
188
|
+
else
|
189
|
+
return Response.wrap(status, headers, body, request)
|
190
|
+
end
|
191
|
+
rescue => exception
|
192
|
+
@logger.error(self) {exception}
|
193
|
+
|
194
|
+
return failure_response(exception)
|
195
|
+
end
|
196
|
+
|
197
|
+
def failure_response(exception)
|
198
|
+
Protocol::HTTP::Response.for_exception(exception)
|
199
|
+
end
|
200
|
+
end
|
201
|
+
end
|
202
|
+
end
|
@@ -0,0 +1,91 @@
|
|
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
|
+
class Response < ::Protocol::HTTP::Response
|
33
|
+
IGNORE_HEADERS = Middleware::Proxy::HOP_HEADERS
|
34
|
+
|
35
|
+
# Append a list of newline encoded headers.
|
36
|
+
def self.wrap_headers(fields)
|
37
|
+
headers = ::Protocol::HTTP::Headers.new
|
38
|
+
meta = {}
|
39
|
+
|
40
|
+
fields.each do |key, value|
|
41
|
+
key = key.downcase
|
42
|
+
|
43
|
+
if key.start_with?('rack.')
|
44
|
+
meta[key] = value
|
45
|
+
else
|
46
|
+
value.to_s.split("\n").each do |part|
|
47
|
+
headers.add(key, part)
|
48
|
+
end
|
49
|
+
end
|
50
|
+
end
|
51
|
+
|
52
|
+
return headers, meta
|
53
|
+
end
|
54
|
+
|
55
|
+
def self.wrap(status, headers, body, request = nil)
|
56
|
+
headers, meta = wrap_headers(headers)
|
57
|
+
|
58
|
+
if block = meta['rack.hijack']
|
59
|
+
body = Async::HTTP::Body::Hijack.wrap(request, &block)
|
60
|
+
else
|
61
|
+
ignored = headers.extract(IGNORE_HEADERS)
|
62
|
+
|
63
|
+
unless ignored.empty?
|
64
|
+
Async.logger.warn("Ignoring protocol-level headers: #{ignored.inspect}")
|
65
|
+
end
|
66
|
+
|
67
|
+
body = Output.wrap(status, headers, body)
|
68
|
+
end
|
69
|
+
|
70
|
+
if request&.head?
|
71
|
+
# 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.
|
72
|
+
body = ::Protocol::HTTP::Body::Head.for(body)
|
73
|
+
end
|
74
|
+
|
75
|
+
protocol = meta['rack.protocol']
|
76
|
+
|
77
|
+
# https://tools.ietf.org/html/rfc7231#section-7.4.2
|
78
|
+
headers.add('server', "falcon/#{Falcon::VERSION}")
|
79
|
+
|
80
|
+
# https://tools.ietf.org/html/rfc7231#section-7.1.1.2
|
81
|
+
headers.add('date', Time.now.httpdate)
|
82
|
+
|
83
|
+
return self.new(status, headers, body, protocol)
|
84
|
+
end
|
85
|
+
|
86
|
+
def initialize(status, headers, body, protocol = nil)
|
87
|
+
super(nil, status, headers, body, protocol)
|
88
|
+
end
|
89
|
+
end
|
90
|
+
end
|
91
|
+
end
|
@@ -0,0 +1,67 @@
|
|
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.
|
28
|
+
class Rewindable < ::Protocol::HTTP::Middleware
|
29
|
+
BUFFERED_MEDIA_TYPES = %r{
|
30
|
+
application/x-www-form-urlencoded|
|
31
|
+
multipart/form-data|
|
32
|
+
multipart/related|
|
33
|
+
multipart/mixed
|
34
|
+
}x
|
35
|
+
|
36
|
+
POST = 'POST'.freeze
|
37
|
+
|
38
|
+
def initialize(app)
|
39
|
+
super(app)
|
40
|
+
end
|
41
|
+
|
42
|
+
def needs_rewind?(request)
|
43
|
+
content_type = request.headers['content-type']
|
44
|
+
|
45
|
+
if request.method == POST and content_type.nil?
|
46
|
+
return true
|
47
|
+
end
|
48
|
+
|
49
|
+
if BUFFERED_MEDIA_TYPES =~ content_type
|
50
|
+
return true
|
51
|
+
end
|
52
|
+
|
53
|
+
return false
|
54
|
+
end
|
55
|
+
|
56
|
+
# Wrap the request body in a rewindable buffer.
|
57
|
+
# @return [Protocol::HTTP::Response] the response.
|
58
|
+
def call(request)
|
59
|
+
if body = request.body and needs_rewind?(request)
|
60
|
+
request.body = Async::HTTP::Body::Rewindable.new(body)
|
61
|
+
end
|
62
|
+
|
63
|
+
return super
|
64
|
+
end
|
65
|
+
end
|
66
|
+
end
|
67
|
+
end
|