protocol-http 0.28.2 → 0.30.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: 3bf15de4010337ee2c56c39bf6955ccfc7e6ae623426e31bface95cf15c8ef26
4
- data.tar.gz: f8d4d30147c5402ca5003195b5b2d6e1688b47bf1a56ee23131b420952efac68
3
+ metadata.gz: 5365fcaf109db92e1db6bc6389e881881f7ec19c4f1bcd747b16d72698e89a7c
4
+ data.tar.gz: 11f68df5f5b9370579764e475a0fe1579c3b4d2f06aaed80efb0cb5fa5c6e392
5
5
  SHA512:
6
- metadata.gz: 236c830db8716243e3c9d7eec818c4b6a6b83ab399efe131b3475a4a1aac9e57dbe7ad809d7e0b5f4715c40371c8b2adbd7ebb6c16571ef5397fc209f08244d8
7
- data.tar.gz: 35574f913cbaaee7f8ce06f8855843dd517f9d29ed898852c53c2ef12003fa0c62308ecd4e4dc3d04a7d34b4171ce64bb87b8d09779cfa69533f0da1c74b1be8
6
+ metadata.gz: 05f867a38d373f1491630bc5a9bf30401b8ac0fa41cec5392787a1fb916d42fa0f14ff8b80078b89246ab5bbdaae40b2dd67b77302571e3cb0803477e48ead4f
7
+ data.tar.gz: dbb7ac119a2d46c5150b8a274c89280582d69f64718849a7537d0a4b876f40c0b9a36c47fc3e2d6f27a244e12465d4ba372a3e4c87c7d0e1c738c0757cd672d5
checksums.yaml.gz.sig CHANGED
Binary file
@@ -11,25 +11,29 @@ module Protocol
11
11
  module Body
12
12
  # A body which buffers all it's contents.
13
13
  class Buffered < Readable
14
- # Wraps an array into a buffered body.
14
+ # Tries to wrap an object in a {Buffered} instance.
15
15
  #
16
16
  # For compatibility, also accepts anything that behaves like an `Array(String)`.
17
17
  #
18
18
  # @parameter body [String | Array(String) | Readable | nil] the body to wrap.
19
19
  # @returns [Readable | nil] the wrapped body or nil if nil was given.
20
- def self.wrap(body)
21
- if body.is_a?(Readable)
22
- return body
23
- elsif body.is_a?(Array)
24
- return self.new(body)
25
- elsif body.is_a?(String)
26
- return self.new([body])
27
- elsif body
28
- return self.for(body)
20
+ def self.wrap(object)
21
+ if object.is_a?(Readable)
22
+ return object
23
+ elsif object.is_a?(Array)
24
+ return self.new(object)
25
+ elsif object.is_a?(String)
26
+ return self.new([object])
27
+ elsif object
28
+ return self.read(object)
29
29
  end
30
30
  end
31
31
 
32
- def self.for(body)
32
+ # Read the entire body into a buffered representation.
33
+ #
34
+ # @parameter body [Readable] the body to read.
35
+ # @returns [Buffered] the buffered body.
36
+ def self.read(body)
33
37
  chunks = []
34
38
 
35
39
  body.each do |chunk|
@@ -77,8 +81,14 @@ module Protocol
77
81
  @chunks << chunk
78
82
  end
79
83
 
84
+ def rewindable?
85
+ true
86
+ end
87
+
80
88
  def rewind
81
89
  @index = 0
90
+
91
+ return true
82
92
  end
83
93
 
84
94
  def inspect
@@ -24,6 +24,14 @@ module Protocol
24
24
  @callback = callback
25
25
  end
26
26
 
27
+ def rewindable?
28
+ false
29
+ end
30
+
31
+ def rewind
32
+ false
33
+ end
34
+
27
35
  def finish
28
36
  super.tap do
29
37
  if @callback
@@ -29,6 +29,14 @@ module Protocol
29
29
  false
30
30
  end
31
31
 
32
+ def rewindable?
33
+ false
34
+ end
35
+
36
+ def rewind
37
+ false
38
+ end
39
+
32
40
  def length
33
41
  nil
34
42
  end
@@ -58,7 +66,7 @@ module Protocol
58
66
  # @returns [Buffered] The buffered body.
59
67
  def finish
60
68
  # Internally, this invokes `self.each` which then invokes `self.close`.
61
- Buffered.for(self)
69
+ Buffered.read(self)
62
70
  end
63
71
 
64
72
  # Enumerate all chunks until finished, then invoke `#close`.
@@ -11,6 +11,16 @@ module Protocol
11
11
  module Body
12
12
  # A body which buffers all it's contents as it is `#read`.
13
13
  class Rewindable < Wrapper
14
+ def self.wrap(message)
15
+ if body = message.body
16
+ if body.rewindable?
17
+ body
18
+ else
19
+ message.body = self.new(body)
20
+ end
21
+ end
22
+ end
23
+
14
24
  def initialize(body)
15
25
  super(body)
16
26
 
@@ -26,7 +36,9 @@ module Protocol
26
36
  (@index < @chunks.size) || super
27
37
  end
28
38
 
29
- # A rewindable body wraps some other body. Convert it to a buffered body
39
+ # A rewindable body wraps some other body. Convert it to a buffered body. The buffered body will share the same chunks as the rewindable body.
40
+ #
41
+ # @returns [Buffered] the buffered body.
30
42
  def buffered
31
43
  Buffered.new(@chunks)
32
44
  end
@@ -54,6 +66,10 @@ module Protocol
54
66
  @index = 0
55
67
  end
56
68
 
69
+ def rewindable?
70
+ true
71
+ end
72
+
57
73
  def inspect
58
74
  "\#<#{self.class} #{@index}/#{@chunks.size} chunks read>"
59
75
  end
@@ -10,6 +10,10 @@ module Protocol
10
10
  module Body
11
11
  # Wrapping body instance. Typically you'd override `#read`.
12
12
  class Wrapper < Readable
13
+ # Wrap the body of the given message in a new instance of this class.
14
+ #
15
+ # @parameter message [Request | Response] the message to wrap.
16
+ # @returns [Wrapper | nil] the wrapped body or nil if the body was nil.
13
17
  def self.wrap(message)
14
18
  if body = message.body
15
19
  message.body = self.new(body)
@@ -42,6 +46,14 @@ module Protocol
42
46
  @body.ready?
43
47
  end
44
48
 
49
+ def rewind
50
+ @body.rewind
51
+ end
52
+
53
+ def rewindable?
54
+ @body.rewindable?
55
+ end
56
+
45
57
  def length
46
58
  @body.length
47
59
  end
@@ -70,9 +70,9 @@ module Protocol
70
70
  end
71
71
 
72
72
  self.each do |name, value|
73
- define_method(name) do |location, headers = nil, body = nil|
73
+ define_method(name) do |location, *arguments, **options|
74
74
  self.call(
75
- Request[value, location.to_s, Headers[headers], body]
75
+ Request[value, location.to_s, *arguments, **options]
76
76
  )
77
77
  end
78
78
  end
@@ -25,7 +25,7 @@ module Protocol
25
25
  class Request
26
26
  prepend Body::Reader
27
27
 
28
- def initialize(scheme = nil, authority = nil, method = nil, path = nil, version = nil, headers = Headers.new, body = nil, protocol = nil)
28
+ def initialize(scheme = nil, authority = nil, method = nil, path = nil, version = nil, headers = Headers.new, body = nil, protocol = nil, interim_response = nil)
29
29
  @scheme = scheme
30
30
  @authority = authority
31
31
  @method = method
@@ -34,6 +34,7 @@ module Protocol
34
34
  @headers = headers
35
35
  @body = body
36
36
  @protocol = protocol
37
+ @interim_response = interim_response
37
38
  end
38
39
 
39
40
  # @attribute [String] the request scheme, usually `"http"` or `"https"`.
@@ -60,11 +61,30 @@ module Protocol
60
61
  # @attribute [String | Array(String) | Nil] the request protocol, usually empty, but occasionally `"websocket"` or `"webtransport"`. In HTTP/1, it is used to request a connection upgrade, and in HTTP/2 it is used to indicate a specfic protocol for the stream.
61
62
  attr_accessor :protocol
62
63
 
64
+ # @attribute [Proc] a callback which is called when an interim response is received.
65
+ attr_accessor :interim_response
66
+
63
67
  # Send the request to the given connection.
64
68
  def call(connection)
65
69
  connection.call(self)
66
70
  end
67
71
 
72
+ # Send an interim response back to the origin of this request, if possible.
73
+ def send_interim_response(status, headers)
74
+ @interim_response&.call(status, headers)
75
+ end
76
+
77
+ def on_interim_response(&block)
78
+ if interim_response = @interim_response
79
+ @interim_response = ->(status, headers) do
80
+ block.call(status, headers)
81
+ interim_response.call(status, headers)
82
+ end
83
+ else
84
+ @interim_response = block
85
+ end
86
+ end
87
+
68
88
  # Whether this is a HEAD request: no body is expected in the response.
69
89
  def head?
70
90
  @method == Methods::HEAD
@@ -81,11 +101,11 @@ module Protocol
81
101
  # @parameter path [String] The path, e.g. `"/index.html"`, `"/search?q=hello"`, etc.
82
102
  # @parameter headers [Hash] The headers, e.g. `{"accept" => "text/html"}`, etc.
83
103
  # @parameter body [String | Array(String) | Body::Readable] The body, e.g. `"Hello, World!"`, etc. See {Body::Buffered.wrap} for more information about .
84
- def self.[](method, path, headers = nil, body = nil)
104
+ def self.[](method, path, _headers = nil, _body = nil, scheme: nil, authority: nil, headers: _headers, body: _body, protocol: nil, interim_response: nil)
85
105
  body = Body::Buffered.wrap(body)
86
- headers = ::Protocol::HTTP::Headers[headers]
106
+ headers = Headers[headers]
87
107
 
88
- self.new(nil, nil, method, path, nil, headers, body)
108
+ self.new(scheme, authority, method, path, nil, headers, body, protocol, interim_response)
89
109
  end
90
110
 
91
111
  # Whether the request can be replayed without side-effects.
@@ -130,9 +130,9 @@ module Protocol
130
130
  # @parameter status [Integer] The HTTP status code, e.g. `200`, `404`, etc.
131
131
  # @parameter headers [Hash] The headers, e.g. `{"content-type" => "text/html"}`, etc.
132
132
  # @parameter body [String | Array(String) | Body::Readable] The body, e.g. `"Hello, World!"`, etc. See {Body::Buffered.wrap} for more information about .
133
- def self.[](status, headers = nil, body = nil, protocol = nil)
133
+ def self.[](status, _headers = nil, _body = nil, headers: _headers, body: _body, protocol: nil)
134
134
  body = Body::Buffered.wrap(body)
135
- headers = ::Protocol::HTTP::Headers[headers]
135
+ headers = Headers[headers]
136
136
 
137
137
  self.new(nil, status, headers, body, protocol)
138
138
  end
@@ -5,6 +5,6 @@
5
5
 
6
6
  module Protocol
7
7
  module HTTP
8
- VERSION = "0.28.2"
8
+ VERSION = "0.30.0"
9
9
  end
10
10
  end
data/readme.md CHANGED
@@ -18,6 +18,25 @@ Please see the [project documentation](https://socketry.github.io/protocol-http/
18
18
 
19
19
  - [Design Overview](https://socketry.github.io/protocol-http/guides/design-overview/index) - This guide explains the high level design of `protocol-http` in the context of wider design patterns that can be used to implement HTTP clients and servers.
20
20
 
21
+ ## Releases
22
+
23
+ Please see the [project releases](https://socketry.github.io/protocol-http/releases/index) for all releases.
24
+
25
+ ### Unreleased
26
+
27
+ - [`Request[]` and `Response[]` Keyword Arguments](https://socketry.github.io/protocol-http/releases/index#request[]-and-response[]-keyword-arguments)
28
+ - [Interim Response Handling](https://socketry.github.io/protocol-http/releases/index#interim-response-handling)
29
+
30
+ ## See Also
31
+
32
+ - [protocol-http1](https://github.com/socketry/protocol-http1) — HTTP/1 client/server implementation using this
33
+ interface.
34
+ - [protocol-http2](https://github.com/socketry/protocol-http2) — HTTP/2 client/server implementation using this
35
+ interface.
36
+ - [async-http](https://github.com/socketry/async-http) — Asynchronous HTTP client and server, supporting multiple HTTP
37
+ protocols & TLS.
38
+ - [async-websocket](https://github.com/socketry/async-websocket) — Asynchronous client and server WebSockets.
39
+
21
40
  ## Contributing
22
41
 
23
42
  We welcome contributions to this project.
@@ -35,13 +54,3 @@ In order to protect users of this project, we require all contributors to comply
35
54
  ### Community Guidelines
36
55
 
37
56
  This project is best served by a collaborative and respectful environment. Treat each other professionally, respect differing viewpoints, and engage constructively. Harassment, discrimination, or harmful behavior is not tolerated. Communicate clearly, listen actively, and support one another. If any issues arise, please inform the project maintainers.
38
-
39
- ## See Also
40
-
41
- - [protocol-http1](https://github.com/socketry/protocol-http1) — HTTP/1 client/server implementation using this
42
- interface.
43
- - [protocol-http2](https://github.com/socketry/protocol-http2) — HTTP/2 client/server implementation using this
44
- interface.
45
- - [async-http](https://github.com/socketry/async-http) — Asynchronous HTTP client and server, supporting multiple HTTP
46
- protocols & TLS.
47
- - [async-websocket](https://github.com/socketry/async-websocket) — Asynchronous client and server WebSockets.
data/releases.md ADDED
@@ -0,0 +1,40 @@
1
+ # Releases
2
+
3
+ ## Unreleased
4
+
5
+ ### `Request[]` and `Response[]` Keyword Arguments
6
+
7
+ The `Request[]` and `Response[]` methods now support keyword arguments as a convenient way to set various positional arguments.
8
+
9
+ ```ruby
10
+ # Request keyword arguments:
11
+ client.get("/", headers: {"accept" => "text/html"}, authority: "example.com")
12
+
13
+ # Response keyword arguments:
14
+ def call(request)
15
+ return Response[200, headers: {"content-Type" => "text/html"}, body: "Hello, World!"]
16
+ ```
17
+
18
+ ### Interim Response Handling
19
+
20
+ The `Request` class now exposes a `#interim_response` attribute which can be used to handle interim responses both on the client side and server side.
21
+
22
+ On the client side, you can pass a callback using the `interim_response` keyword argument which will be invoked whenever an interim response is received:
23
+
24
+ ```ruby
25
+ client = ...
26
+ response = client.get("/index", interim_response: proc{|status, headers| ...})
27
+ ```
28
+
29
+ On the server side, you can send an interim response using the `#send_interim_response` method:
30
+
31
+ ```ruby
32
+ def call(request)
33
+ if request.headers["expect"] == "100-continue"
34
+ # Send an interim response:
35
+ request.send_interim_response(100)
36
+ end
37
+
38
+ # ...
39
+ end
40
+ ```
data.tar.gz.sig CHANGED
Binary file
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: protocol-http
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.28.2
4
+ version: 0.30.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Samuel Williams
@@ -47,7 +47,7 @@ cert_chain:
47
47
  Q2K9NVun/S785AP05vKkXZEFYxqG6EW012U4oLcFl5MySFajYXRYbuUpH6AY+HP8
48
48
  voD0MPg1DssDLKwXyt1eKD/+Fq0bFWhwVM/1XiAXL7lyYUyOq24KHgQ2Csg=
49
49
  -----END CERTIFICATE-----
50
- date: 2024-08-21 00:00:00.000000000 Z
50
+ date: 2024-08-30 00:00:00.000000000 Z
51
51
  dependencies: []
52
52
  description:
53
53
  email:
@@ -93,6 +93,7 @@ files:
93
93
  - lib/protocol/http/version.rb
94
94
  - license.md
95
95
  - readme.md
96
+ - releases.md
96
97
  homepage: https://github.com/socketry/protocol-http
97
98
  licenses:
98
99
  - MIT
metadata.gz.sig CHANGED
Binary file