async-http 0.73.0 → 0.75.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: 01cf4d909911a28f4f386982504e5865d9053210af1cb121a4acc8e0812a63bd
4
- data.tar.gz: 20b913990be812792c72f5e68649ee8084310112287e316c438c6a220cee9982
3
+ metadata.gz: 946e45fa80db4dc1a3416f91a6533d58e7e4c5b8e7db9a687b922e9161f84dcf
4
+ data.tar.gz: 7810dfb04b6c120e91660c4cabec884ef531bb4b4078c149558a6190314f88cb
5
5
  SHA512:
6
- metadata.gz: 6b1eefc4c6f0d1a631c1b6f0365ae36a7fe4067e1b372f334473b99f379f0755d29d53a5a04183f5eef46a9c0cf6504a2a56b7c5741757700b44d5c2283fb129
7
- data.tar.gz: 1ed32dc7b17b30ddd2d842e688e89df28a0cc51a0610398ea567191c6158c5adde2ebdd337035450fca499b0c312e2d9b3bed616f9cf1058c1d136bbdd10acd4
6
+ metadata.gz: 456325793d94251a8b8b117000361e512c23aad9ce03dd2df6c387a35405ac83a7cae1b0de270bf0264485ab98ff5fff3778d720845fe26753950bedd1066b38
7
+ data.tar.gz: de2cb3749808740c03bd0d0d6cfe5facb3282d26f8465ddeb734158389cfdd70b2bf5610a01bf1d4f0af44fdc1d6c15249f912942038778e9f38217e96ad9c84
checksums.yaml.gz.sig CHANGED
Binary file
@@ -39,14 +39,14 @@ module Async
39
39
  # @parameter url [String] The URL to request, e.g. `https://www.codeotaku.com`.
40
40
  # @parameter headers [Hash | Protocol::HTTP::Headers] The headers to send with the request.
41
41
  # @parameter body [String | Protocol::HTTP::Body] The body to send with the request.
42
- def call(method, url, headers = nil, body = nil, &block)
42
+ def call(verb, url, *arguments, **options, &block)
43
43
  endpoint = Endpoint[url]
44
44
  client = self.client_for(endpoint)
45
45
 
46
- body = Body::Buffered.wrap(body)
47
- headers = ::Protocol::HTTP::Headers[headers]
46
+ options[:authority] ||= endpoint.authority
47
+ options[:scheme] ||= endpoint.scheme
48
48
 
49
- request = ::Protocol::HTTP::Request.new(endpoint.scheme, endpoint.authority, method, endpoint.path, nil, headers, body)
49
+ request = ::Protocol::HTTP::Request[verb, endpoint.path, *arguments, **options]
50
50
 
51
51
  response = client.call(request)
52
52
 
@@ -68,8 +68,8 @@ module Async
68
68
  end
69
69
 
70
70
  ::Protocol::HTTP::Methods.each do |name, verb|
71
- define_method(verb.downcase) do |url, headers = nil, body = nil, &block|
72
- self.call(verb, url, headers, body, &block)
71
+ define_method(verb.downcase) do |url, *arguments, **options, &block|
72
+ self.call(verb, url, *arguments, **options, &block)
73
73
  end
74
74
  end
75
75
 
@@ -1,7 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  # Released under the MIT License.
4
- # Copyright, 2018-2023, by Samuel Williams.
4
+ # Copyright, 2018-2024, by Samuel Williams.
5
5
 
6
6
  require_relative 'connection'
7
7
 
@@ -33,22 +33,16 @@ module Async
33
33
 
34
34
  if protocol = request.protocol
35
35
  # 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.
36
- task.async do |subtask|
37
- subtask.annotate("Upgrading request.")
38
-
36
+ task.async(annotation: "Upgrading request...") do
39
37
  # If this fails, this connection will be closed.
40
38
  write_upgrade_body(protocol, body)
41
39
  end
42
40
  elsif request.connect?
43
- task.async do |subtask|
44
- subtask.annotate("Tunnelling body.")
45
-
41
+ task.async(annotation: "Tunnneling request...") do
46
42
  write_tunnel_body(@version, body)
47
43
  end
48
44
  else
49
- task.async do |subtask|
50
- subtask.annotate("Streaming body.")
51
-
45
+ task.async(annotation: "Streaming request...") do
52
46
  # 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.
53
47
  write_body(@version, body, false, trailer)
54
48
  end
@@ -59,33 +59,42 @@ module Async
59
59
  if response
60
60
  trailer = response.headers.trailer!
61
61
 
62
- write_response(@version, response.status, response.headers)
63
-
64
62
  # Some operations in this method are long running, that is, it's expected that `body.call(stream)` could literally run indefinitely. In order to facilitate garbage collection, we want to nullify as many local variables before calling the streaming body. This ensures that the garbage collection can clean up as much state as possible during the long running operation, so we don't retain objects that are no longer needed.
65
-
63
+
66
64
  if body and protocol = response.protocol
65
+ # We force a 101 response if the protocol is upgraded - HTTP/2 CONNECT will return 200 for success, but this won't be understood by HTTP/1 clients:
66
+ write_response(@version, 101, response.headers)
67
+
67
68
  stream = write_upgrade_body(protocol)
68
69
 
69
70
  # At this point, the request body is hijacked, so we don't want to call #finish below.
70
- request = response = nil
71
-
72
- body.call(stream)
73
- elsif request.connect? and response.success?
74
- stream = write_tunnel_body(request.version)
75
-
76
- # Same as above:
77
- request = response = nil
78
-
79
- body.call(stream)
80
- else
81
- head = request.head?
82
- version = request.version
83
-
84
- # Same as above:
85
71
  request = nil unless request.body
86
72
  response = nil
87
73
 
88
- write_body(version, body, head, trailer)
74
+ # We must return here as no further request processing can be done:
75
+ return body.call(stream)
76
+ else
77
+ write_response(@version, response.status, response.headers)
78
+
79
+ if request.connect? and response.success?
80
+ stream = write_tunnel_body(request.version)
81
+
82
+ # Same as above:
83
+ request = nil unless request.body
84
+ response = nil
85
+
86
+ # We must return here as no further request processing can be done:
87
+ return body.call(stream)
88
+ else
89
+ head = request.head?
90
+ version = request.version
91
+
92
+ # Same as above:
93
+ request = nil unless request.body
94
+ response = nil
95
+
96
+ write_body(version, body, head, trailer)
97
+ end
89
98
  end
90
99
 
91
100
  # We are done with the body, you shouldn't need to call close on it:
@@ -66,14 +66,14 @@ module Async
66
66
  end
67
67
 
68
68
  def close(error = nil)
69
- super
70
-
71
69
  # Ensure the reader task is stopped.
72
70
  if @reader
73
71
  reader = @reader
74
72
  @reader = nil
75
73
  reader.stop
76
74
  end
75
+
76
+ super
77
77
  end
78
78
 
79
79
  def read_in_background(parent: Task.current)
@@ -101,6 +101,8 @@ module Async
101
101
  ensure
102
102
  # Don't call #close twice.
103
103
  if @reader
104
+ @reader = nil
105
+
104
106
  self.close(error)
105
107
  end
106
108
  end
@@ -1,7 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  # Released under the MIT License.
4
- # Copyright, 2018-2023, by Samuel Williams.
4
+ # Copyright, 2018-2024, by Samuel Williams.
5
5
 
6
6
  require_relative '../request'
7
7
  require_relative 'stream'
@@ -1,7 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  # Released under the MIT License.
4
- # Copyright, 2018-2023, by Samuel Williams.
4
+ # Copyright, 2018-2024, by Samuel Williams.
5
5
 
6
6
  require_relative '../response'
7
7
  require_relative 'stream'
@@ -54,6 +54,11 @@ module Async
54
54
  @response.status = status
55
55
  @headers = ::Protocol::HTTP::Headers.new
56
56
 
57
+ # If the protocol request was successful, ensure the response protocol matches:
58
+ if status == 200 and protocol = @response.request.protocol
59
+ @response.protocol = Array(protocol).first
60
+ end
61
+
57
62
  headers.each do |key, value|
58
63
  # It's guaranteed that this should be the first header:
59
64
  if key == CONTENT_LENGTH
@@ -118,7 +123,7 @@ module Async
118
123
 
119
124
  @exception = error
120
125
 
121
- notify!
126
+ self.notify!
122
127
  end
123
128
  end
124
129
 
@@ -1,7 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  # Released under the MIT License.
4
- # Copyright, 2017-2023, by Samuel Williams.
4
+ # Copyright, 2017-2024, by Samuel Williams.
5
5
 
6
6
  require 'protocol/http/request'
7
7
  require 'protocol/http/headers'
@@ -5,6 +5,6 @@
5
5
 
6
6
  module Async
7
7
  module HTTP
8
- VERSION = "0.73.0"
8
+ VERSION = "0.75.0"
9
9
  end
10
10
  end
data/readme.md CHANGED
@@ -12,6 +12,22 @@ Please see the [project documentation](https://socketry.github.io/async-http/) f
12
12
 
13
13
  - [Testing](https://socketry.github.io/async-http/guides/testing/index) - This guide explains how to use `Async::HTTP` clients and servers in your tests.
14
14
 
15
+ ## Releases
16
+
17
+ Please see the [project releases](https://socketry.github.io/async-http/releases/index) for all releases.
18
+
19
+ ### v0.75.0
20
+
21
+ - Better handling of HTTP/1 \<-\> HTTP/2 proxying, specifically upgrade/CONNECT requests.
22
+
23
+ ### v0.74.0
24
+
25
+ - [`Async::HTTP::Internet` accepts keyword arguments](https://socketry.github.io/async-http/releases/index#async::http::internet-accepts-keyword-arguments)
26
+
27
+ ### v0.73.0
28
+
29
+ - [Update support for `interim_response`](https://socketry.github.io/async-http/releases/index#update-support-for-interim_response)
30
+
15
31
  ## See Also
16
32
 
17
33
  - [benchmark-http](https://github.com/socketry/benchmark-http) — A benchmarking tool to report on web server concurrency.
data/releases.md ADDED
@@ -0,0 +1,55 @@
1
+ # Releases
2
+
3
+ ## v0.75.0
4
+
5
+ - Better handling of HTTP/1 \<-\> HTTP/2 proxying, specifically upgrade/CONNECT requests.
6
+
7
+ ## v0.74.0
8
+
9
+ ### `Async::HTTP::Internet` accepts keyword arguments
10
+
11
+ `Async::HTTP::Internet` now accepts keyword arguments for making a request, e.g.
12
+
13
+ ``` ruby
14
+ internet = Async::HTTP::Internet.instance
15
+
16
+ # This will let you override the authority (HTTP/1.1 host header, HTTP/2 :authority header):
17
+ internet.get("https://proxy.local", authority: "example.com")
18
+
19
+ # This will let you override the scheme:
20
+ internet.get("https://example.com", scheme: "http")
21
+ ```
22
+
23
+ ## v0.73.0
24
+
25
+ ### Update support for `interim_response`
26
+
27
+ `Protocol::HTTP::Request` now supports an `interim_response` callback, which will be called with the interim response status and headers. This works on both the client and the server:
28
+
29
+ ``` ruby
30
+ # Server side:
31
+ def call(request)
32
+ if request.headers['expect'].include?('100-continue')
33
+ request.send_interim_response(100)
34
+ end
35
+
36
+ # ...
37
+ end
38
+
39
+ # Client side:
40
+ body = Async::HTTP::Body::Writable.new
41
+
42
+ interim_repsonse = proc do |status, headers|
43
+ if status == 100
44
+ # Continue sending the body...
45
+ body.write("Hello, world!")
46
+ body.close
47
+ end
48
+ end
49
+
50
+ Async::HTTP::Internet.instance.post("https://example.com", body, interim_response: interim_response) do |response|
51
+ unless response.success?
52
+ body.close
53
+ end
54
+ end
55
+ ```
data.tar.gz.sig CHANGED
Binary file
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: async-http
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.73.0
4
+ version: 0.75.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Samuel Williams
@@ -58,7 +58,7 @@ cert_chain:
58
58
  Q2K9NVun/S785AP05vKkXZEFYxqG6EW012U4oLcFl5MySFajYXRYbuUpH6AY+HP8
59
59
  voD0MPg1DssDLKwXyt1eKD/+Fq0bFWhwVM/1XiAXL7lyYUyOq24KHgQ2Csg=
60
60
  -----END CERTIFICATE-----
61
- date: 2024-08-31 00:00:00.000000000 Z
61
+ date: 2024-09-04 00:00:00.000000000 Z
62
62
  dependencies:
63
63
  - !ruby/object:Gem::Dependency
64
64
  name: async
@@ -224,6 +224,7 @@ files:
224
224
  - lib/async/http/version.rb
225
225
  - license.md
226
226
  - readme.md
227
+ - releases.md
227
228
  homepage: https://github.com/socketry/async-http
228
229
  licenses:
229
230
  - MIT
metadata.gz.sig CHANGED
Binary file