async-http 0.94.2 → 0.94.3
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 +4 -4
- checksums.yaml.gz.sig +0 -0
- data/lib/async/http/body/hijack.rb +17 -0
- data/lib/async/http/body/pipe.rb +3 -0
- data/lib/async/http/body.rb +3 -0
- data/lib/async/http/client.rb +16 -4
- data/lib/async/http/endpoint.rb +33 -0
- data/lib/async/http/internet.rb +7 -0
- data/lib/async/http/middleware/location_redirector.rb +9 -0
- data/lib/async/http/mock/endpoint.rb +10 -0
- data/lib/async/http/protocol/configurable.rb +15 -0
- data/lib/async/http/protocol/http1/client.rb +3 -0
- data/lib/async/http/protocol/http1/connection.rb +13 -0
- data/lib/async/http/protocol/http1/finishable.rb +9 -0
- data/lib/async/http/protocol/http1/request.rb +22 -0
- data/lib/async/http/protocol/http1/response.rb +10 -0
- data/lib/async/http/protocol/http1/server.rb +7 -0
- data/lib/async/http/protocol/http1.rb +1 -0
- data/lib/async/http/protocol/http10.rb +1 -0
- data/lib/async/http/protocol/http11.rb +1 -0
- data/lib/async/http/protocol/http2/client.rb +10 -0
- data/lib/async/http/protocol/http2/connection.rb +16 -0
- data/lib/async/http/protocol/http2/input.rb +5 -0
- data/lib/async/http/protocol/http2/output.rb +13 -0
- data/lib/async/http/protocol/http2/request.rb +17 -0
- data/lib/async/http/protocol/http2/response.rb +23 -0
- data/lib/async/http/protocol/http2/server.rb +16 -0
- data/lib/async/http/protocol/http2/stream.rb +19 -0
- data/lib/async/http/protocol/http2.rb +1 -0
- data/lib/async/http/protocol/https.rb +7 -0
- data/lib/async/http/protocol/request.rb +9 -1
- data/lib/async/http/protocol/response.rb +6 -1
- data/lib/async/http/proxy.rb +12 -0
- data/lib/async/http/server.rb +14 -0
- data/lib/async/http/statistics.rb +19 -0
- data/lib/async/http/version.rb +3 -1
- data/lib/async/http.rb +0 -3
- data/readme.md +20 -4
- data/releases.md +4 -0
- data.tar.gz.sig +0 -0
- metadata +3 -3
- metadata.gz.sig +0 -0
|
@@ -10,7 +10,12 @@ module Async
|
|
|
10
10
|
module HTTP
|
|
11
11
|
module Protocol
|
|
12
12
|
module HTTP1
|
|
13
|
+
# An HTTP/1 response received from a server.
|
|
13
14
|
class Response < Protocol::Response
|
|
15
|
+
# Read the response from the connection, handling interim responses.
|
|
16
|
+
# @parameter connection [Connection] The HTTP/1 connection to read from.
|
|
17
|
+
# @parameter request [Request] The original request.
|
|
18
|
+
# @returns [Response | Nil] The final response.
|
|
14
19
|
def self.read(connection, request)
|
|
15
20
|
while parts = connection.read_response(request.method)
|
|
16
21
|
response = self.new(connection, *parts)
|
|
@@ -39,6 +44,8 @@ module Async
|
|
|
39
44
|
super(version, status, headers, body, protocol)
|
|
40
45
|
end
|
|
41
46
|
|
|
47
|
+
# Assign the connection pool, releasing the connection if it is already idle or closed.
|
|
48
|
+
# @parameter pool [Async::Pool::Controller] The connection pool.
|
|
42
49
|
def pool=(pool)
|
|
43
50
|
if @connection.idle? or @connection.closed?
|
|
44
51
|
pool.release(@connection)
|
|
@@ -47,14 +54,17 @@ module Async
|
|
|
47
54
|
end
|
|
48
55
|
end
|
|
49
56
|
|
|
57
|
+
# @returns [Connection] The underlying HTTP/1 connection.
|
|
50
58
|
def connection
|
|
51
59
|
@connection
|
|
52
60
|
end
|
|
53
61
|
|
|
62
|
+
# @returns [Boolean] Whether connection hijacking is available (when the body is `nil`).
|
|
54
63
|
def hijack?
|
|
55
64
|
@body.nil?
|
|
56
65
|
end
|
|
57
66
|
|
|
67
|
+
# Hijack the underlying connection for bidirectional communication.
|
|
58
68
|
def hijack!
|
|
59
69
|
@connection.hijack!
|
|
60
70
|
end
|
|
@@ -16,19 +16,24 @@ module Async
|
|
|
16
16
|
module HTTP
|
|
17
17
|
module Protocol
|
|
18
18
|
module HTTP1
|
|
19
|
+
# An HTTP/1 server connection that receives requests and sends responses.
|
|
19
20
|
class Server < Connection
|
|
21
|
+
# Initialize the HTTP/1 server connection.
|
|
20
22
|
def initialize(...)
|
|
21
23
|
super
|
|
22
24
|
|
|
23
25
|
@ready = Async::Notification.new
|
|
24
26
|
end
|
|
25
27
|
|
|
28
|
+
# Called when the connection is closed, signalling any waiting tasks.
|
|
26
29
|
def closed(error = nil)
|
|
27
30
|
super
|
|
28
31
|
|
|
29
32
|
@ready.signal
|
|
30
33
|
end
|
|
31
34
|
|
|
35
|
+
# Write a failure response with the given status code.
|
|
36
|
+
# @parameter status [Integer] The HTTP status code to send.
|
|
32
37
|
def fail_request(status)
|
|
33
38
|
@persistent = false
|
|
34
39
|
write_response(@version, status, {})
|
|
@@ -38,6 +43,8 @@ module Async
|
|
|
38
43
|
Console.debug(self, "Failed to write failure response!", error)
|
|
39
44
|
end
|
|
40
45
|
|
|
46
|
+
# Read the next incoming request from the connection.
|
|
47
|
+
# @returns [Request | Nil] The next request, or `nil` if the connection is closed.
|
|
41
48
|
def next_request
|
|
42
49
|
if closed?
|
|
43
50
|
return nil
|
|
@@ -12,9 +12,12 @@ module Async
|
|
|
12
12
|
module HTTP
|
|
13
13
|
module Protocol
|
|
14
14
|
module HTTP2
|
|
15
|
+
# An HTTP/2 client connection that sends requests and reads responses.
|
|
15
16
|
class Client < ::Protocol::HTTP2::Client
|
|
16
17
|
include Connection
|
|
17
18
|
|
|
19
|
+
# Initialize the HTTP/2 client with an IO stream.
|
|
20
|
+
# @parameter stream [IO::Stream] The underlying stream.
|
|
18
21
|
def initialize(stream)
|
|
19
22
|
@stream = stream
|
|
20
23
|
|
|
@@ -23,6 +26,8 @@ module Async
|
|
|
23
26
|
super(framer)
|
|
24
27
|
end
|
|
25
28
|
|
|
29
|
+
# Create a new response stream for the next request.
|
|
30
|
+
# @returns [Response] The response object to be populated.
|
|
26
31
|
def create_response
|
|
27
32
|
Response::Stream.create(self, self.next_stream_id).response
|
|
28
33
|
end
|
|
@@ -38,10 +43,15 @@ module Async
|
|
|
38
43
|
return response
|
|
39
44
|
end
|
|
40
45
|
|
|
46
|
+
# Write a request to the remote server via the given response stream.
|
|
47
|
+
# @parameter response [Response] The response stream to write through.
|
|
48
|
+
# @parameter request [Protocol::HTTP::Request] The request to send.
|
|
41
49
|
def write_request(response, request)
|
|
42
50
|
response.send_request(request)
|
|
43
51
|
end
|
|
44
52
|
|
|
53
|
+
# Wait for the response headers to arrive.
|
|
54
|
+
# @parameter response [Response] The response to wait on.
|
|
45
55
|
def read_response(response)
|
|
46
56
|
response.wait
|
|
47
57
|
end
|
|
@@ -26,7 +26,9 @@ module Async
|
|
|
26
26
|
CONNECTION = "connection".freeze
|
|
27
27
|
TRAILER = "trailer".freeze
|
|
28
28
|
|
|
29
|
+
# Provides shared connection behaviour for HTTP/2 client and server connections.
|
|
29
30
|
module Connection
|
|
31
|
+
# Initialize the connection state.
|
|
30
32
|
def initialize(...)
|
|
31
33
|
super
|
|
32
34
|
|
|
@@ -36,36 +38,45 @@ module Async
|
|
|
36
38
|
@write_frame_guard = Async::Semaphore.new(1)
|
|
37
39
|
end
|
|
38
40
|
|
|
41
|
+
# Synchronize write access to the connection.
|
|
42
|
+
# @yields {|...| ...} The block to execute while holding the write lock.
|
|
39
43
|
def synchronize(&block)
|
|
40
44
|
@write_frame_guard.acquire(&block)
|
|
41
45
|
end
|
|
42
46
|
|
|
47
|
+
# @returns [String] A string representation of this connection.
|
|
43
48
|
def to_s
|
|
44
49
|
"\#<#{self.class} #{@streams.count} active streams>"
|
|
45
50
|
end
|
|
46
51
|
|
|
52
|
+
# @returns [String] A JSON-compatible representation.
|
|
47
53
|
def as_json(...)
|
|
48
54
|
to_s
|
|
49
55
|
end
|
|
50
56
|
|
|
57
|
+
# @returns [String] A JSON string representation.
|
|
51
58
|
def to_json(...)
|
|
52
59
|
as_json.to_json(...)
|
|
53
60
|
end
|
|
54
61
|
|
|
55
62
|
attr :stream
|
|
56
63
|
|
|
64
|
+
# @returns [Boolean] Whether this is an HTTP/1 connection.
|
|
57
65
|
def http1?
|
|
58
66
|
false
|
|
59
67
|
end
|
|
60
68
|
|
|
69
|
+
# @returns [Boolean] Whether this is an HTTP/2 connection.
|
|
61
70
|
def http2?
|
|
62
71
|
true
|
|
63
72
|
end
|
|
64
73
|
|
|
74
|
+
# Start the background reader task if it is not already running.
|
|
65
75
|
def start_connection
|
|
66
76
|
@reader || read_in_background
|
|
67
77
|
end
|
|
68
78
|
|
|
79
|
+
# Close the connection and stop the background reader.
|
|
69
80
|
def close(error = nil)
|
|
70
81
|
# Ensure the reader task is stopped.
|
|
71
82
|
if @reader
|
|
@@ -77,6 +88,7 @@ module Async
|
|
|
77
88
|
super
|
|
78
89
|
end
|
|
79
90
|
|
|
91
|
+
# Start a transient background task that reads frames from the connection.
|
|
80
92
|
def read_in_background(parent: Task.current)
|
|
81
93
|
raise RuntimeError, "Connection is closed!" if closed?
|
|
82
94
|
|
|
@@ -106,12 +118,14 @@ module Async
|
|
|
106
118
|
|
|
107
119
|
attr :promises
|
|
108
120
|
|
|
121
|
+
# @returns [Protocol::HTTP::Peer] The peer information for this connection.
|
|
109
122
|
def peer
|
|
110
123
|
@peer ||= ::Protocol::HTTP::Peer.for(@stream.io)
|
|
111
124
|
end
|
|
112
125
|
|
|
113
126
|
attr :count
|
|
114
127
|
|
|
128
|
+
# @returns [Integer] The maximum number of concurrent streams allowed.
|
|
115
129
|
def concurrency
|
|
116
130
|
self.maximum_concurrent_streams
|
|
117
131
|
end
|
|
@@ -121,10 +135,12 @@ module Async
|
|
|
121
135
|
@stream&.readable?
|
|
122
136
|
end
|
|
123
137
|
|
|
138
|
+
# @returns [Boolean] Whether the connection can be reused.
|
|
124
139
|
def reusable?
|
|
125
140
|
!self.closed?
|
|
126
141
|
end
|
|
127
142
|
|
|
143
|
+
# @returns [String] The HTTP version string.
|
|
128
144
|
def version
|
|
129
145
|
VERSION
|
|
130
146
|
end
|
|
@@ -11,6 +11,9 @@ module Async
|
|
|
11
11
|
module HTTP2
|
|
12
12
|
# A writable body which requests window updates when data is read from it.
|
|
13
13
|
class Input < ::Protocol::HTTP::Body::Writable
|
|
14
|
+
# Initialize the input body.
|
|
15
|
+
# @parameter stream [Stream] The HTTP/2 stream to read from.
|
|
16
|
+
# @parameter length [Integer | Nil] The expected content length.
|
|
14
17
|
def initialize(stream, length)
|
|
15
18
|
super(length)
|
|
16
19
|
|
|
@@ -18,6 +21,8 @@ module Async
|
|
|
18
21
|
@remaining = length
|
|
19
22
|
end
|
|
20
23
|
|
|
24
|
+
# Read the next chunk of data, requesting window updates as needed.
|
|
25
|
+
# @returns [String | Nil] The next chunk, or `nil` if the body is complete.
|
|
21
26
|
def read
|
|
22
27
|
if chunk = super
|
|
23
28
|
# If we read a chunk fron the stream, we want to extend the window if required so more data will be provided.
|
|
@@ -9,7 +9,12 @@ module Async
|
|
|
9
9
|
module HTTP
|
|
10
10
|
module Protocol
|
|
11
11
|
module HTTP2
|
|
12
|
+
# Writes body data to an HTTP/2 stream, respecting flow control windows.
|
|
12
13
|
class Output
|
|
14
|
+
# Initialize the output handler.
|
|
15
|
+
# @parameter stream [Stream] The HTTP/2 stream to write to.
|
|
16
|
+
# @parameter body [Protocol::HTTP::Body::Readable] The body to read from.
|
|
17
|
+
# @parameter trailer [Protocol::HTTP::Headers | Nil] Optional trailing headers.
|
|
13
18
|
def initialize(stream, body, trailer = nil)
|
|
14
19
|
@stream = stream
|
|
15
20
|
@body = body
|
|
@@ -23,6 +28,7 @@ module Async
|
|
|
23
28
|
|
|
24
29
|
attr :trailer
|
|
25
30
|
|
|
31
|
+
# Start an asynchronous task to write the body to the stream.
|
|
26
32
|
def start(parent: Task.current)
|
|
27
33
|
raise "Task already started!" if @task
|
|
28
34
|
|
|
@@ -33,6 +39,9 @@ module Async
|
|
|
33
39
|
end
|
|
34
40
|
end
|
|
35
41
|
|
|
42
|
+
# Signal that the flow control window has been updated.
|
|
43
|
+
# @parameter size [Integer] The new window size.
|
|
44
|
+
# @returns [Boolean] Always returns `true`.
|
|
36
45
|
def window_updated(size)
|
|
37
46
|
@guard.synchronize do
|
|
38
47
|
@window_updated.signal
|
|
@@ -41,6 +50,8 @@ module Async
|
|
|
41
50
|
return true
|
|
42
51
|
end
|
|
43
52
|
|
|
53
|
+
# Write a chunk of data to the HTTP/2 stream, respecting flow control.
|
|
54
|
+
# @parameter chunk [String] The data to write.
|
|
44
55
|
def write(chunk)
|
|
45
56
|
until chunk.empty?
|
|
46
57
|
maximum_size = @stream.available_frame_size
|
|
@@ -62,6 +73,8 @@ module Async
|
|
|
62
73
|
end
|
|
63
74
|
end
|
|
64
75
|
|
|
76
|
+
# Finish writing to the stream.
|
|
77
|
+
# @parameter error [Exception | Nil] An optional error that caused the close.
|
|
65
78
|
def close_write(error = nil)
|
|
66
79
|
if stream = @stream
|
|
67
80
|
@stream = nil
|
|
@@ -12,7 +12,9 @@ module Async
|
|
|
12
12
|
module HTTP2
|
|
13
13
|
# Typically used on the server side to represent an incoming request, and write the response.
|
|
14
14
|
class Request < Protocol::Request
|
|
15
|
+
# Represents the HTTP/2 stream associated with an incoming server-side request.
|
|
15
16
|
class Stream < HTTP2::Stream
|
|
17
|
+
# Initialize the request stream.
|
|
16
18
|
def initialize(*)
|
|
17
19
|
super
|
|
18
20
|
|
|
@@ -22,6 +24,9 @@ module Async
|
|
|
22
24
|
|
|
23
25
|
attr :request
|
|
24
26
|
|
|
27
|
+
# Process the initial headers received from the client and construct the request.
|
|
28
|
+
# @parameter headers [Array] The list of header key-value pairs.
|
|
29
|
+
# @parameter end_stream [Boolean] Whether the stream is complete after these headers.
|
|
25
30
|
def receive_initial_headers(headers, end_stream)
|
|
26
31
|
@headers = ::Protocol::HTTP::Headers.new
|
|
27
32
|
|
|
@@ -79,6 +84,8 @@ module Async
|
|
|
79
84
|
return headers
|
|
80
85
|
end
|
|
81
86
|
|
|
87
|
+
# Called when the stream is closed.
|
|
88
|
+
# @parameter error [Exception | Nil] The error that caused the close, if any.
|
|
82
89
|
def closed(error)
|
|
83
90
|
@request = nil
|
|
84
91
|
|
|
@@ -86,6 +93,8 @@ module Async
|
|
|
86
93
|
end
|
|
87
94
|
end
|
|
88
95
|
|
|
96
|
+
# Initialize the request from an HTTP/2 stream.
|
|
97
|
+
# @parameter stream [Stream] The HTTP/2 stream for this request.
|
|
89
98
|
def initialize(stream)
|
|
90
99
|
super(nil, nil, nil, nil, VERSION, nil, nil, nil, self.public_method(:write_interim_response))
|
|
91
100
|
|
|
@@ -94,14 +103,17 @@ module Async
|
|
|
94
103
|
|
|
95
104
|
attr :stream
|
|
96
105
|
|
|
106
|
+
# @returns [Connection] The underlying HTTP/2 connection.
|
|
97
107
|
def connection
|
|
98
108
|
@stream.connection
|
|
99
109
|
end
|
|
100
110
|
|
|
111
|
+
# @returns [Boolean] Whether the request has the required pseudo-headers.
|
|
101
112
|
def valid?
|
|
102
113
|
@scheme and @method and (@path or @method == ::Protocol::HTTP::Methods::CONNECT)
|
|
103
114
|
end
|
|
104
115
|
|
|
116
|
+
# @returns [Boolean] Whether connection hijacking is supported (not available for HTTP/2).
|
|
105
117
|
def hijack?
|
|
106
118
|
false
|
|
107
119
|
end
|
|
@@ -110,6 +122,8 @@ module Async
|
|
|
110
122
|
[STATUS, "500"],
|
|
111
123
|
]
|
|
112
124
|
|
|
125
|
+
# Send a response back to the client via the HTTP/2 stream.
|
|
126
|
+
# @parameter response [Protocol::HTTP::Response | Nil] The response to send.
|
|
113
127
|
def send_response(response)
|
|
114
128
|
if response.nil?
|
|
115
129
|
return @stream.send_headers(NO_RESPONSE, ::Protocol::HTTP2::END_STREAM)
|
|
@@ -143,6 +157,9 @@ module Async
|
|
|
143
157
|
end
|
|
144
158
|
end
|
|
145
159
|
|
|
160
|
+
# Write an interim (1xx) response to the client.
|
|
161
|
+
# @parameter status [Integer] The interim HTTP status code.
|
|
162
|
+
# @parameter headers [Hash | Nil] Optional interim response headers.
|
|
146
163
|
def write_interim_response(status, headers = nil)
|
|
147
164
|
interim_response_headers = [
|
|
148
165
|
[STATUS, status]
|
|
@@ -12,7 +12,9 @@ module Async
|
|
|
12
12
|
module HTTP2
|
|
13
13
|
# Typically used on the client side for writing a request and reading the incoming response.
|
|
14
14
|
class Response < Protocol::Response
|
|
15
|
+
# Represents the HTTP/2 stream associated with an outgoing client-side response.
|
|
15
16
|
class Stream < HTTP2::Stream
|
|
17
|
+
# Initialize the response stream.
|
|
16
18
|
def initialize(*)
|
|
17
19
|
super
|
|
18
20
|
|
|
@@ -24,6 +26,8 @@ module Async
|
|
|
24
26
|
|
|
25
27
|
attr :response
|
|
26
28
|
|
|
29
|
+
# Wait for the response headers and return the response body.
|
|
30
|
+
# @returns [Protocol::HTTP::Body::Readable | Nil] The response body.
|
|
27
31
|
def wait_for_input
|
|
28
32
|
# The input isn't ready until the response headers have been received:
|
|
29
33
|
@response.wait
|
|
@@ -32,6 +36,9 @@ module Async
|
|
|
32
36
|
return @response.body
|
|
33
37
|
end
|
|
34
38
|
|
|
39
|
+
# Handle a push promise stream from the server.
|
|
40
|
+
# @parameter promised_stream_id [Integer] The stream ID for the promised resource.
|
|
41
|
+
# @parameter headers [Array] The promise headers.
|
|
35
42
|
def accept_push_promise_stream(promised_stream_id, headers)
|
|
36
43
|
raise ProtocolError, "Cannot accept push promise stream!"
|
|
37
44
|
end
|
|
@@ -86,6 +93,9 @@ module Async
|
|
|
86
93
|
return headers
|
|
87
94
|
end
|
|
88
95
|
|
|
96
|
+
# Process interim (1xx) response headers.
|
|
97
|
+
# @parameter status [Integer] The interim status code.
|
|
98
|
+
# @parameter headers [Array] The interim response headers.
|
|
89
99
|
def receive_interim_headers(status, headers)
|
|
90
100
|
if headers.any?
|
|
91
101
|
headers = ::Protocol::HTTP::Headers[headers]
|
|
@@ -114,6 +124,8 @@ module Async
|
|
|
114
124
|
end
|
|
115
125
|
end
|
|
116
126
|
|
|
127
|
+
# Called when the stream is closed.
|
|
128
|
+
# @parameter error [Exception | Nil] The error that caused the close, if any.
|
|
117
129
|
def closed(error)
|
|
118
130
|
super
|
|
119
131
|
|
|
@@ -127,6 +139,8 @@ module Async
|
|
|
127
139
|
end
|
|
128
140
|
end
|
|
129
141
|
|
|
142
|
+
# Initialize the response from an HTTP/2 stream.
|
|
143
|
+
# @parameter stream [Stream] The HTTP/2 stream for this response.
|
|
130
144
|
def initialize(stream)
|
|
131
145
|
super(stream.connection.version, nil, nil)
|
|
132
146
|
|
|
@@ -137,6 +151,8 @@ module Async
|
|
|
137
151
|
attr :stream
|
|
138
152
|
attr :request
|
|
139
153
|
|
|
154
|
+
# Assign the connection pool, releasing the connection when the stream is closed.
|
|
155
|
+
# @parameter pool [Async::Pool::Controller] The connection pool.
|
|
140
156
|
def pool=(pool)
|
|
141
157
|
# If we are already closed, the stream can be released now:
|
|
142
158
|
if @stream.closed?
|
|
@@ -147,22 +163,29 @@ module Async
|
|
|
147
163
|
end
|
|
148
164
|
end
|
|
149
165
|
|
|
166
|
+
# @returns [Connection] The underlying HTTP/2 connection.
|
|
150
167
|
def connection
|
|
151
168
|
@stream.connection
|
|
152
169
|
end
|
|
153
170
|
|
|
171
|
+
# Wait for the response headers to be received.
|
|
154
172
|
def wait
|
|
155
173
|
@stream.wait
|
|
156
174
|
end
|
|
157
175
|
|
|
176
|
+
# @returns [Boolean] Whether the original request was a HEAD request.
|
|
158
177
|
def head?
|
|
159
178
|
@request&.head?
|
|
160
179
|
end
|
|
161
180
|
|
|
181
|
+
# @returns [Boolean] Whether the response has a valid status.
|
|
162
182
|
def valid?
|
|
163
183
|
!!@status
|
|
164
184
|
end
|
|
165
185
|
|
|
186
|
+
# Build a request object from push promise headers.
|
|
187
|
+
# @parameter headers [Array] The push promise pseudo-headers and headers.
|
|
188
|
+
# @returns [Protocol::HTTP::Request] The constructed request.
|
|
166
189
|
def build_request(headers)
|
|
167
190
|
request = ::Protocol::HTTP::Request.new
|
|
168
191
|
request.headers = ::Protocol::HTTP::Headers.new
|
|
@@ -12,9 +12,12 @@ module Async
|
|
|
12
12
|
module HTTP
|
|
13
13
|
module Protocol
|
|
14
14
|
module HTTP2
|
|
15
|
+
# An HTTP/2 server connection that receives requests and sends responses.
|
|
15
16
|
class Server < ::Protocol::HTTP2::Server
|
|
16
17
|
include Connection
|
|
17
18
|
|
|
19
|
+
# Initialize the HTTP/2 server with an IO stream.
|
|
20
|
+
# @parameter stream [IO::Stream] The underlying stream.
|
|
18
21
|
def initialize(stream)
|
|
19
22
|
# Used by some generic methods in Connetion:
|
|
20
23
|
@stream = stream
|
|
@@ -28,12 +31,15 @@ module Async
|
|
|
28
31
|
|
|
29
32
|
attr :requests
|
|
30
33
|
|
|
34
|
+
# Accept a new stream from a client.
|
|
35
|
+
# @parameter stream_id [Integer] The stream ID assigned by the client.
|
|
31
36
|
def accept_stream(stream_id)
|
|
32
37
|
super do
|
|
33
38
|
Request::Stream.create(self, stream_id)
|
|
34
39
|
end
|
|
35
40
|
end
|
|
36
41
|
|
|
42
|
+
# Close the server connection and stop accepting requests.
|
|
37
43
|
def close(error = nil)
|
|
38
44
|
if @requests
|
|
39
45
|
# Stop the request loop:
|
|
@@ -44,6 +50,9 @@ module Async
|
|
|
44
50
|
super
|
|
45
51
|
end
|
|
46
52
|
|
|
53
|
+
# Enumerate incoming requests, yielding each one for processing.
|
|
54
|
+
# @yields {|request| ...} Each incoming request.
|
|
55
|
+
# @parameter request [Request] The incoming HTTP/2 request.
|
|
47
56
|
def each(task: Task.current)
|
|
48
57
|
task.annotate("Reading #{version} requests for #{self.class}.")
|
|
49
58
|
|
|
@@ -51,6 +60,8 @@ module Async
|
|
|
51
60
|
@requests&.async do |task, request|
|
|
52
61
|
task.annotate("Incoming request: #{request.method} #{request.path.inspect}.")
|
|
53
62
|
|
|
63
|
+
response = nil
|
|
64
|
+
|
|
54
65
|
task.defer_stop do
|
|
55
66
|
response = yield(request)
|
|
56
67
|
rescue
|
|
@@ -60,6 +71,11 @@ module Async
|
|
|
60
71
|
raise
|
|
61
72
|
else
|
|
62
73
|
request.send_response(response)
|
|
74
|
+
# If send response is successful, we clear it so that we don't close it below.
|
|
75
|
+
response = nil
|
|
76
|
+
ensure
|
|
77
|
+
# If some failure occurs and we didn't send the response correctly, ensure that it's closed:
|
|
78
|
+
response&.close
|
|
63
79
|
end
|
|
64
80
|
end
|
|
65
81
|
|
|
@@ -14,7 +14,9 @@ module Async
|
|
|
14
14
|
module HTTP
|
|
15
15
|
module Protocol
|
|
16
16
|
module HTTP2
|
|
17
|
+
# An HTTP/2 stream that manages headers, input data, and output data for a single request/response exchange.
|
|
17
18
|
class Stream < ::Protocol::HTTP2::Stream
|
|
19
|
+
# Initialize the stream state.
|
|
18
20
|
def initialize(*)
|
|
19
21
|
super
|
|
20
22
|
|
|
@@ -36,6 +38,9 @@ module Async
|
|
|
36
38
|
|
|
37
39
|
attr :input
|
|
38
40
|
|
|
41
|
+
# Add a header to the stream, validating against HTTP/2 constraints.
|
|
42
|
+
# @parameter key [String] The header name.
|
|
43
|
+
# @parameter value [String] The header value.
|
|
39
44
|
def add_header(key, value, trailer: false)
|
|
40
45
|
if key == CONNECTION
|
|
41
46
|
raise ::Protocol::HTTP2::HeaderError, "Connection header is not allowed!"
|
|
@@ -48,12 +53,17 @@ module Async
|
|
|
48
53
|
end
|
|
49
54
|
end
|
|
50
55
|
|
|
56
|
+
# Process trailing headers received after the body.
|
|
57
|
+
# @parameter headers [Array] The trailing header key-value pairs.
|
|
58
|
+
# @parameter end_stream [Boolean] Whether the stream ends after these headers.
|
|
51
59
|
def receive_trailing_headers(headers, end_stream)
|
|
52
60
|
headers.each do |key, value|
|
|
53
61
|
add_header(key, value, trailer: true)
|
|
54
62
|
end
|
|
55
63
|
end
|
|
56
64
|
|
|
65
|
+
# Process an incoming HEADERS frame, dispatching to initial or trailing header handling.
|
|
66
|
+
# @parameter frame [Protocol::HTTP2::HeadersFrame] The headers frame to process.
|
|
57
67
|
def process_headers(frame)
|
|
58
68
|
if @headers and frame.end_stream?
|
|
59
69
|
self.receive_trailing_headers(super, frame.end_stream?)
|
|
@@ -74,6 +84,7 @@ module Async
|
|
|
74
84
|
send_reset_stream(error.code)
|
|
75
85
|
end
|
|
76
86
|
|
|
87
|
+
# @returns [Input | Nil] The input body for this stream, if available.
|
|
77
88
|
def wait_for_input
|
|
78
89
|
return @input
|
|
79
90
|
end
|
|
@@ -88,6 +99,8 @@ module Async
|
|
|
88
99
|
end
|
|
89
100
|
end
|
|
90
101
|
|
|
102
|
+
# Update the local flow control window after receiving data.
|
|
103
|
+
# @parameter frame [Protocol::HTTP2::DataFrame] The received data frame.
|
|
91
104
|
def update_local_window(frame)
|
|
92
105
|
consume_local_window(frame)
|
|
93
106
|
|
|
@@ -95,6 +108,9 @@ module Async
|
|
|
95
108
|
# request_window_update
|
|
96
109
|
end
|
|
97
110
|
|
|
111
|
+
# Process an incoming DATA frame and write it to the input body.
|
|
112
|
+
# @parameter frame [Protocol::HTTP2::DataFrame] The data frame to process.
|
|
113
|
+
# @returns [String] The unpacked data.
|
|
98
114
|
def process_data(frame)
|
|
99
115
|
data = frame.unpack
|
|
100
116
|
|
|
@@ -142,6 +158,9 @@ module Async
|
|
|
142
158
|
end
|
|
143
159
|
end
|
|
144
160
|
|
|
161
|
+
# Called when the flow control window is updated.
|
|
162
|
+
# @parameter size [Integer] The new window size.
|
|
163
|
+
# @returns [Boolean] Always returns `true`.
|
|
145
164
|
def window_updated(size)
|
|
146
165
|
super
|
|
147
166
|
|
|
@@ -23,11 +23,18 @@ module Async
|
|
|
23
23
|
nil => HTTP11,
|
|
24
24
|
}
|
|
25
25
|
|
|
26
|
+
# Initialize the HTTPS protocol negotiator.
|
|
27
|
+
# @parameter handlers [Hash] A mapping of ALPN protocol names to protocol classes.
|
|
28
|
+
# @parameter options [Hash] Per-protocol options keyed by protocol class.
|
|
26
29
|
def initialize(handlers = HANDLERS, **options)
|
|
27
30
|
@handlers = handlers
|
|
28
31
|
@options = options
|
|
29
32
|
end
|
|
30
33
|
|
|
34
|
+
# Register a protocol handler for a given ALPN protocol name.
|
|
35
|
+
# @parameter name [String] The ALPN protocol name.
|
|
36
|
+
# @parameter protocol [Class] The protocol class to handle connections.
|
|
37
|
+
# @parameter options [Hash] Options to pass when creating client or server instances.
|
|
31
38
|
def add(name, protocol, **options)
|
|
32
39
|
@handlers[name] = protocol
|
|
33
40
|
@options[protocol] = options
|
|
@@ -15,27 +15,35 @@ module Async
|
|
|
15
15
|
class RequestFailed < StandardError
|
|
16
16
|
end
|
|
17
17
|
|
|
18
|
-
#
|
|
18
|
+
# An incoming HTTP request generated by server protocol implementations.
|
|
19
19
|
class Request < ::Protocol::HTTP::Request
|
|
20
|
+
# @returns [Connection | Nil] The underlying protocol connection.
|
|
20
21
|
def connection
|
|
21
22
|
nil
|
|
22
23
|
end
|
|
23
24
|
|
|
25
|
+
# @returns [Boolean] Whether this request supports connection hijacking.
|
|
24
26
|
def hijack?
|
|
25
27
|
false
|
|
26
28
|
end
|
|
27
29
|
|
|
30
|
+
# Write an interim (1xx) response back to the client.
|
|
31
|
+
# @parameter status [Integer] The interim HTTP status code.
|
|
32
|
+
# @parameter headers [Hash | Nil] Optional headers to include.
|
|
28
33
|
def write_interim_response(status, headers = nil)
|
|
29
34
|
end
|
|
30
35
|
|
|
36
|
+
# @returns [Protocol::HTTP::Peer | Nil] The peer associated with this connection.
|
|
31
37
|
def peer
|
|
32
38
|
self.connection&.peer
|
|
33
39
|
end
|
|
34
40
|
|
|
41
|
+
# @returns [Addrinfo | Nil] The remote address of the peer.
|
|
35
42
|
def remote_address
|
|
36
43
|
self.peer&.address
|
|
37
44
|
end
|
|
38
45
|
|
|
46
|
+
# @returns [String] A string representation of the request.
|
|
39
47
|
def inspect
|
|
40
48
|
"#<#{self.class}:0x#{self.object_id.to_s(16)} method=#{method} path=#{path} version=#{version}>"
|
|
41
49
|
end
|
|
@@ -10,24 +10,29 @@ require_relative "../body/writable"
|
|
|
10
10
|
module Async
|
|
11
11
|
module HTTP
|
|
12
12
|
module Protocol
|
|
13
|
-
#
|
|
13
|
+
# An HTTP response received from a server via client protocol implementations.
|
|
14
14
|
class Response < ::Protocol::HTTP::Response
|
|
15
|
+
# @returns [Connection | Nil] The underlying protocol connection.
|
|
15
16
|
def connection
|
|
16
17
|
nil
|
|
17
18
|
end
|
|
18
19
|
|
|
20
|
+
# @returns [Boolean] Whether this response supports connection hijacking.
|
|
19
21
|
def hijack?
|
|
20
22
|
false
|
|
21
23
|
end
|
|
22
24
|
|
|
25
|
+
# @returns [Protocol::HTTP::Peer | Nil] The peer associated with this connection.
|
|
23
26
|
def peer
|
|
24
27
|
self.connection&.peer
|
|
25
28
|
end
|
|
26
29
|
|
|
30
|
+
# @returns [Addrinfo | Nil] The remote address of the peer.
|
|
27
31
|
def remote_address
|
|
28
32
|
self.peer&.remote_address
|
|
29
33
|
end
|
|
30
34
|
|
|
35
|
+
# @returns [String] A string representation of the response.
|
|
31
36
|
def inspect
|
|
32
37
|
"#<#{self.class}:0x#{self.object_id.to_s(16)} status=#{status}>"
|
|
33
38
|
end
|