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 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