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 +4 -4
- checksums.yaml.gz.sig +0 -0
- data/lib/.DS_Store +0 -0
- data/lib/falcon/server.rb +2 -5
- data/lib/falcon/version.rb +1 -1
- data/lib/rack/handler/falcon.rb +1 -2
- data.tar.gz.sig +0 -0
- metadata +8 -12
- metadata.gz.sig +0 -0
- data/lib/falcon/adapters/input.rb +0 -152
- data/lib/falcon/adapters/output.rb +0 -138
- data/lib/falcon/adapters/rack.rb +0 -224
- data/lib/falcon/adapters/response.rb +0 -113
- data/lib/falcon/adapters/rewindable.rb +0 -74
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 41752345deec82e8434e7b59548a97dab31902b6038893db4a6fa65eda46479d
|
4
|
+
data.tar.gz: 7aaaca95cfdd733b9ae68ad49db16f9007266f9e42211c156376001f88cc3b1d
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
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
|
54
|
-
use Adapters::Rack
|
51
|
+
use ::Protocol::Rack::Adapter
|
55
52
|
|
56
53
|
run rack_app
|
57
54
|
end
|
data/lib/falcon/version.rb
CHANGED
data/lib/rack/handler/falcon.rb
CHANGED
@@ -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 = ::
|
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.
|
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-
|
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:
|
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:
|
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
|
data/lib/falcon/adapters/rack.rb
DELETED
@@ -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
|