falcon 0.12.0 → 0.13.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/examples/beer/config.ru +15 -7
- data/falcon.gemspec +3 -3
- data/lib/falcon.rb +1 -0
- data/lib/falcon/adapters/input.rb +111 -0
- data/lib/falcon/adapters/rack.rb +111 -0
- data/lib/falcon/command/serve.rb +6 -0
- data/lib/falcon/server.rb +2 -76
- data/lib/falcon/verbose.rb +20 -26
- data/lib/falcon/version.rb +1 -1
- data/lib/rack/handler/falcon.rb +1 -1
- metadata +8 -7
- data/lib/falcon/input.rb +0 -109
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 24e547afb939a6ed6819928e4b2a38dc8044d13d5009f065ee445bb57be69547
|
4
|
+
data.tar.gz: ac02c9762122bcac8d751e1d3b494212c85187988b54a071f6965bd5041064ed
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 86109950c4f6d80e86d23396a646a31588190172cfd7d9a2f6ee5d05f4156923c71b5ad55cd764a3bf3bded3b4dcf407c84c2410bccb87a3d1fc122df695b401
|
7
|
+
data.tar.gz: e21aa13ba9ab5421c00dfbd3e1d00975d931ef879571448853bbb0870412648e219888e62a5a5f22b0b61ff714daf507bc12fe91512b3b3a74cf4f101d3df41b
|
data/examples/beer/config.ru
CHANGED
@@ -1,5 +1,7 @@
|
|
1
1
|
#!/usr/bin/env falcon --verbose serve -c
|
2
2
|
|
3
|
+
require 'rack'
|
4
|
+
|
3
5
|
def bottles(n)
|
4
6
|
n == 1 ? "#{n} bottle" : "#{n} bottles"
|
5
7
|
end
|
@@ -7,25 +9,31 @@ end
|
|
7
9
|
# Browsers don't show streaming content until a certain threshold has been met. For most browers, it's about 1024 bytes. So, we have a comment of about that length which we feed to the client before streaming actual content. For more details see https://stackoverflow.com/questions/16909227
|
8
10
|
COMMENT = "<!--#{'-' * 1024}-->"
|
9
11
|
|
12
|
+
# To test this example with the curl command-line tool, you'll need to add the `--no-buffer` flag or set the COMMENT size to the required 4096 bytes in order for curl to start streaming.
|
13
|
+
# curl http://localhost:9292/ --no-buffer
|
14
|
+
|
10
15
|
run lambda {|env|
|
11
16
|
task = Async::Task.current
|
12
|
-
body = Async::HTTP::Body.new
|
17
|
+
body = Async::HTTP::Body::Writable.new
|
18
|
+
|
19
|
+
request = Rack::Request.new(env)
|
20
|
+
count = (request.params['count'] || 99).to_i
|
13
21
|
|
14
|
-
body.write("<!DOCTYPE html><html><head><title
|
22
|
+
body.write("<!DOCTYPE html><html><head><title>#{count} Bottles of Beer</title></head><body>")
|
15
23
|
|
16
24
|
task.async do |task|
|
17
25
|
body.write(COMMENT)
|
18
26
|
|
19
|
-
|
27
|
+
count.downto(1) do |i|
|
20
28
|
puts "#{bottles(i)} of beer on the wall..."
|
21
29
|
body.write("<p>#{bottles(i)} of beer on the wall, ")
|
22
|
-
task.sleep(1)
|
30
|
+
task.sleep(0.1)
|
23
31
|
body.write("#{bottles(i)} of beer, ")
|
24
|
-
task.sleep(1)
|
32
|
+
task.sleep(0.1)
|
25
33
|
body.write("take one down and pass it around, ")
|
26
|
-
task.sleep(1)
|
34
|
+
task.sleep(0.1)
|
27
35
|
body.write("#{bottles(i-1)} of beer on the wall.</p>")
|
28
|
-
task.sleep(1)
|
36
|
+
task.sleep(0.1)
|
29
37
|
body.write("<script>var child; while (child = document.body.firstChild) child.remove();</script>")
|
30
38
|
end
|
31
39
|
|
data/falcon.gemspec
CHANGED
@@ -1,4 +1,4 @@
|
|
1
|
-
|
1
|
+
|
2
2
|
require_relative 'lib/falcon/version'
|
3
3
|
|
4
4
|
Gem::Specification.new do |spec|
|
@@ -17,8 +17,8 @@ Gem::Specification.new do |spec|
|
|
17
17
|
spec.require_paths = ["lib"]
|
18
18
|
|
19
19
|
spec.add_dependency("async-io", "~> 1.6")
|
20
|
-
spec.add_dependency("async-http", "~> 0.
|
21
|
-
spec.add_dependency("async-container", "~> 0.
|
20
|
+
spec.add_dependency("async-http", "~> 0.19.0")
|
21
|
+
spec.add_dependency("async-container", "~> 0.4.0")
|
22
22
|
|
23
23
|
spec.add_dependency("rack", ">= 1.0")
|
24
24
|
|
data/lib/falcon.rb
CHANGED
@@ -0,0 +1,111 @@
|
|
1
|
+
# Copyright, 2018, by Samuel G. D. Williams. <http://www.codeotaku.com>
|
2
|
+
#
|
3
|
+
# Permission is hereby granted, free of charge, to any person obtaining a copy
|
4
|
+
# of this software and associated documentation files (the "Software"), to deal
|
5
|
+
# in the Software without restriction, including without limitation the rights
|
6
|
+
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
7
|
+
# copies of the Software, and to permit persons to whom the Software is
|
8
|
+
# furnished to do so, subject to the following conditions:
|
9
|
+
#
|
10
|
+
# The above copyright notice and this permission notice shall be included in
|
11
|
+
# all copies or substantial portions of the Software.
|
12
|
+
#
|
13
|
+
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
14
|
+
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
15
|
+
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
16
|
+
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
17
|
+
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
18
|
+
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
19
|
+
# THE SOFTWARE.
|
20
|
+
|
21
|
+
require 'async/http/body'
|
22
|
+
|
23
|
+
module Falcon
|
24
|
+
module Adapters
|
25
|
+
class Input
|
26
|
+
def initialize(body)
|
27
|
+
@body = body
|
28
|
+
@chunks = []
|
29
|
+
|
30
|
+
@index = 0
|
31
|
+
@buffer = Async::IO::BinaryString.new
|
32
|
+
@finished = false
|
33
|
+
end
|
34
|
+
|
35
|
+
def each(&block)
|
36
|
+
return to_enum unless block_given?
|
37
|
+
|
38
|
+
while chunk = read_next
|
39
|
+
yield chunk
|
40
|
+
end
|
41
|
+
|
42
|
+
@closed = true
|
43
|
+
end
|
44
|
+
|
45
|
+
def rewind
|
46
|
+
@index = 0
|
47
|
+
@finished = false
|
48
|
+
@buffer.clear
|
49
|
+
end
|
50
|
+
|
51
|
+
def read(length = nil, buffer = nil)
|
52
|
+
if length
|
53
|
+
fill_buffer(length) if @buffer.bytesize <= length
|
54
|
+
|
55
|
+
return @buffer.slice!(0, length)
|
56
|
+
else
|
57
|
+
buffer ||= Async::IO::BinaryString.new
|
58
|
+
|
59
|
+
buffer << @buffer
|
60
|
+
@buffer.clear
|
61
|
+
|
62
|
+
while chunk = read_next
|
63
|
+
buffer << chunk
|
64
|
+
end
|
65
|
+
|
66
|
+
return buffer
|
67
|
+
end
|
68
|
+
end
|
69
|
+
|
70
|
+
def eof?
|
71
|
+
@finished and @buffer.empty?
|
72
|
+
end
|
73
|
+
|
74
|
+
def gets
|
75
|
+
read
|
76
|
+
end
|
77
|
+
|
78
|
+
def close
|
79
|
+
@body.finish
|
80
|
+
end
|
81
|
+
|
82
|
+
private
|
83
|
+
|
84
|
+
def read_next
|
85
|
+
return nil if @finished
|
86
|
+
|
87
|
+
chunk = nil
|
88
|
+
|
89
|
+
if @index < @chunks.count
|
90
|
+
chunk = @chunks[@index]
|
91
|
+
@index += 1
|
92
|
+
else
|
93
|
+
if chunk = @body.read
|
94
|
+
@chunks << chunk
|
95
|
+
@index += 1
|
96
|
+
end
|
97
|
+
end
|
98
|
+
|
99
|
+
@finished = true if chunk.nil?
|
100
|
+
|
101
|
+
return chunk
|
102
|
+
end
|
103
|
+
|
104
|
+
def fill_buffer(length)
|
105
|
+
while @buffer.bytesize < length and chunk = read_next
|
106
|
+
@buffer << chunk
|
107
|
+
end
|
108
|
+
end
|
109
|
+
end
|
110
|
+
end
|
111
|
+
end
|
@@ -0,0 +1,111 @@
|
|
1
|
+
# Copyright, 2017, by Samuel G. D. Williams. <http://www.codeotaku.com>
|
2
|
+
#
|
3
|
+
# Permission is hereby granted, free of charge, to any person obtaining a copy
|
4
|
+
# of this software and associated documentation files (the "Software"), to deal
|
5
|
+
# in the Software without restriction, including without limitation the rights
|
6
|
+
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
7
|
+
# copies of the Software, and to permit persons to whom the Software is
|
8
|
+
# furnished to do so, subject to the following conditions:
|
9
|
+
#
|
10
|
+
# The above copyright notice and this permission notice shall be included in
|
11
|
+
# all copies or substantial portions of the Software.
|
12
|
+
#
|
13
|
+
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
14
|
+
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
15
|
+
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
16
|
+
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
17
|
+
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
18
|
+
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
19
|
+
# THE SOFTWARE.
|
20
|
+
|
21
|
+
require_relative 'input'
|
22
|
+
|
23
|
+
require 'async/http/body/buffered'
|
24
|
+
require 'async/logger'
|
25
|
+
|
26
|
+
module Falcon
|
27
|
+
module Adapters
|
28
|
+
class Rack
|
29
|
+
def initialize(app, logger = Async.logger)
|
30
|
+
@app = app
|
31
|
+
@logger = logger
|
32
|
+
end
|
33
|
+
|
34
|
+
def call(request, peer: nil, address: nil)
|
35
|
+
request_path, query_string = request.path.split('?', 2)
|
36
|
+
server_name, server_port = (request.authority || '').split(':', 2)
|
37
|
+
|
38
|
+
env = {
|
39
|
+
'rack.version' => [2, 0, 0],
|
40
|
+
|
41
|
+
'rack.input' => Input.new(request.body),
|
42
|
+
'rack.errors' => $stderr,
|
43
|
+
|
44
|
+
'rack.multithread' => true,
|
45
|
+
'rack.multiprocess' => true,
|
46
|
+
'rack.run_once' => false,
|
47
|
+
|
48
|
+
# The HTTP request method, such as “GET” or “POST”. This cannot ever be an empty string, and so is always required.
|
49
|
+
'REQUEST_METHOD' => request.method,
|
50
|
+
|
51
|
+
# 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.
|
52
|
+
'SCRIPT_NAME' => '',
|
53
|
+
|
54
|
+
# 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.
|
55
|
+
'PATH_INFO' => request_path,
|
56
|
+
|
57
|
+
# The portion of the request URL that follows the ?, if any. May be empty, but is always required!
|
58
|
+
'QUERY_STRING' => query_string || '',
|
59
|
+
|
60
|
+
# The server protocol, e.g. HTTP/1.1
|
61
|
+
'SERVER_PROTOCOL' => request.version,
|
62
|
+
'rack.url_scheme' => 'http',
|
63
|
+
|
64
|
+
# I'm not sure what sane defaults should be here:
|
65
|
+
'SERVER_NAME' => server_name || '',
|
66
|
+
'SERVER_PORT' => server_port || '',
|
67
|
+
}
|
68
|
+
|
69
|
+
if content_type = request.headers.delete('content-type')
|
70
|
+
env['CONTENT_TYPE'] = content_type
|
71
|
+
end
|
72
|
+
|
73
|
+
if content_length = request.headers.delete('content-length')
|
74
|
+
env['CONTENT_LENGTH'] = content_length
|
75
|
+
end
|
76
|
+
|
77
|
+
request.headers.each do |key, value|
|
78
|
+
env["HTTP_#{key.upcase.tr('-', '_')}"] = value
|
79
|
+
end
|
80
|
+
|
81
|
+
env['rack.hijack?'] = true
|
82
|
+
env['rack.hijack'] = lambda do
|
83
|
+
env['rack.hijack_io'] = peer
|
84
|
+
end
|
85
|
+
|
86
|
+
if remote_address = peer.remote_address
|
87
|
+
env['REMOTE_ADDR'] = remote_address.ip_address if remote_address.ip?
|
88
|
+
end
|
89
|
+
|
90
|
+
status, headers, body = @app.call(env)
|
91
|
+
|
92
|
+
# We normalize headers to be lower case.
|
93
|
+
headers = headers.map{|key, value| [key.downcase, value]}.to_h
|
94
|
+
|
95
|
+
if env['rack.hijack_io']
|
96
|
+
throw :hijack
|
97
|
+
else
|
98
|
+
return Async::HTTP::Response[status, headers, Async::HTTP::Body::Buffered.wrap(body)]
|
99
|
+
end
|
100
|
+
rescue => exception
|
101
|
+
@logger.error "#{exception.class}: #{exception.message}\n\t#{$!.backtrace.join("\n\t")}"
|
102
|
+
|
103
|
+
return failure_response(exception)
|
104
|
+
end
|
105
|
+
|
106
|
+
def failure_response(exception)
|
107
|
+
Async::HTTP::Response.for_exception(exception)
|
108
|
+
end
|
109
|
+
end
|
110
|
+
end
|
111
|
+
end
|
data/lib/falcon/command/serve.rb
CHANGED
@@ -20,6 +20,7 @@
|
|
20
20
|
|
21
21
|
require_relative '../server'
|
22
22
|
require_relative '../verbose'
|
23
|
+
require_relative '../adapters/rack'
|
23
24
|
|
24
25
|
require 'async/container'
|
25
26
|
require 'async/io/trap'
|
@@ -57,6 +58,11 @@ module Falcon
|
|
57
58
|
def load_app(verbose)
|
58
59
|
app, options = Rack::Builder.parse_file(@options[:config])
|
59
60
|
|
61
|
+
# We adapt the rack app to Async::HTTP::Middleware
|
62
|
+
app = Adapters::Rack.new(app)
|
63
|
+
|
64
|
+
app = Async::HTTP::ContentEncoding.new(app)
|
65
|
+
|
60
66
|
if verbose
|
61
67
|
app = Verbose.new(app)
|
62
68
|
end
|
data/lib/falcon/server.rb
CHANGED
@@ -18,9 +18,8 @@
|
|
18
18
|
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
19
19
|
# THE SOFTWARE.
|
20
20
|
|
21
|
-
require_relative 'input'
|
22
|
-
|
23
21
|
require 'async/http/server'
|
22
|
+
require 'async/http/content_encoding'
|
24
23
|
|
25
24
|
module Falcon
|
26
25
|
class Server < Async::HTTP::Server
|
@@ -30,81 +29,8 @@ module Falcon
|
|
30
29
|
@app = app
|
31
30
|
end
|
32
31
|
|
33
|
-
def logger
|
34
|
-
Async.logger
|
35
|
-
end
|
36
|
-
|
37
32
|
def handle_request(request, peer, address)
|
38
|
-
|
39
|
-
server_name, server_port = (request.authority || '').split(':', 2)
|
40
|
-
|
41
|
-
env = {
|
42
|
-
'rack.version' => [2, 0, 0],
|
43
|
-
|
44
|
-
'rack.input' => Input.new(request.body),
|
45
|
-
'rack.errors' => $stderr,
|
46
|
-
|
47
|
-
'rack.multithread' => true,
|
48
|
-
'rack.multiprocess' => true,
|
49
|
-
'rack.run_once' => false,
|
50
|
-
|
51
|
-
# The HTTP request method, such as “GET” or “POST”. This cannot ever be an empty string, and so is always required.
|
52
|
-
'REQUEST_METHOD' => request.method,
|
53
|
-
|
54
|
-
# 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.
|
55
|
-
'SCRIPT_NAME' => '',
|
56
|
-
|
57
|
-
# 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.
|
58
|
-
'PATH_INFO' => request_path,
|
59
|
-
|
60
|
-
# The portion of the request URL that follows the ?, if any. May be empty, but is always required!
|
61
|
-
'QUERY_STRING' => query_string || '',
|
62
|
-
|
63
|
-
# The server protocol, e.g. HTTP/1.1
|
64
|
-
'SERVER_PROTOCOL' => request.version,
|
65
|
-
'rack.url_scheme' => 'http',
|
66
|
-
|
67
|
-
# I'm not sure what sane defaults should be here:
|
68
|
-
'SERVER_NAME' => server_name || '',
|
69
|
-
'SERVER_PORT' => server_port || '',
|
70
|
-
}
|
71
|
-
|
72
|
-
if content_type = request.headers['content-type']
|
73
|
-
env['CONTENT_TYPE'] = content_type
|
74
|
-
end
|
75
|
-
|
76
|
-
request.headers.each do |key, value|
|
77
|
-
env["HTTP_#{key.upcase.tr('-', '_')}"] = value
|
78
|
-
end
|
79
|
-
|
80
|
-
env['rack.hijack?'] = true
|
81
|
-
env['rack.hijack'] = lambda do
|
82
|
-
env['rack.hijack_io'] = peer
|
83
|
-
end
|
84
|
-
|
85
|
-
if content_type = request.headers['HTTP_CONTENT_TYPE']
|
86
|
-
env['CONTENT_TYPE'] = content_type
|
87
|
-
end
|
88
|
-
|
89
|
-
if remote_address = peer.remote_address
|
90
|
-
env['REMOTE_ADDR'] = remote_address.ip_address if remote_address.ip?
|
91
|
-
end
|
92
|
-
|
93
|
-
response = @app.call(env)
|
94
|
-
|
95
|
-
if env['rack.hijack_io']
|
96
|
-
throw :hijack
|
97
|
-
else
|
98
|
-
return response
|
99
|
-
end
|
100
|
-
rescue => exception
|
101
|
-
logger.error "#{exception.class}: #{exception.message}\n\t#{$!.backtrace.join("\n\t")}"
|
102
|
-
|
103
|
-
return failure_response(exception)
|
104
|
-
end
|
105
|
-
|
106
|
-
def failure_response(exception)
|
107
|
-
[500, {'Content-Type' => 'text/plain'}, ["Request failed due to: #{exception.class}: #{exception.message}"]]
|
33
|
+
@app.call(request, peer: peer, address: address)
|
108
34
|
end
|
109
35
|
end
|
110
36
|
end
|
data/lib/falcon/verbose.rb
CHANGED
@@ -19,45 +19,39 @@
|
|
19
19
|
# THE SOFTWARE.
|
20
20
|
|
21
21
|
require 'async/logger'
|
22
|
+
require 'async/http/statistics'
|
22
23
|
|
23
24
|
module Falcon
|
24
|
-
class Verbose
|
25
|
+
class Verbose < Async::HTTP::Middleware
|
25
26
|
def initialize(app, logger = Async.logger)
|
26
|
-
|
27
|
+
super(app)
|
28
|
+
|
27
29
|
@logger = logger
|
28
30
|
end
|
29
31
|
|
30
|
-
def annotate(
|
31
|
-
|
32
|
-
|
33
|
-
|
32
|
+
def annotate(request, peer: nil, address: nil)
|
33
|
+
task = Async::Task.current
|
34
|
+
|
35
|
+
# @logger.debug "#{request.method} #{request.path} #{request.version} from #{address.inspect}"
|
36
|
+
# @logger.debug request.headers.inspect
|
34
37
|
|
35
|
-
task.annotate("#{
|
38
|
+
task.annotate("#{request.method} #{request.path} from #{address.inspect}")
|
36
39
|
end
|
37
40
|
|
38
|
-
def
|
39
|
-
|
41
|
+
def call(request, **options)
|
42
|
+
annotate(request, **options)
|
40
43
|
|
41
|
-
|
42
|
-
request_path = env['PATH_INFO']
|
43
|
-
server_protocol = env['SERVER_PROTOCOL']
|
44
|
+
statistics = Async::HTTP::Statistics.start
|
44
45
|
|
45
|
-
|
46
|
-
status, headers, body = response
|
47
|
-
@logger.info "#{request_method} #{request_path} #{server_protocol} -> #{status}; Content length #{headers.fetch('Content-Length', '-')} bytes; took #{duration} seconds"
|
48
|
-
else
|
49
|
-
@logger.info "#{request_method} #{request_path} #{server_protocol} -> #{error}; took #{duration} seconds"
|
50
|
-
end
|
51
|
-
end
|
52
|
-
|
53
|
-
def call(env)
|
54
|
-
start_time = Time.now
|
46
|
+
response = super
|
55
47
|
|
56
|
-
|
48
|
+
statistics.wrap(response) do |statistics, error|
|
49
|
+
@logger.info "#{request.method} #{request.path} #{request.version} -> #{response.status}; #{statistics.inspect}"
|
50
|
+
# @logger.info response.headers.inspect
|
51
|
+
@logger.error "#{error.class}: #{error.message}" if error
|
52
|
+
end
|
57
53
|
|
58
|
-
response
|
59
|
-
ensure
|
60
|
-
log(start_time, env, response, $!)
|
54
|
+
return response
|
61
55
|
end
|
62
56
|
end
|
63
57
|
end
|
data/lib/falcon/version.rb
CHANGED
data/lib/rack/handler/falcon.rb
CHANGED
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: falcon
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 0.13.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Samuel Williams
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2018-04-
|
11
|
+
date: 2018-04-17 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: async-io
|
@@ -30,28 +30,28 @@ dependencies:
|
|
30
30
|
requirements:
|
31
31
|
- - "~>"
|
32
32
|
- !ruby/object:Gem::Version
|
33
|
-
version:
|
33
|
+
version: 0.19.0
|
34
34
|
type: :runtime
|
35
35
|
prerelease: false
|
36
36
|
version_requirements: !ruby/object:Gem::Requirement
|
37
37
|
requirements:
|
38
38
|
- - "~>"
|
39
39
|
- !ruby/object:Gem::Version
|
40
|
-
version:
|
40
|
+
version: 0.19.0
|
41
41
|
- !ruby/object:Gem::Dependency
|
42
42
|
name: async-container
|
43
43
|
requirement: !ruby/object:Gem::Requirement
|
44
44
|
requirements:
|
45
45
|
- - "~>"
|
46
46
|
- !ruby/object:Gem::Version
|
47
|
-
version:
|
47
|
+
version: 0.4.0
|
48
48
|
type: :runtime
|
49
49
|
prerelease: false
|
50
50
|
version_requirements: !ruby/object:Gem::Requirement
|
51
51
|
requirements:
|
52
52
|
- - "~>"
|
53
53
|
- !ruby/object:Gem::Version
|
54
|
-
version:
|
54
|
+
version: 0.4.0
|
55
55
|
- !ruby/object:Gem::Dependency
|
56
56
|
name: rack
|
57
57
|
requirement: !ruby/object:Gem::Requirement
|
@@ -158,9 +158,10 @@ files:
|
|
158
158
|
- examples/sinatra/config.ru
|
159
159
|
- falcon.gemspec
|
160
160
|
- lib/falcon.rb
|
161
|
+
- lib/falcon/adapters/input.rb
|
162
|
+
- lib/falcon/adapters/rack.rb
|
161
163
|
- lib/falcon/command.rb
|
162
164
|
- lib/falcon/command/serve.rb
|
163
|
-
- lib/falcon/input.rb
|
164
165
|
- lib/falcon/server.rb
|
165
166
|
- lib/falcon/verbose.rb
|
166
167
|
- lib/falcon/version.rb
|
data/lib/falcon/input.rb
DELETED
@@ -1,109 +0,0 @@
|
|
1
|
-
# Copyright, 2017, by Samuel G. D. Williams. <http://www.codeotaku.com>
|
2
|
-
#
|
3
|
-
# Permission is hereby granted, free of charge, to any person obtaining a copy
|
4
|
-
# of this software and associated documentation files (the "Software"), to deal
|
5
|
-
# in the Software without restriction, including without limitation the rights
|
6
|
-
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
7
|
-
# copies of the Software, and to permit persons to whom the Software is
|
8
|
-
# furnished to do so, subject to the following conditions:
|
9
|
-
#
|
10
|
-
# The above copyright notice and this permission notice shall be included in
|
11
|
-
# all copies or substantial portions of the Software.
|
12
|
-
#
|
13
|
-
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
14
|
-
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
15
|
-
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
16
|
-
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
17
|
-
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
18
|
-
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
19
|
-
# THE SOFTWARE.
|
20
|
-
|
21
|
-
require 'async/http/server'
|
22
|
-
|
23
|
-
module Falcon
|
24
|
-
class Input
|
25
|
-
def initialize(body)
|
26
|
-
@body = body
|
27
|
-
@chunks = []
|
28
|
-
|
29
|
-
@index = 0
|
30
|
-
@buffer = Async::IO::BinaryString.new
|
31
|
-
@finished = false
|
32
|
-
end
|
33
|
-
|
34
|
-
def each(&block)
|
35
|
-
return to_enum unless block_given?
|
36
|
-
|
37
|
-
while chunk = read_next
|
38
|
-
yield chunk
|
39
|
-
end
|
40
|
-
|
41
|
-
@closed = true
|
42
|
-
end
|
43
|
-
|
44
|
-
def rewind
|
45
|
-
@index = 0
|
46
|
-
@finished = false
|
47
|
-
@buffer.clear
|
48
|
-
end
|
49
|
-
|
50
|
-
def read(length = nil, buffer = nil)
|
51
|
-
if length
|
52
|
-
fill_buffer(length) if @buffer.bytesize <= length
|
53
|
-
|
54
|
-
return @buffer.slice!(0, length)
|
55
|
-
else
|
56
|
-
buffer ||= Async::IO::BinaryString.new
|
57
|
-
|
58
|
-
buffer << @buffer
|
59
|
-
@buffer.clear
|
60
|
-
|
61
|
-
while chunk = read_next
|
62
|
-
buffer << chunk
|
63
|
-
end
|
64
|
-
|
65
|
-
return buffer
|
66
|
-
end
|
67
|
-
end
|
68
|
-
|
69
|
-
def eof?
|
70
|
-
@finished and @buffer.empty?
|
71
|
-
end
|
72
|
-
|
73
|
-
def gets
|
74
|
-
read
|
75
|
-
end
|
76
|
-
|
77
|
-
def close
|
78
|
-
@body.finish
|
79
|
-
end
|
80
|
-
|
81
|
-
private
|
82
|
-
|
83
|
-
def read_next
|
84
|
-
return nil if @finished
|
85
|
-
|
86
|
-
chunk = nil
|
87
|
-
|
88
|
-
if @index < @chunks.count
|
89
|
-
chunk = @chunks[@index]
|
90
|
-
@index += 1
|
91
|
-
else
|
92
|
-
if chunk = @body.read
|
93
|
-
@chunks << chunk
|
94
|
-
@index += 1
|
95
|
-
end
|
96
|
-
end
|
97
|
-
|
98
|
-
@finished = true if chunk.nil?
|
99
|
-
|
100
|
-
return chunk
|
101
|
-
end
|
102
|
-
|
103
|
-
def fill_buffer(length)
|
104
|
-
while @buffer.bytesize < length and chunk = read_next
|
105
|
-
@buffer << chunk
|
106
|
-
end
|
107
|
-
end
|
108
|
-
end
|
109
|
-
end
|