falcon 0.36.4

Sign up to get free protection for your applications and to get access to all the features.
Files changed (92) hide show
  1. checksums.yaml +7 -0
  2. data/.editorconfig +5 -0
  3. data/.github/FUNDING.yml +3 -0
  4. data/.github/workflows/development.yml +60 -0
  5. data/.gitignore +14 -0
  6. data/.rspec +3 -0
  7. data/Gemfile +17 -0
  8. data/README.md +316 -0
  9. data/bake/falcon/supervisor.rb +8 -0
  10. data/bin/falcon +32 -0
  11. data/bin/falcon-host +28 -0
  12. data/examples/beer/config.ru +57 -0
  13. data/examples/beer/falcon.rb +8 -0
  14. data/examples/benchmark/config.ru +39 -0
  15. data/examples/benchmark/falcon.rb +6 -0
  16. data/examples/csv/config.ru +31 -0
  17. data/examples/google/falcon.rb +14 -0
  18. data/examples/hello/config.ru +22 -0
  19. data/examples/hello/falcon.rb +24 -0
  20. data/examples/hello/preload.rb +7 -0
  21. data/examples/internet/config.ru +54 -0
  22. data/examples/memory/allocations.rb +39 -0
  23. data/examples/memory/config.ru +14 -0
  24. data/examples/push/client.rb +29 -0
  25. data/examples/push/config.ru +28 -0
  26. data/examples/push/index.html +14 -0
  27. data/examples/push/script.js +2 -0
  28. data/examples/push/style.css +4 -0
  29. data/examples/redis/Gemfile +9 -0
  30. data/examples/redis/config.ru +28 -0
  31. data/examples/sequel/Gemfile +4 -0
  32. data/examples/sequel/config.ru +8 -0
  33. data/examples/sequel/data.sqlite3 +0 -0
  34. data/examples/server/standalone.rb +27 -0
  35. data/examples/sinatra/Gemfile +7 -0
  36. data/examples/sinatra/Gemfile.lock +53 -0
  37. data/examples/sinatra/config.ru +16 -0
  38. data/examples/trailers/config.ru +34 -0
  39. data/examples/trailers/falcon.rb +8 -0
  40. data/falcon.gemspec +45 -0
  41. data/gems/rack1.gemfile +4 -0
  42. data/gems/rack3.gemfile +4 -0
  43. data/lib/falcon.rb +23 -0
  44. data/lib/falcon/adapters/early_hints.rb +49 -0
  45. data/lib/falcon/adapters/input.rb +131 -0
  46. data/lib/falcon/adapters/output.rb +101 -0
  47. data/lib/falcon/adapters/rack.rb +202 -0
  48. data/lib/falcon/adapters/response.rb +91 -0
  49. data/lib/falcon/adapters/rewindable.rb +67 -0
  50. data/lib/falcon/command.rb +31 -0
  51. data/lib/falcon/command/host.rb +69 -0
  52. data/lib/falcon/command/paths.rb +47 -0
  53. data/lib/falcon/command/proxy.rb +71 -0
  54. data/lib/falcon/command/redirect.rb +76 -0
  55. data/lib/falcon/command/serve.rb +151 -0
  56. data/lib/falcon/command/supervisor.rb +78 -0
  57. data/lib/falcon/command/top.rb +94 -0
  58. data/lib/falcon/command/virtual.rb +89 -0
  59. data/lib/falcon/configuration.rb +147 -0
  60. data/lib/falcon/configuration/application.rb +46 -0
  61. data/lib/falcon/configuration/lets_encrypt_tls.rb +30 -0
  62. data/lib/falcon/configuration/proxy.rb +27 -0
  63. data/lib/falcon/configuration/rack.rb +38 -0
  64. data/lib/falcon/configuration/self_signed_tls.rb +47 -0
  65. data/lib/falcon/configuration/supervisor.rb +37 -0
  66. data/lib/falcon/configuration/tls.rb +70 -0
  67. data/lib/falcon/controller/host.rb +60 -0
  68. data/lib/falcon/controller/proxy.rb +109 -0
  69. data/lib/falcon/controller/redirect.rb +69 -0
  70. data/lib/falcon/controller/serve.rb +111 -0
  71. data/lib/falcon/controller/virtual.rb +100 -0
  72. data/lib/falcon/endpoint.rb +52 -0
  73. data/lib/falcon/extensions/openssl.rb +33 -0
  74. data/lib/falcon/middleware/proxy.rb +145 -0
  75. data/lib/falcon/middleware/redirect.rb +66 -0
  76. data/lib/falcon/proxy_endpoint.rb +71 -0
  77. data/lib/falcon/server.rb +54 -0
  78. data/lib/falcon/service/application.rb +93 -0
  79. data/lib/falcon/service/generic.rb +60 -0
  80. data/lib/falcon/service/proxy.rb +60 -0
  81. data/lib/falcon/service/supervisor.rb +104 -0
  82. data/lib/falcon/services.rb +78 -0
  83. data/lib/falcon/tls.rb +46 -0
  84. data/lib/falcon/verbose.rb +59 -0
  85. data/lib/falcon/version.rb +25 -0
  86. data/lib/rack/handler/falcon.rb +40 -0
  87. data/logo-square.afdesign +0 -0
  88. data/logo.afdesign +0 -0
  89. data/logo.svg +107 -0
  90. data/server.rb +21 -0
  91. data/tasks/benchmark.rake +103 -0
  92. 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