async-http 0.26.0 → 0.27.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: f41c8e5613af9f0522c24461c31c8cdc410ca04f04fecd3b1e727c716e245f1c
4
- data.tar.gz: 965be2b71c176eb7f11c05b182551be39bc17371997b50ea1ca3e8a9745934ec
3
+ metadata.gz: 46c2f19cb8438e19ef4b02d5a35dc2916c316119682cd022d3eda40f8a0acf55
4
+ data.tar.gz: 6d9c903d8337af465cb0ce824fada879e83e90063cf8ebf2f99445c4e330b01e
5
5
  SHA512:
6
- metadata.gz: c080e937e8daf6914b9b9f0b37ef16f310fde136f9d834b4a7be11f9a13239927c8ec9e6ec51143b5b27e4f4141a0a9859c321a4d252c60caf3ea0e827537857
7
- data.tar.gz: ab34e94fbd6ce5e5e73d8509dd506c3044c95f30d9f37e68491b4426c047a94cab5265c912bca6b5be662bbf2cc01c06f3d1639900d655c98360087685f5a98e
6
+ metadata.gz: ae20e6866313108201bcf25af615131296725836d9bc94b7d816b00abd1416d7ffe098d2614df8c9d285b3bddce965fee0b0cfe9867f1d09b8ef4f4ca517ba8c
7
+ data.tar.gz: 2c9087a2300ecfed16f8a7977e549511ede35fc83eb283b4f9939219bac7e5f04640bcfe0d061780c1579f5e007eb7e545ac8c2c79504af5efae98e41408b56a
data/.travis.yml CHANGED
@@ -18,6 +18,7 @@ matrix:
18
18
  - rvm: 2.3
19
19
  - rvm: 2.4
20
20
  - rvm: 2.5
21
+ - rvm: 2.6
21
22
  - rvm: jruby-head
22
23
  env: JRUBY_OPTS="--debug -X+O"
23
24
  - rvm: ruby-head
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.new(Async::IO::Endpoint.tcp('127.0.0.1', 9294, reuse_port: true), PROTOCOL) do |request, peer, address|
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.new(Async::IO::Endpoint.tcp('127.0.0.1', 9294, reuse_port: true), PROTOCOL) do |request, peer, address|
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
 
@@ -38,7 +38,7 @@ module Async
38
38
  @wrappers = wrappers
39
39
  end
40
40
 
41
- def call(request, *)
41
+ def call(request)
42
42
  request.headers['accept-encoding'] = @accept_encoding
43
43
 
44
44
  response = super
@@ -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
@@ -24,13 +24,19 @@ module Async
24
24
  module HTTP
25
25
  module Body
26
26
  class File < Readable
27
- def initialize(path, range = nil, block_size: Async::IO::Stream::BLOCK_SIZE)
28
- @path = path
29
- @file = File.open(path)
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} path=#{@path} offset=#{@offset} remaining=#{@remaining}>"
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
- @stream.close
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
- # Buffer any remaining body.
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
@@ -39,6 +39,12 @@ module Async
39
39
  @callback = callback
40
40
  end
41
41
 
42
+ def stop(*)
43
+ super
44
+
45
+ @callback.call
46
+ end
47
+
42
48
  def read
43
49
  unless chunk = super
44
50
  @callback.call
@@ -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
- # Cause the next call to write to fail with the given error.
60
+ # Stop generating output; cause the next call to write to fail with the given error.
60
61
  def stop(error)
61
- @stopped = error
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
@@ -34,13 +34,16 @@ module Async
34
34
  @authority = authority || endpoint.hostname
35
35
 
36
36
  @retries = retries
37
- @connections = connect(**options)
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
- @connections.close
60
+ @pool.close
58
61
  end
59
62
 
60
- include Verbs
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 connections, it's possible these connections 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.
71
- connection = @connections.acquire
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
- @connections.release(connection)
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
- @connections.release(connection)
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
- @connections.release(connection)
95
+ @pool.release(connection)
93
96
 
94
97
  if request.idempotent? and attempt < @retries
95
98
  retry
@@ -40,7 +40,7 @@ module Async
40
40
  @wrappers = wrappers
41
41
  end
42
42
 
43
- def call(request, *)
43
+ def call(request)
44
44
  response = super
45
45
 
46
46
  # TODO use http-accept and sort by priority
@@ -30,7 +30,7 @@ module Async
30
30
 
31
31
  VERBS = [GET, HEAD, POST, PUT, PATCH, DELETE, CONNECT].freeze
32
32
 
33
- module Verbs
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 Verbs
50
+ include Methods
51
51
 
52
- def call(*args)
53
- @app.call(*args)
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
@@ -44,6 +44,12 @@ module Async
44
44
  @constructor = block
45
45
  end
46
46
 
47
+ attr :available
48
+
49
+ def empty?
50
+ @available.empty?
51
+ end
52
+
47
53
  def acquire
48
54
  resource = wait_for_next_available
49
55
 
@@ -20,12 +20,8 @@
20
20
 
21
21
  require 'async/io/protocol/line'
22
22
 
23
- require_relative 'request_failed'
24
- require_relative 'bad_request'
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 < HTTP::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(response.version || self.version, response.status, response.headers, response.body)
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, request.version, request.headers)
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(*read_response(request))
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 '../request'
22
- require_relative '../response'
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 < HTTP::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
- request = Request.new(stream)
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
- body.stop(EOFError.new(error)) if error
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
- def call(request)
235
- request.version ||= self.version
236
-
237
- stream = @controller.new_stream
238
- @count += 1
239
-
240
- headers = Headers::Merged.new({
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
- stream.on(:headers) do |headers|
252
+ def assign_headers(headers)
257
253
  headers.each do |key, value|
258
254
  if key == STATUS
259
- response.status = value.to_i
255
+ @status = value.to_i
260
256
  elsif key == REASON
261
- response.reason = value
257
+ @reason = value
262
258
  else
263
- response.headers[key] = value
259
+ @headers[key] = value
264
260
  end
265
261
  end
266
-
267
- # At this point, we are now expecting two events: data and close.
268
- stream.on(:close) do |error|
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
- body.write(chunk.to_s) unless chunk.empty?
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.on(:close) do |error|
285
- # The remote server has closed the connection while we were sending the request.
286
- if error
287
- exception = EOFError.new(error)
288
- finished.signal
289
- end
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, 2018, by Samuel G. D. Williams. <http://www.codeotaku.com>
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
- # Failed to send the request. The request body has NOT been consumed (i.e. #read) and you should retry the request.
25
- class RequestFailed < StandardError
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
@@ -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::Buffered::Reader
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
@@ -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::Buffered::Reader
27
+ prepend Body::Reader
27
28
 
28
29
  def initialize(version = nil, status = 200, reason = nil, headers = [], body = nil)
29
30
  @version = version
@@ -26,18 +26,16 @@ require_relative 'response'
26
26
 
27
27
  module Async
28
28
  module HTTP
29
- class Server
30
- def initialize(endpoint, protocol_class = Protocol::HTTP1, &block)
31
- @endpoint = endpoint
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 handle_request(request, peer, address)
40
- Response[200, {}, []]
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
- handle_request(request, peer, address)
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.
@@ -20,6 +20,6 @@
20
20
 
21
21
  module Async
22
22
  module HTTP
23
- VERSION = "0.26.0"
23
+ VERSION = "0.27.0"
24
24
  end
25
25
  end
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.26.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-26 00:00:00.000000000 Z
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/request_failed.rb
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