async-http 0.26.0 → 0.27.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/.travis.yml +1 -0
- data/Rakefile +2 -2
- data/lib/async/http/accept_encoding.rb +1 -1
- data/lib/async/http/body/buffered.rb +0 -12
- data/lib/async/http/body/file.rb +22 -4
- data/lib/async/http/body/fixed.rb +4 -1
- data/lib/async/http/body/readable.rb +1 -1
- data/lib/async/http/body/reader.rb +59 -0
- data/lib/async/http/body/streamable.rb +6 -0
- data/lib/async/http/body/writable.rb +5 -4
- data/lib/async/http/client.rb +11 -8
- data/lib/async/http/content_encoding.rb +1 -1
- data/lib/async/http/middleware.rb +6 -6
- data/lib/async/http/pool.rb +6 -0
- data/lib/async/http/protocol/http11.rb +19 -14
- data/lib/async/http/protocol/http2.rb +96 -57
- data/lib/async/http/protocol/{bad_request.rb → request.rb} +33 -1
- data/lib/async/http/protocol/{request_failed.rb → response.rb} +26 -2
- data/lib/async/http/request.rb +2 -1
- data/lib/async/http/response.rb +2 -1
- data/lib/async/http/server.rb +10 -11
- data/lib/async/http/version.rb +1 -1
- metadata +5 -4
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 46c2f19cb8438e19ef4b02d5a35dc2916c316119682cd022d3eda40f8a0acf55
|
4
|
+
data.tar.gz: 6d9c903d8337af465cb0ce824fada879e83e90063cf8ebf2f99445c4e330b01e
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: ae20e6866313108201bcf25af615131296725836d9bc94b7d816b00abd1416d7ffe098d2614df8c9d285b3bddce965fee0b0cfe9867f1d09b8ef4f4ca517ba8c
|
7
|
+
data.tar.gz: 2c9087a2300ecfed16f8a7977e549511ede35fc83eb283b4f9939219bac7e5f04640bcfe0d061780c1579f5e007eb7e545ac8c2c79504af5efae98e41408b56a
|
data/.travis.yml
CHANGED
data/Rakefile
CHANGED
@@ -21,7 +21,7 @@ task :server do
|
|
21
21
|
require 'async/container/forked'
|
22
22
|
require 'async/http/server'
|
23
23
|
|
24
|
-
server = Async::HTTP::Server.
|
24
|
+
server = Async::HTTP::Server.for(Async::IO::Endpoint.tcp('127.0.0.1', 9294, reuse_port: true), PROTOCOL) do |request|
|
25
25
|
return Async::HTTP::Response[200, {'content-type' => 'text/plain'}, ["Hello World"]]
|
26
26
|
end
|
27
27
|
|
@@ -58,7 +58,7 @@ task :wrk do
|
|
58
58
|
require 'async/http/server'
|
59
59
|
require 'async/container/forked'
|
60
60
|
|
61
|
-
server = Async::HTTP::Server.
|
61
|
+
server = Async::HTTP::Server.for(Async::IO::Endpoint.tcp('127.0.0.1', 9294, reuse_port: true), PROTOCOL) do |request|
|
62
62
|
return Async::HTTP::Response[200, {'content-type' => 'text/plain'}, ["Hello World"]]
|
63
63
|
end
|
64
64
|
|
@@ -89,18 +89,6 @@ module Async
|
|
89
89
|
def inspect
|
90
90
|
"\#<#{self.class} #{@chunks.count} chunks, #{self.length} bytes>"
|
91
91
|
end
|
92
|
-
|
93
|
-
module Reader
|
94
|
-
def read
|
95
|
-
self.body ? self.body.join : nil
|
96
|
-
end
|
97
|
-
|
98
|
-
def finish
|
99
|
-
return if self.body.nil?
|
100
|
-
|
101
|
-
self.body = self.body.close
|
102
|
-
end
|
103
|
-
end
|
104
92
|
end
|
105
93
|
end
|
106
94
|
end
|
data/lib/async/http/body/file.rb
CHANGED
@@ -24,13 +24,19 @@ module Async
|
|
24
24
|
module HTTP
|
25
25
|
module Body
|
26
26
|
class File < Readable
|
27
|
-
|
28
|
-
|
29
|
-
|
27
|
+
BLOCK_SIZE = Async::IO::Stream::BLOCK_SIZE
|
28
|
+
|
29
|
+
def self.open(path, *args)
|
30
|
+
self.new(::File.open(path), *args)
|
31
|
+
end
|
32
|
+
|
33
|
+
def initialize(file, range = nil, block_size: BLOCK_SIZE)
|
34
|
+
@file = file
|
30
35
|
|
31
36
|
@block_size = block_size
|
32
37
|
|
33
38
|
if range
|
39
|
+
@file.seek(range.min)
|
34
40
|
@offset = range.min
|
35
41
|
@length = @remaining = range.size
|
36
42
|
else
|
@@ -39,12 +45,22 @@ module Async
|
|
39
45
|
end
|
40
46
|
end
|
41
47
|
|
48
|
+
attr :offset
|
42
49
|
attr :length
|
43
50
|
|
44
51
|
def empty?
|
45
52
|
@remaining == 0
|
46
53
|
end
|
47
54
|
|
55
|
+
def rewind
|
56
|
+
@file.seek(@offset)
|
57
|
+
end
|
58
|
+
|
59
|
+
def close
|
60
|
+
@file.close
|
61
|
+
@remaining = 0
|
62
|
+
end
|
63
|
+
|
48
64
|
def read
|
49
65
|
if @remaining > 0
|
50
66
|
amount = [@remaining, @block_size].min
|
@@ -60,6 +76,8 @@ module Async
|
|
60
76
|
end
|
61
77
|
|
62
78
|
def join
|
79
|
+
return "" if @remaining == 0
|
80
|
+
|
63
81
|
buffer = @file.read(@remaining)
|
64
82
|
|
65
83
|
@remaining = 0
|
@@ -68,7 +86,7 @@ module Async
|
|
68
86
|
end
|
69
87
|
|
70
88
|
def inspect
|
71
|
-
"\#<#{self.class}
|
89
|
+
"\#<#{self.class} file=#{@file.inspect} offset=#{@offset} remaining=#{@remaining}>"
|
72
90
|
end
|
73
91
|
end
|
74
92
|
end
|
@@ -38,7 +38,9 @@ module Async
|
|
38
38
|
end
|
39
39
|
|
40
40
|
def stop(error)
|
41
|
-
@
|
41
|
+
if @remaining != 0
|
42
|
+
@stream.close
|
43
|
+
end
|
42
44
|
end
|
43
45
|
|
44
46
|
def read
|
@@ -74,6 +76,7 @@ module Async
|
|
74
76
|
end
|
75
77
|
|
76
78
|
def stop(error)
|
79
|
+
# We can't really do anything in this case except close the connection.
|
77
80
|
@stream.close
|
78
81
|
end
|
79
82
|
|
@@ -25,7 +25,7 @@ module Async
|
|
25
25
|
module Body
|
26
26
|
# A generic base class for wrapping body instances. Typically you'd override `#read`.
|
27
27
|
class Readable
|
28
|
-
#
|
28
|
+
# Read all remaining chunks into a buffered body.
|
29
29
|
def close
|
30
30
|
Buffered.for(self)
|
31
31
|
end
|
@@ -0,0 +1,59 @@
|
|
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
|
+
module Async
|
22
|
+
module HTTP
|
23
|
+
module Body
|
24
|
+
module Reader
|
25
|
+
# Read chunks from the body.
|
26
|
+
def each(&block)
|
27
|
+
self.body.each(&block)
|
28
|
+
end
|
29
|
+
|
30
|
+
# Reads the entire request/response body.
|
31
|
+
def read
|
32
|
+
if self.body
|
33
|
+
self.body.join
|
34
|
+
end
|
35
|
+
end
|
36
|
+
|
37
|
+
# Immediately stop reading the body. May close the underlying connection. Discards body.
|
38
|
+
def stop(error = EOFError)
|
39
|
+
if self.body
|
40
|
+
self.body.stop(error)
|
41
|
+
self.body = nil
|
42
|
+
end
|
43
|
+
end
|
44
|
+
|
45
|
+
# Gracefully finish reading the body. This will buffer the remainder of the body.
|
46
|
+
def finish
|
47
|
+
if self.body
|
48
|
+
self.body = self.body.close
|
49
|
+
end
|
50
|
+
end
|
51
|
+
|
52
|
+
# Close the connection as quickly as possible. Discards body.
|
53
|
+
def close
|
54
|
+
self.stop
|
55
|
+
end
|
56
|
+
end
|
57
|
+
end
|
58
|
+
end
|
59
|
+
end
|
@@ -36,13 +36,14 @@ module Async
|
|
36
36
|
@stopped = nil
|
37
37
|
end
|
38
38
|
|
39
|
+
# Has the producer called #finish and has the reader consumed the nil token?
|
39
40
|
def empty?
|
40
41
|
@finished
|
41
42
|
end
|
42
43
|
|
43
44
|
# Read the next available chunk.
|
44
45
|
def read
|
45
|
-
# I'm not sure if this is a good idea
|
46
|
+
# I'm not sure if this is a good idea.
|
46
47
|
# if @stopped
|
47
48
|
# raise @stopped
|
48
49
|
# end
|
@@ -56,9 +57,9 @@ module Async
|
|
56
57
|
return chunk
|
57
58
|
end
|
58
59
|
|
59
|
-
#
|
60
|
+
# Stop generating output; cause the next call to write to fail with the given error.
|
60
61
|
def stop(error)
|
61
|
-
@stopped
|
62
|
+
@stopped ||= error
|
62
63
|
end
|
63
64
|
|
64
65
|
# Write a single chunk to the body. Signal completion by calling `#finish`.
|
@@ -77,7 +78,7 @@ module Async
|
|
77
78
|
|
78
79
|
alias << write
|
79
80
|
|
80
|
-
# Signal that output has finished.
|
81
|
+
# Signal that output has finished. This must be called at least once.
|
81
82
|
def finish
|
82
83
|
@queue.enqueue(nil)
|
83
84
|
end
|
data/lib/async/http/client.rb
CHANGED
@@ -34,13 +34,16 @@ module Async
|
|
34
34
|
@authority = authority || endpoint.hostname
|
35
35
|
|
36
36
|
@retries = retries
|
37
|
-
@
|
37
|
+
@pool = connect(**options)
|
38
38
|
end
|
39
39
|
|
40
40
|
attr :endpoint
|
41
41
|
attr :protocol
|
42
42
|
attr :authority
|
43
43
|
|
44
|
+
attr :retries
|
45
|
+
attr :pool
|
46
|
+
|
44
47
|
def self.open(*args, &block)
|
45
48
|
client = self.new(*args)
|
46
49
|
|
@@ -54,10 +57,10 @@ module Async
|
|
54
57
|
end
|
55
58
|
|
56
59
|
def close
|
57
|
-
@
|
60
|
+
@pool.close
|
58
61
|
end
|
59
62
|
|
60
|
-
include
|
63
|
+
include Methods
|
61
64
|
|
62
65
|
def call(request)
|
63
66
|
request.authority ||= @authority
|
@@ -67,20 +70,20 @@ module Async
|
|
67
70
|
begin
|
68
71
|
attempt += 1
|
69
72
|
|
70
|
-
# As we cache
|
71
|
-
connection = @
|
73
|
+
# As we cache pool, it's possible these pool go bad (e.g. closed by remote host). In this case, we need to try again. It's up to the caller to impose a timeout on this. If this is the last attempt, we force a new connection.
|
74
|
+
connection = @pool.acquire
|
72
75
|
|
73
76
|
response = connection.call(request)
|
74
77
|
|
75
78
|
# The connection won't be released until the body is completely read/released.
|
76
79
|
Body::Streamable.wrap(response) do
|
77
|
-
@
|
80
|
+
@pool.release(connection)
|
78
81
|
end
|
79
82
|
|
80
83
|
return response
|
81
84
|
rescue Protocol::RequestFailed
|
82
85
|
# This is a specific case where the entire request wasn't sent before a failure occurred. So, we can even resend non-idempotent requests.
|
83
|
-
@
|
86
|
+
@pool.release(connection)
|
84
87
|
|
85
88
|
attempt += 1
|
86
89
|
if attempt < @retries
|
@@ -89,7 +92,7 @@ module Async
|
|
89
92
|
raise
|
90
93
|
end
|
91
94
|
rescue
|
92
|
-
@
|
95
|
+
@pool.release(connection)
|
93
96
|
|
94
97
|
if request.idempotent? and attempt < @retries
|
95
98
|
retry
|
@@ -30,7 +30,7 @@ module Async
|
|
30
30
|
|
31
31
|
VERBS = [GET, HEAD, POST, PUT, PATCH, DELETE, CONNECT].freeze
|
32
32
|
|
33
|
-
module
|
33
|
+
module Methods
|
34
34
|
VERBS.each do |verb|
|
35
35
|
define_method(verb.downcase) do |location, headers = {}, body = []|
|
36
36
|
self.call(Request[verb, location.to_str, headers, body])
|
@@ -47,17 +47,17 @@ module Async
|
|
47
47
|
@app.close
|
48
48
|
end
|
49
49
|
|
50
|
-
include
|
50
|
+
include Methods
|
51
51
|
|
52
|
-
def call(
|
53
|
-
@app.call(
|
52
|
+
def call(request)
|
53
|
+
@app.call(request)
|
54
54
|
end
|
55
55
|
|
56
56
|
module Okay
|
57
57
|
def self.close
|
58
58
|
end
|
59
59
|
|
60
|
-
def self.call(request
|
60
|
+
def self.call(request)
|
61
61
|
Response[200, {}, []]
|
62
62
|
end
|
63
63
|
end
|
@@ -66,7 +66,7 @@ module Async
|
|
66
66
|
def self.close
|
67
67
|
end
|
68
68
|
|
69
|
-
def self.call(request
|
69
|
+
def self.call(request)
|
70
70
|
Response[200, {'content-type' => 'text/plain'}, ["Hello World!"]]
|
71
71
|
end
|
72
72
|
end
|
data/lib/async/http/pool.rb
CHANGED
@@ -20,12 +20,8 @@
|
|
20
20
|
|
21
21
|
require 'async/io/protocol/line'
|
22
22
|
|
23
|
-
require_relative '
|
24
|
-
require_relative '
|
25
|
-
|
26
|
-
require_relative '../request'
|
27
|
-
require_relative '../response'
|
28
|
-
require_relative '../headers'
|
23
|
+
require_relative 'request'
|
24
|
+
require_relative 'response'
|
29
25
|
|
30
26
|
require_relative '../body/chunked'
|
31
27
|
require_relative '../body/fixed'
|
@@ -52,6 +48,10 @@ module Async
|
|
52
48
|
@count = 0
|
53
49
|
end
|
54
50
|
|
51
|
+
def peer
|
52
|
+
@stream.io
|
53
|
+
end
|
54
|
+
|
55
55
|
attr :count
|
56
56
|
|
57
57
|
# Only one simultaneous connection at a time.
|
@@ -93,15 +93,13 @@ module Async
|
|
93
93
|
return @stream.io
|
94
94
|
end
|
95
95
|
|
96
|
-
class Request <
|
96
|
+
class Request < Protocol::Request
|
97
97
|
def initialize(protocol)
|
98
98
|
super(*protocol.read_request)
|
99
99
|
|
100
100
|
@protocol = protocol
|
101
101
|
end
|
102
102
|
|
103
|
-
attr :protocol
|
104
|
-
|
105
103
|
def hijack?
|
106
104
|
true
|
107
105
|
end
|
@@ -133,7 +131,7 @@ module Async
|
|
133
131
|
def receive_requests(task: Task.current)
|
134
132
|
while request = next_request
|
135
133
|
if response = yield(request, self)
|
136
|
-
write_response(
|
134
|
+
write_response(self.version, response.status, response.headers, response.body)
|
137
135
|
request.finish
|
138
136
|
|
139
137
|
# This ensures we yield at least once every iteration of the loop and allow other fibers to execute.
|
@@ -144,14 +142,21 @@ module Async
|
|
144
142
|
end
|
145
143
|
end
|
146
144
|
|
145
|
+
class Response < Protocol::Response
|
146
|
+
def initialize(protocol, request)
|
147
|
+
super(*protocol.read_response(request))
|
148
|
+
|
149
|
+
@protocol = protocol
|
150
|
+
end
|
151
|
+
end
|
152
|
+
|
153
|
+
# Used by the client to send requests to the remote server.
|
147
154
|
def call(request)
|
148
|
-
request.version ||= self.version
|
149
|
-
|
150
155
|
Async.logger.debug(self) {"#{request.method} #{request.path} #{request.headers.inspect}"}
|
151
156
|
|
152
157
|
# We carefully interpret https://tools.ietf.org/html/rfc7230#section-6.3.1 to implement this correctly.
|
153
158
|
begin
|
154
|
-
write_request(request.authority, request.method, request.path,
|
159
|
+
write_request(request.authority, request.method, request.path, self.version, request.headers)
|
155
160
|
rescue
|
156
161
|
# If we fail to fully write the request and body, we can retry this request.
|
157
162
|
raise RequestFailed.new
|
@@ -160,7 +165,7 @@ module Async
|
|
160
165
|
# Once we start writing the body, we can't recover if the request fails. That's because the body might be generated dynamically, streaming, etc.
|
161
166
|
write_body(request.body)
|
162
167
|
|
163
|
-
return Response.new(
|
168
|
+
return Response.new(self, request)
|
164
169
|
rescue
|
165
170
|
# This will ensure that #reusable? returns false.
|
166
171
|
@stream.close
|
@@ -18,10 +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 '
|
22
|
-
require_relative '
|
23
|
-
require_relative '../headers'
|
24
|
-
require_relative '../body/writable'
|
21
|
+
require_relative 'request'
|
22
|
+
require_relative 'response'
|
25
23
|
|
26
24
|
require_relative 'http11'
|
27
25
|
|
@@ -79,6 +77,10 @@ module Async
|
|
79
77
|
@count = 0
|
80
78
|
end
|
81
79
|
|
80
|
+
def peer
|
81
|
+
@stream.io
|
82
|
+
end
|
83
|
+
|
82
84
|
attr :count
|
83
85
|
|
84
86
|
# Multiple requests can be processed at the same time.
|
@@ -122,13 +124,18 @@ module Async
|
|
122
124
|
@stream.close
|
123
125
|
end
|
124
126
|
|
125
|
-
class Request <
|
126
|
-
def initialize(stream)
|
127
|
+
class Request < Protocol::Request
|
128
|
+
def initialize(protocol, stream)
|
127
129
|
super(nil, nil, nil, VERSION, Headers.new, Body::Writable.new)
|
128
130
|
|
131
|
+
@protocol = protocol
|
129
132
|
@stream = stream
|
130
133
|
end
|
131
134
|
|
135
|
+
def hijack?
|
136
|
+
false
|
137
|
+
end
|
138
|
+
|
132
139
|
attr :stream
|
133
140
|
|
134
141
|
def assign_headers(headers)
|
@@ -150,16 +157,14 @@ module Async
|
|
150
157
|
end
|
151
158
|
end
|
152
159
|
end
|
153
|
-
|
154
|
-
def hijack?
|
155
|
-
false
|
156
|
-
end
|
157
160
|
end
|
158
161
|
|
159
162
|
def receive_requests(task: Task.current, &block)
|
160
163
|
# emits new streams opened by the client
|
161
164
|
@controller.on(:stream) do |stream|
|
162
|
-
|
165
|
+
@count += 1
|
166
|
+
|
167
|
+
request = Request.new(self, stream)
|
163
168
|
body = request.body
|
164
169
|
|
165
170
|
stream.on(:headers) do |headers|
|
@@ -188,7 +193,12 @@ module Async
|
|
188
193
|
end
|
189
194
|
|
190
195
|
stream.on(:close) do |error|
|
191
|
-
|
196
|
+
if error
|
197
|
+
body.stop(EOFError.new(error))
|
198
|
+
else
|
199
|
+
# In theory, we should have received half_close, so there is no need to:
|
200
|
+
# body.finish
|
201
|
+
end
|
192
202
|
end
|
193
203
|
end
|
194
204
|
|
@@ -231,41 +241,50 @@ module Async
|
|
231
241
|
Async.logger.error(request) {$!}
|
232
242
|
end
|
233
243
|
|
234
|
-
|
235
|
-
|
236
|
-
|
237
|
-
|
238
|
-
|
239
|
-
|
240
|
-
|
241
|
-
SCHEME => HTTPS,
|
242
|
-
METHOD => request.method,
|
243
|
-
PATH => request.path,
|
244
|
-
AUTHORITY => request.authority,
|
245
|
-
}, request.headers)
|
246
|
-
|
247
|
-
finished = Async::Notification.new
|
248
|
-
|
249
|
-
exception = nil
|
250
|
-
response = Response.new
|
251
|
-
response.version = self.version
|
252
|
-
response.headers = Headers.new
|
253
|
-
body = Body::Writable.new
|
254
|
-
response.body = body
|
244
|
+
class Response < Protocol::Response
|
245
|
+
def initialize(protocol, stream)
|
246
|
+
super(self.version, nil, nil, Headers.new, Body::Writable.new)
|
247
|
+
|
248
|
+
@protocol = protocol
|
249
|
+
@stream = stream
|
250
|
+
end
|
255
251
|
|
256
|
-
|
252
|
+
def assign_headers(headers)
|
257
253
|
headers.each do |key, value|
|
258
254
|
if key == STATUS
|
259
|
-
|
255
|
+
@status = value.to_i
|
260
256
|
elsif key == REASON
|
261
|
-
|
257
|
+
@reason = value
|
262
258
|
else
|
263
|
-
|
259
|
+
@headers[key] = value
|
264
260
|
end
|
265
261
|
end
|
266
|
-
|
267
|
-
|
268
|
-
|
262
|
+
end
|
263
|
+
end
|
264
|
+
|
265
|
+
# Used by the client to send requests to the remote server.
|
266
|
+
def call(request)
|
267
|
+
@count += 1
|
268
|
+
|
269
|
+
stream = @controller.new_stream
|
270
|
+
response = Response.new(self, stream)
|
271
|
+
body = response.body
|
272
|
+
|
273
|
+
exception = nil
|
274
|
+
finished = Async::Notification.new
|
275
|
+
waiting = true
|
276
|
+
|
277
|
+
stream.on(:close) do |error|
|
278
|
+
if waiting
|
279
|
+
if error
|
280
|
+
# If the stream was closed due to an error, we will raise it rather than returning normally.
|
281
|
+
exception = EOFError.new(error)
|
282
|
+
end
|
283
|
+
|
284
|
+
waiting = false
|
285
|
+
finished.signal
|
286
|
+
else
|
287
|
+
# At this point, we are now expecting two events: data and close.
|
269
288
|
# If we receive close after this point, it's not a request error, but a failure we need to signal to the body.
|
270
289
|
if error
|
271
290
|
body.stop(EOFError.new(error))
|
@@ -273,22 +292,52 @@ module Async
|
|
273
292
|
body.finish
|
274
293
|
end
|
275
294
|
end
|
295
|
+
end
|
296
|
+
|
297
|
+
stream.on(:headers) do |headers|
|
298
|
+
response.assign_headers(headers)
|
276
299
|
|
300
|
+
# Once we receive the headers, we can return. The body will be read in the background.
|
301
|
+
waiting = false
|
277
302
|
finished.signal
|
278
303
|
end
|
279
304
|
|
305
|
+
# This is a little bit tricky due to the event handlers.
|
306
|
+
# 1/ Caller invokes `response.stop` which causes `body.write` below to fail.
|
307
|
+
# 2/ We invoke `stream.close(:internal_error)` which eventually triggers `on(:close)` above.
|
308
|
+
# 3/ Error is set to :internal_error which causes us to call `body.stop` a 2nd time.
|
309
|
+
# So, we guard against that, by ensuring that `Writable#stop` only stores the first exception assigned to it.
|
280
310
|
stream.on(:data) do |chunk|
|
281
|
-
|
311
|
+
begin
|
312
|
+
# If the body is stopped, write will fail...
|
313
|
+
body.write(chunk.to_s) unless chunk.empty?
|
314
|
+
rescue
|
315
|
+
# ... so, we close the stream:
|
316
|
+
stream.close(:internal_error)
|
317
|
+
end
|
282
318
|
end
|
283
319
|
|
284
|
-
stream
|
285
|
-
|
286
|
-
|
287
|
-
|
288
|
-
|
289
|
-
|
320
|
+
write_request(request, stream)
|
321
|
+
|
322
|
+
Async.logger.debug(self) {"Request sent, waiting for signal."}
|
323
|
+
finished.wait
|
324
|
+
|
325
|
+
if exception
|
326
|
+
raise exception
|
290
327
|
end
|
291
328
|
|
329
|
+
Async.logger.debug(self) {"Stream finished: #{response.inspect}"}
|
330
|
+
return response
|
331
|
+
end
|
332
|
+
|
333
|
+
private def write_request(request, stream)
|
334
|
+
headers = Headers::Merged.new({
|
335
|
+
SCHEME => HTTPS,
|
336
|
+
METHOD => request.method,
|
337
|
+
PATH => request.path,
|
338
|
+
AUTHORITY => request.authority,
|
339
|
+
}, request.headers)
|
340
|
+
|
292
341
|
if request.body.nil? or request.body.empty?
|
293
342
|
stream.headers(headers, end_stream: true)
|
294
343
|
request.body.read if request.body
|
@@ -308,16 +357,6 @@ module Async
|
|
308
357
|
|
309
358
|
start_connection
|
310
359
|
@stream.flush
|
311
|
-
|
312
|
-
Async.logger.debug(self) {"Stream flushed, waiting for signal."}
|
313
|
-
finished.wait
|
314
|
-
|
315
|
-
if exception
|
316
|
-
raise exception
|
317
|
-
end
|
318
|
-
|
319
|
-
Async.logger.debug(self) {"Stream finished: #{response.inspect}"}
|
320
|
-
return response
|
321
360
|
end
|
322
361
|
end
|
323
362
|
end
|
@@ -1,4 +1,4 @@
|
|
1
|
-
# Copyright,
|
1
|
+
# Copyright, 2017, by Samuel G. D. Williams. <http://www.codeotaku.com>
|
2
2
|
#
|
3
3
|
# Permission is hereby granted, free of charge, to any person obtaining a copy
|
4
4
|
# of this software and associated documentation files (the "Software"), to deal
|
@@ -18,12 +18,44 @@
|
|
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 '../request'
|
22
|
+
require_relative '../headers'
|
23
|
+
|
24
|
+
require_relative '../body/writable'
|
25
|
+
|
21
26
|
module Async
|
22
27
|
module HTTP
|
23
28
|
module Protocol
|
29
|
+
# Failed to send the request. The request body has NOT been consumed (i.e. #read) and you should retry the request.
|
30
|
+
class RequestFailed < StandardError
|
31
|
+
end
|
32
|
+
|
24
33
|
# The request was invalid/malformed in some way.
|
25
34
|
class BadRequest < StandardError
|
26
35
|
end
|
36
|
+
|
37
|
+
# This is generated by server protocols.
|
38
|
+
class Request < HTTP::Request
|
39
|
+
attr :protocol
|
40
|
+
|
41
|
+
def hijack?
|
42
|
+
false
|
43
|
+
end
|
44
|
+
|
45
|
+
def peer
|
46
|
+
if @protocol
|
47
|
+
@protocol.peer
|
48
|
+
end
|
49
|
+
end
|
50
|
+
|
51
|
+
def remote_address
|
52
|
+
@remote_address ||= peer.remote_address
|
53
|
+
end
|
54
|
+
|
55
|
+
def remote_address= value
|
56
|
+
@remote_address = value
|
57
|
+
end
|
58
|
+
end
|
27
59
|
end
|
28
60
|
end
|
29
61
|
end
|
@@ -18,11 +18,35 @@
|
|
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 '../response'
|
22
|
+
require_relative '../headers'
|
23
|
+
|
24
|
+
require_relative '../body/writable'
|
25
|
+
|
21
26
|
module Async
|
22
27
|
module HTTP
|
23
28
|
module Protocol
|
24
|
-
#
|
25
|
-
class
|
29
|
+
# This is generated by client protocols.
|
30
|
+
class Response < HTTP::Response
|
31
|
+
attr :protocol
|
32
|
+
|
33
|
+
def hijack?
|
34
|
+
false
|
35
|
+
end
|
36
|
+
|
37
|
+
def peer
|
38
|
+
if @protocol
|
39
|
+
@protocol.peer
|
40
|
+
end
|
41
|
+
end
|
42
|
+
|
43
|
+
def remote_address
|
44
|
+
@remote_address ||= peer.remote_address
|
45
|
+
end
|
46
|
+
|
47
|
+
def remote_address= value
|
48
|
+
@remote_address = value
|
49
|
+
end
|
26
50
|
end
|
27
51
|
end
|
28
52
|
end
|
data/lib/async/http/request.rb
CHANGED
@@ -19,12 +19,13 @@
|
|
19
19
|
# THE SOFTWARE.
|
20
20
|
|
21
21
|
require_relative 'body/buffered'
|
22
|
+
require_relative 'body/reader'
|
22
23
|
require_relative 'middleware'
|
23
24
|
|
24
25
|
module Async
|
25
26
|
module HTTP
|
26
27
|
class Request
|
27
|
-
prepend Body::
|
28
|
+
prepend Body::Reader
|
28
29
|
|
29
30
|
def initialize(authority = nil, method = nil, path = nil, version = nil, headers = [], body = nil)
|
30
31
|
@authority = authority
|
data/lib/async/http/response.rb
CHANGED
@@ -19,11 +19,12 @@
|
|
19
19
|
# THE SOFTWARE.
|
20
20
|
|
21
21
|
require_relative 'body/buffered'
|
22
|
+
require_relative 'body/reader'
|
22
23
|
|
23
24
|
module Async
|
24
25
|
module HTTP
|
25
26
|
class Response
|
26
|
-
prepend Body::
|
27
|
+
prepend Body::Reader
|
27
28
|
|
28
29
|
def initialize(version = nil, status = 200, reason = nil, headers = [], body = nil)
|
29
30
|
@version = version
|
data/lib/async/http/server.rb
CHANGED
@@ -26,18 +26,16 @@ require_relative 'response'
|
|
26
26
|
|
27
27
|
module Async
|
28
28
|
module HTTP
|
29
|
-
class Server
|
30
|
-
def
|
31
|
-
|
32
|
-
@protocol_class = protocol_class || endpoint.protocol
|
33
|
-
|
34
|
-
if block_given?
|
35
|
-
define_singleton_method(:handle_request, block)
|
36
|
-
end
|
29
|
+
class Server < Middleware
|
30
|
+
def self.for(*args, &block)
|
31
|
+
self.new(block, *args)
|
37
32
|
end
|
38
33
|
|
39
|
-
def
|
40
|
-
|
34
|
+
def initialize(app, endpoint, protocol_class = Protocol::HTTP1)
|
35
|
+
super(app)
|
36
|
+
|
37
|
+
@endpoint = endpoint
|
38
|
+
@protocol_class = protocol_class || endpoint.protocol
|
41
39
|
end
|
42
40
|
|
43
41
|
def accept(peer, address, task: Task.current)
|
@@ -49,10 +47,11 @@ module Async
|
|
49
47
|
Async.logger.debug(self) {"Incoming connnection from #{address.inspect} to #{protocol}"}
|
50
48
|
|
51
49
|
protocol.receive_requests do |request|
|
50
|
+
request.remote_address = address
|
52
51
|
# Async.logger.debug(self) {"Incoming request from #{address.inspect}: #{request.method} #{request.path}"}
|
53
52
|
|
54
53
|
# If this returns nil, we assume that the connection has been hijacked.
|
55
|
-
|
54
|
+
self.call(request)
|
56
55
|
end
|
57
56
|
rescue EOFError, Errno::ECONNRESET, Errno::EPIPE
|
58
57
|
# Sometimes client will disconnect without completing a result or reading the entire buffer. That means we are done.
|
data/lib/async/http/version.rb
CHANGED
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: async-http
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 0.27.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-06-
|
11
|
+
date: 2018-06-27 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: async
|
@@ -146,6 +146,7 @@ files:
|
|
146
146
|
- lib/async/http/body/fixed.rb
|
147
147
|
- lib/async/http/body/inflate.rb
|
148
148
|
- lib/async/http/body/readable.rb
|
149
|
+
- lib/async/http/body/reader.rb
|
149
150
|
- lib/async/http/body/rewindable.rb
|
150
151
|
- lib/async/http/body/streamable.rb
|
151
152
|
- lib/async/http/body/wrapper.rb
|
@@ -157,13 +158,13 @@ files:
|
|
157
158
|
- lib/async/http/middleware/builder.rb
|
158
159
|
- lib/async/http/pool.rb
|
159
160
|
- lib/async/http/protocol.rb
|
160
|
-
- lib/async/http/protocol/bad_request.rb
|
161
161
|
- lib/async/http/protocol/http1.rb
|
162
162
|
- lib/async/http/protocol/http10.rb
|
163
163
|
- lib/async/http/protocol/http11.rb
|
164
164
|
- lib/async/http/protocol/http2.rb
|
165
165
|
- lib/async/http/protocol/https.rb
|
166
|
-
- lib/async/http/protocol/
|
166
|
+
- lib/async/http/protocol/request.rb
|
167
|
+
- lib/async/http/protocol/response.rb
|
167
168
|
- lib/async/http/reference.rb
|
168
169
|
- lib/async/http/relative_location.rb
|
169
170
|
- lib/async/http/request.rb
|