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 +4 -4
- checksums.yaml.gz.sig +0 -0
- data/lib/async/http/internet.rb +6 -6
- data/lib/async/http/protocol/http1/client.rb +4 -10
- data/lib/async/http/protocol/http1/server.rb +28 -19
- data/lib/async/http/protocol/http2/connection.rb +4 -2
- data/lib/async/http/protocol/http2/request.rb +1 -1
- data/lib/async/http/protocol/http2/response.rb +7 -2
- data/lib/async/http/protocol/request.rb +1 -1
- data/lib/async/http/version.rb +1 -1
- data/readme.md +16 -0
- data/releases.md +55 -0
- data.tar.gz.sig +0 -0
- metadata +3 -2
- metadata.gz.sig +0 -0
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 946e45fa80db4dc1a3416f91a6533d58e7e4c5b8e7db9a687b922e9161f84dcf
|
4
|
+
data.tar.gz: 7810dfb04b6c120e91660c4cabec884ef531bb4b4078c149558a6190314f88cb
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 456325793d94251a8b8b117000361e512c23aad9ce03dd2df6c387a35405ac83a7cae1b0de270bf0264485ab98ff5fff3778d720845fe26753950bedd1066b38
|
7
|
+
data.tar.gz: de2cb3749808740c03bd0d0d6cfe5facb3282d26f8465ddeb734158389cfdd70b2bf5610a01bf1d4f0af44fdc1d6c15249f912942038778e9f38217e96ad9c84
|
checksums.yaml.gz.sig
CHANGED
Binary file
|
data/lib/async/http/internet.rb
CHANGED
@@ -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(
|
42
|
+
def call(verb, url, *arguments, **options, &block)
|
43
43
|
endpoint = Endpoint[url]
|
44
44
|
client = self.client_for(endpoint)
|
45
45
|
|
46
|
-
|
47
|
-
|
46
|
+
options[:authority] ||= endpoint.authority
|
47
|
+
options[:scheme] ||= endpoint.scheme
|
48
48
|
|
49
|
-
request = ::Protocol::HTTP::Request
|
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,
|
72
|
-
self.call(verb, url,
|
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-
|
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
|
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
|
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
|
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
|
-
|
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-
|
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
|
|
data/lib/async/http/version.rb
CHANGED
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.
|
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-
|
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
|