async-http 0.40.3 → 0.41.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.
Files changed (55) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +4 -4
  3. data/async-http.gemspec +3 -3
  4. data/examples/request.rb +3 -3
  5. data/examples/upload/client.rb +2 -2
  6. data/examples/upload/server.rb +3 -3
  7. data/lib/async/http.rb +1 -1
  8. data/lib/async/http/body.rb +2 -11
  9. data/lib/async/http/body/hijack.rb +3 -3
  10. data/lib/async/http/body/writable.rb +3 -2
  11. data/lib/async/http/client.rb +11 -10
  12. data/lib/async/http/{url_endpoint.rb → endpoint.rb} +1 -1
  13. data/lib/async/http/internet.rb +6 -6
  14. data/lib/async/http/pool.rb +1 -1
  15. data/lib/async/http/protocol/http1/client.rb +19 -8
  16. data/lib/async/http/protocol/http1/connection.rb +8 -25
  17. data/lib/async/http/protocol/http1/request.rb +10 -7
  18. data/lib/async/http/protocol/http1/response.rb +19 -3
  19. data/lib/async/http/protocol/http1/server.rb +14 -2
  20. data/lib/async/http/protocol/http10.rb +4 -0
  21. data/lib/async/http/protocol/http11.rb +4 -0
  22. data/lib/async/http/protocol/http2.rb +8 -0
  23. data/lib/async/http/protocol/http2/connection.rb +11 -2
  24. data/lib/async/http/protocol/http2/promise.rb +2 -2
  25. data/lib/async/http/protocol/http2/request.rb +17 -9
  26. data/lib/async/http/protocol/http2/response.rb +12 -6
  27. data/lib/async/http/protocol/http2/server.rb +12 -10
  28. data/lib/async/http/protocol/http2/stream.rb +1 -1
  29. data/lib/async/http/protocol/request.rb +6 -6
  30. data/lib/async/http/protocol/response.rb +5 -6
  31. data/lib/async/http/relative_location.rb +5 -4
  32. data/lib/async/http/server.rb +7 -5
  33. data/lib/async/http/statistics.rb +2 -2
  34. data/lib/async/http/version.rb +1 -1
  35. data/tasks/server.rake +4 -4
  36. metadata +10 -29
  37. data/lib/async/http/accept_encoding.rb +0 -65
  38. data/lib/async/http/body/buffered.rb +0 -89
  39. data/lib/async/http/body/chunked.rb +0 -76
  40. data/lib/async/http/body/deflate.rb +0 -113
  41. data/lib/async/http/body/file.rb +0 -98
  42. data/lib/async/http/body/fixed.rb +0 -72
  43. data/lib/async/http/body/inflate.rb +0 -59
  44. data/lib/async/http/body/readable.rb +0 -76
  45. data/lib/async/http/body/reader.rb +0 -83
  46. data/lib/async/http/body/remainder.rb +0 -56
  47. data/lib/async/http/body/rewindable.rb +0 -60
  48. data/lib/async/http/body/streamable.rb +0 -83
  49. data/lib/async/http/body/wrapper.rb +0 -65
  50. data/lib/async/http/content_encoding.rb +0 -76
  51. data/lib/async/http/headers.rb +0 -27
  52. data/lib/async/http/middleware.rb +0 -79
  53. data/lib/async/http/middleware/builder.rb +0 -61
  54. data/lib/async/http/request.rb +0 -71
  55. data/lib/async/http/response.rb +0 -90
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 9467ea667ebfd156ef91b71cfa8da9598d73839cc378545672aa4db3338f80f9
4
- data.tar.gz: 03e93ca6e98b1e22f58cde6542e44eba52f6193d779492dfba1be835c2f31d02
3
+ metadata.gz: 35e99a53d484c9c4e0ee98db95695ea2a238dc71e03c958425c37f4a2c6560dd
4
+ data.tar.gz: 7a6bcefcc687b98652d0fa1b23d234f6223ca8c5dd80fd88c44624883e871322
5
5
  SHA512:
6
- metadata.gz: 9890cd89c5e1148863714c1f517f3ad7edd62cefb6a7f513580a32649a055b67929b7e7c26b8440b00667b42854a9ccb4e8275df17bacbed6b9e73721e2f61e7
7
- data.tar.gz: 7cc0038e3cd139cda370e485d29719b611224a156928de22de597293108b399057809b43c813d7b171cadc2ea926cb0e0760d6cd267bcf67d1bc5e9ac1290060
6
+ metadata.gz: 16b58d9b615e7ddb4fe12cda38dd48c3f4a598e27e4175cc14063faad08a8aab42e75e66ff8e7d47207af882f03fe5339b11ac28d281e02f91ca6278da77a3d5
7
+ data.tar.gz: 388048425c3444cc34d69712be5d33e6854023be0339a91b2b1f748a74e703339beb5c03de90cdaa693af51d3aa512b6591b57a161c94e6151dbbb327c7fc349
data/README.md CHANGED
@@ -97,13 +97,13 @@ Here is a basic example of a client/server running in the same reactor:
97
97
  require 'async/http/server'
98
98
  require 'async/http/client'
99
99
  require 'async/reactor'
100
- require 'async/http/url_endpoint'
100
+ require 'async/http/endpoint'
101
101
  require 'async/http/response'
102
102
 
103
- endpoint = Async::HTTP::URLEndpoint.parse('http://127.0.0.1:9294')
103
+ endpoint = Async::HTTP::Endpoint.parse('http://127.0.0.1:9294')
104
104
 
105
105
  app = lambda do |request|
106
- Async::HTTP::Response[200, {}, ["Hello World"]]
106
+ Protocol::HTTP::Response[200, {}, ["Hello World"]]
107
107
  end
108
108
 
109
109
  server = Async::HTTP::Server.new(app, endpoint)
@@ -140,7 +140,7 @@ trusted_fingerprints = {
140
140
  }
141
141
 
142
142
  Async do
143
- endpoint = Async::HTTP::URLEndpoint.parse("https://www.codeotaku.com/index")
143
+ endpoint = Async::HTTP::Endpoint.parse("https://www.codeotaku.com/index")
144
144
 
145
145
  # This is a quick hack/POC:
146
146
  ssl_context = endpoint.ssl_context
data/async-http.gemspec CHANGED
@@ -19,9 +19,9 @@ Gem::Specification.new do |spec|
19
19
  spec.add_dependency("async", "~> 1.14")
20
20
  spec.add_dependency("async-io", "~> 1.18")
21
21
 
22
- spec.add_dependency("protocol-http", "~> 0.3.0")
23
- spec.add_dependency("protocol-http1", "~> 0.5.0")
24
- spec.add_dependency("protocol-http2", "~> 0.2.0")
22
+ spec.add_dependency("protocol-http", "~> 0.5.0")
23
+ spec.add_dependency("protocol-http1", "~> 0.6.0")
24
+ spec.add_dependency("protocol-http2", "~> 0.3.0")
25
25
 
26
26
  # spec.add_dependency("openssl")
27
27
 
data/examples/request.rb CHANGED
@@ -6,12 +6,12 @@
6
6
  require 'async'
7
7
  require 'async/logger'
8
8
  require 'async/http/client'
9
- require 'async/http/url_endpoint'
9
+ require 'async/http/endpoint'
10
10
 
11
11
  # Async.logger.level = Logger::DEBUG
12
12
 
13
13
  Async.run do |task|
14
- endpoint = Async::HTTP::URLEndpoint.parse("https://www.google.com")
14
+ endpoint = Async::HTTP::Endpoint.parse("https://www.google.com")
15
15
 
16
16
  client = Async::HTTP::Client.new(endpoint)
17
17
 
@@ -19,7 +19,7 @@ Async.run do |task|
19
19
  'accept' => 'text/html',
20
20
  }
21
21
 
22
- request = Async::HTTP::Request.new(client.scheme, "www.google.com", "GET", "/search?q=cats", headers)
22
+ request = Protocol::HTTP::Request.new(client.scheme, "www.google.com", "GET", "/search?q=cats", headers)
23
23
 
24
24
  puts "Sending request..."
25
25
  response = client.call(request)
@@ -6,10 +6,10 @@ require 'async'
6
6
  require 'async/http/body/file'
7
7
  require 'async/http/body/delayed'
8
8
  require 'async/http/client'
9
- require 'async/http/url_endpoint'
9
+ require 'async/http/endpoint'
10
10
 
11
11
  Async.run do
12
- endpoint = Async::HTTP::URLEndpoint.parse("http://localhost:9222")
12
+ endpoint = Async::HTTP::Endpoint.parse("http://localhost:9222")
13
13
  client = Async::HTTP::Client.new(endpoint, Async::HTTP::Protocol::HTTP2)
14
14
 
15
15
  headers = [
@@ -3,16 +3,16 @@ $LOAD_PATH.unshift File.expand_path("../../lib", __dir__)
3
3
 
4
4
  require 'async'
5
5
  require 'async/http/server'
6
- require 'async/http/url_endpoint'
6
+ require 'async/http/endpoint'
7
7
 
8
8
  protocol = Async::HTTP::Protocol::HTTP2
9
- endpoint = Async::HTTP::URLEndpoint.parse('http://127.0.0.1:9222', reuse_port: true)
9
+ endpoint = Async::HTTP::Endpoint.parse('http://127.0.0.1:9222', reuse_port: true)
10
10
 
11
11
  Async.logger.level = Logger::DEBUG
12
12
 
13
13
  Async.run do
14
14
  server = Async::HTTP::Server.for(endpoint, protocol) do |request|
15
- Async::HTTP::Response[200, {}, request.body]
15
+ Protocol::HTTP::Response[200, {}, request.body]
16
16
  end
17
17
 
18
18
  server.run
data/lib/async/http.rb CHANGED
@@ -23,4 +23,4 @@ require_relative 'http/version'
23
23
  require_relative 'http/client'
24
24
  require_relative 'http/server'
25
25
 
26
- require_relative 'http/url_endpoint'
26
+ require_relative 'http/endpoint'
@@ -18,22 +18,13 @@
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 'protocol/http/body/buffered'
21
22
  require_relative 'body/writable'
22
- require_relative 'body/buffered'
23
23
 
24
24
  module Async
25
25
  module HTTP
26
- # These classes implement a general IO model for streaming HTTP bodies.
27
26
  module Body
28
- # The implementation assumes a sequential unbuffered stream of data.
29
- # class Body
30
- # def each -> yield(String | nil)
31
- # def read -> String | nil
32
- # def join -> String
33
-
34
- # def finish -> buffer the stream and close it.
35
- # def close(error = nil) -> close the stream immediately.
36
- # end
27
+ include ::Protocol::HTTP::Body
37
28
  end
38
29
  end
39
30
  end
@@ -18,16 +18,16 @@
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 'readable'
21
+ require 'protocol/http/body/readable'
22
22
  require_relative 'stream'
23
23
 
24
24
  module Async
25
25
  module HTTP
26
26
  module Body
27
27
  # A body which is designed for hijacked connections.
28
- class Hijack < Readable
28
+ class Hijack < ::Protocol::HTTP::Body::Readable
29
29
  def self.response(request, status, headers, &block)
30
- Async::HTTP::Response[status, headers, self.wrap(request, &block)]
30
+ ::Protocol::HTTP::Response[status, headers, self.wrap(request, &block)]
31
31
  end
32
32
 
33
33
  def self.wrap(request, &block)
@@ -18,13 +18,14 @@
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 'readable'
22
-
21
+ require 'protocol/http/body/readable'
23
22
  require 'async/queue'
24
23
 
25
24
  module Async
26
25
  module HTTP
27
26
  module Body
27
+ include ::Protocol::HTTP::Body
28
+
28
29
  # A dynamic body which you can write to and read from.
29
30
  class Writable < Readable
30
31
  class Closed < StandardError
@@ -21,13 +21,14 @@
21
21
  require 'async/io/endpoint'
22
22
  require 'async/io/stream'
23
23
 
24
+ require 'protocol/http/body/streamable'
25
+ require 'protocol/http/methods'
26
+
24
27
  require_relative 'protocol'
25
- require_relative 'body/streamable'
26
- require_relative 'middleware'
27
28
 
28
29
  module Async
29
30
  module HTTP
30
- class Client
31
+ class Client < ::Protocol::HTTP::Methods
31
32
  # Provides a robust interface to a server.
32
33
  # * If there are no connections, it will create one.
33
34
  # * If there are already connections, it will reuse it.
@@ -53,6 +54,10 @@ module Async
53
54
  attr :scheme
54
55
  attr :authority
55
56
 
57
+ def secure?
58
+ @endpoint.secure?
59
+ end
60
+
56
61
  def self.open(*args, &block)
57
62
  client = self.new(*args)
58
63
 
@@ -69,8 +74,6 @@ module Async
69
74
  @pool.close
70
75
  end
71
76
 
72
- include Methods
73
-
74
77
  def call(request)
75
78
  request.scheme ||= self.scheme
76
79
  request.authority ||= self.authority
@@ -84,10 +87,10 @@ module Async
84
87
  # 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.
85
88
  connection = @pool.acquire
86
89
 
87
- response = connection.call(request)
90
+ response = request.call(connection)
88
91
 
89
92
  # The connection won't be released until the body is completely read/released.
90
- Body::Streamable.wrap(response) do
93
+ ::Protocol::HTTP::Body::Streamable.wrap(response) do
91
94
  @pool.release(connection)
92
95
  end
93
96
 
@@ -118,9 +121,7 @@ module Async
118
121
  Pool.new(connection_limit) do
119
122
  Async.logger.debug(self) {"Making connection to #{@endpoint.inspect}"}
120
123
 
121
- peer = @endpoint.connect
122
-
123
- @protocol.client(IO::Stream.new(peer))
124
+ @protocol.client(IO::Stream.new(@endpoint.connect))
124
125
  end
125
126
  end
126
127
  end
@@ -27,7 +27,7 @@ require_relative 'protocol/https'
27
27
 
28
28
  module Async
29
29
  module HTTP
30
- class URLEndpoint < Async::IO::Endpoint
30
+ class Endpoint < Async::IO::Endpoint
31
31
  def self.parse(string, **options)
32
32
  url = URI.parse(string).normalize
33
33
 
@@ -19,9 +19,9 @@
19
19
  # THE SOFTWARE.
20
20
 
21
21
  require_relative 'client'
22
- require_relative 'url_endpoint'
23
- require_relative 'middleware'
24
- require_relative 'body/buffered'
22
+ require_relative 'endpoint'
23
+ require 'protocol/http/middleware'
24
+ require 'protocol/http/body/buffered'
25
25
 
26
26
  module Async
27
27
  module HTTP
@@ -31,7 +31,7 @@ module Async
31
31
  end
32
32
 
33
33
  def call(method, url, headers = [], body = nil)
34
- endpoint = URLEndpoint.parse(url)
34
+ endpoint = Endpoint.parse(url)
35
35
 
36
36
  client = @clients.fetch(endpoint) do
37
37
  @clients[endpoint] = Client.new(endpoint)
@@ -39,7 +39,7 @@ module Async
39
39
 
40
40
  body = Body::Buffered.wrap(body)
41
41
 
42
- request = Request.new(client.scheme, endpoint.authority, method, endpoint.path, nil, headers, body)
42
+ request = ::Protocol::HTTP::Request.new(client.scheme, endpoint.authority, method, endpoint.path, nil, headers, body)
43
43
 
44
44
  return client.call(request)
45
45
  end
@@ -49,7 +49,7 @@ module Async
49
49
  @clients.clear
50
50
  end
51
51
 
52
- VERBS.each do |verb|
52
+ ::Protocol::HTTP::Methods.each do |name, verb|
53
53
  define_method(verb.downcase) do |url, headers = [], body = nil|
54
54
  self.call(verb, url.to_str, headers, body)
55
55
  end
@@ -97,7 +97,7 @@ module Async
97
97
  end
98
98
 
99
99
  def to_s
100
- "\#<#{self.class} resources=#{availability_string} limit=#{@limit}>"
100
+ "\#<#{self.class} resources=#{availability_string} limit=#{@limit.inspect}>"
101
101
  end
102
102
 
103
103
  protected
@@ -31,23 +31,34 @@ module Async
31
31
 
32
32
  # We carefully interpret https://tools.ietf.org/html/rfc7230#section-6.3.1 to implement this correctly.
33
33
  begin
34
- self.write_request(request.authority, request.method, request.path, self.version, request.headers)
34
+ write_request(request.authority, request.method, request.path, @version, request.headers)
35
35
  rescue
36
36
  # If we fail to fully write the request and body, we can retry this request.
37
- raise RequestFailed.new
37
+ raise RequestFailed
38
38
  end
39
39
 
40
40
  if request.body?
41
- task.async do
42
- # 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.
43
- self.write_body(request.body)
41
+ body = request.body
42
+
43
+ if protocol = request.protocol
44
+ # This is a very tricky apect of handling HTTP/1 upgrade connections. In theory, this approach is a bit inefficient, because we spin up a task just to handle writing to the underlying stream when we could be writing to the stream directly. But we need to maintain some level of compatibility with HTTP/2. Additionally, we don't know if the upgrade request will be accepted, so starting to write the body at this point needs to be handled with care.
45
+ task.async do
46
+ # If this fails, this connection will be closed.
47
+ write_upgrade_body(protocol, body)
48
+ end
49
+ else
50
+ task.async do
51
+ # 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.
52
+ write_body(@version, body)
53
+ end
44
54
  end
55
+ elsif protocol = request.protocol
56
+ write_upgrade_body(protocol)
45
57
  else
46
- self.write_empty_body(request.body)
58
+ write_empty_body(request.body)
47
59
  end
48
60
 
49
- # This won't return the response until the entire body is written.
50
- return Response.new(self, request)
61
+ return Response.read(self, request)
51
62
  rescue
52
63
  # This will ensure that #reusable? returns false.
53
64
  @stream.close
@@ -23,10 +23,6 @@ require 'protocol/http1'
23
23
  require_relative 'request'
24
24
  require_relative 'response'
25
25
 
26
- require_relative '../../body/chunked'
27
- require_relative '../../body/fixed'
28
- require_relative '../../body/remainder'
29
-
30
26
  module Async
31
27
  module HTTP
32
28
  module Protocol
@@ -40,17 +36,16 @@ module Async
40
36
 
41
37
  attr :version
42
38
 
43
- def read_line?
44
- @stream.read_until(CRLF)
39
+ def http1?
40
+ true
45
41
  end
46
42
 
47
- # @return [Async::Wrapper] the underlying non-blocking IO.
48
- def hijack!
49
- @persistent = false
50
-
51
- @stream.flush
52
-
53
- return @stream.io
43
+ def http2?
44
+ false
45
+ end
46
+
47
+ def read_line?
48
+ @stream.read_until(CRLF)
54
49
  end
55
50
 
56
51
  def peer
@@ -78,18 +73,6 @@ module Async
78
73
 
79
74
  @stream.close
80
75
  end
81
-
82
- def read_chunked_body
83
- Body::Chunked.new(self)
84
- end
85
-
86
- def read_fixed_body(length)
87
- Body::Fixed.new(@stream, length)
88
- end
89
-
90
- def read_remainder_body
91
- Body::Remainder.new(@stream)
92
- end
93
76
  end
94
77
  end
95
78
  end
@@ -25,15 +25,18 @@ module Async
25
25
  module Protocol
26
26
  module HTTP1
27
27
  class Request < Protocol::Request
28
- def self.read(protocol)
29
- if parts = protocol.read_request
30
- self.new(protocol, *parts)
28
+ def self.read(connection)
29
+ if parts = connection.read_request
30
+ self.new(connection, *parts)
31
31
  end
32
32
  end
33
33
 
34
- def initialize(protocol, *parts)
35
- super(nil, *parts)
36
- @protocol = protocol
34
+ def initialize(connection, authority, method, path, version, headers, body)
35
+ @connection = connection
36
+
37
+ protocol = connection.upgrade?(headers)
38
+
39
+ super(nil, authority, method, path, version, headers, body, protocol)
37
40
  end
38
41
 
39
42
  def hijack?
@@ -41,7 +44,7 @@ module Async
41
44
  end
42
45
 
43
46
  def hijack!
44
- @protocol.hijack!
47
+ @connection.hijack!
45
48
  end
46
49
  end
47
50
  end
@@ -25,10 +25,26 @@ module Async
25
25
  module Protocol
26
26
  module HTTP1
27
27
  class Response < Protocol::Response
28
- def initialize(protocol, request)
29
- super(*protocol.read_response(request.method))
28
+ def self.read(connection, request)
29
+ if parts = connection.read_response(request.method)
30
+ self.new(connection, *parts)
31
+ end
32
+ end
33
+
34
+ def initialize(connection, version, status, reason, headers, body)
35
+ @connection = connection
36
+
37
+ protocol = connection.upgrade?(headers)
30
38
 
31
- @protocol = protocol
39
+ super(version, status, reason, headers, body, protocol)
40
+ end
41
+
42
+ def hijack?
43
+ @body.nil?
44
+ end
45
+
46
+ def hijack!
47
+ @connection.hijack!
32
48
  end
33
49
  end
34
50
  end