protocol-http 0.71.0 → 0.72.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.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 5e4555d4a7ae53c17ce8bd6ccf545bd01d6eeafdf4ee288ddfb3f2aae76a3327
4
- data.tar.gz: 7808028142ff2afc20f5fd7a5a1476e74c17fa7a847effaf924253d77858f1d3
3
+ metadata.gz: 65ff75ab6759c365978c021f9f35150b4d5fa7a7b2bba8783041afda704607bf
4
+ data.tar.gz: b234c9cce9f99d7c4313fde4d2b4b15bf10a074875efc358d4b5fb2bad19e27d
5
5
  SHA512:
6
- metadata.gz: c724bfa82172160c4982791c20c707e6b75fdef0bef4155da339ec092e2b86c16d92da18e540ee8254844766750fe7c2b7896a2cae4fb2da155385102ff8d9b7
7
- data.tar.gz: 7b965c45ef273d6dac8df08ead9282bc9f35a70d0dc3100739b2bc331ef690d5b532f7c1a8c74d547ba628e89a9291e945f3fd8a2a6ce42aab81e94454f8fff7
6
+ metadata.gz: 16529f44e3cfde0d49afa69d08d64570fe165af759ea2d0d74aef74363768569d8af7ec2008c1a67ac8ceadae655b719d4d6aa9a28c628633d31a1197b186cdd
7
+ data.tar.gz: a7858b7d471e34772eb722d03af12fa7ec077deff39c9ce94bd9fff3146e0453fe1cab0823479a0ff56dfd662c10fdf856906e37ce5892bc2843ee002a79f27c
checksums.yaml.gz.sig CHANGED
Binary file
@@ -259,6 +259,23 @@ content = uppercase.join # => "HELLO WORLD"
259
259
 
260
260
  ## Life-cycle
261
261
 
262
+ Bodies model application-facing streams. Their close operations describe what the application will do next; protocol implementations are responsible for mapping those operations to the wire protocol safely.
263
+
264
+ ### Directional Closure
265
+
266
+ Request and response bodies are independent, so a bidirectional {ruby Protocol::HTTP::Body::Stream} can close either direction without implicitly closing the other:
267
+
268
+ | Operation | Application-level meaning | Typical protocol consequence |
269
+ | --- | --- | --- |
270
+ | `read` returns `nil` | The peer or producer completed the input normally. | The inbound body has reached end-of-stream. |
271
+ | `close_read` before end-of-stream | The application will not consume the remaining input, but may continue writing. | Discard unread data, terminate the exchange, or make the connection non-reusable. |
272
+ | `close_write` without an error | The application has finished producing output, but may continue reading. | Preserve previously written data and send a normal end-of-stream. |
273
+ | `close` without an error | The application has finished with both directions. | Complete or terminate the exchange without reporting an application error. |
274
+ | `close` with an error | The application cannot continue the exchange successfully. | Propagate the error or terminate the exchange using an appropriate protocol error. |
275
+ | `discard` | Consume input without processing it. | Prefer preserving the exchange or connection for reuse. |
276
+
277
+ These operations update local application-facing state. Returning from a close operation does not guarantee that the peer has observed it or that the underlying transport has been closed synchronously. Those details depend on the protocol and may be completed later.
278
+
262
279
  ### Initialization
263
280
 
264
281
  Bodies are typically initialized with the data they need to process. For example:
@@ -1,7 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  # Released under the MIT License.
4
- # Copyright, 2019-2024, by Samuel Williams.
4
+ # Copyright, 2019-2026, by Samuel Williams.
5
5
  # Copyright, 2023, by Bruno Sutic.
6
6
 
7
7
  require_relative "stream"
@@ -22,7 +22,11 @@ module Protocol
22
22
  class Readable
23
23
  # Close the stream immediately. After invoking this method, the stream should be considered closed, and all internal resources should be released.
24
24
  #
25
- # If an error occured while handling the output, it can be passed as an argument. This may be propagated to the client, for example the client may be informed that the stream was not fully read correctly.
25
+ # Closing the stream before it reaches end-of-file abandons any remaining input. The protocol implementation must account for that unread data before the associated exchange or connection can be reused. Depending on the protocol, it may discard the remaining data, terminate the exchange, or make the connection non-reusable. Use {discard} when preserving the exchange or connection is preferred.
26
+ #
27
+ # When closing before end-of-file, omitting the error represents deliberate application-level abandonment, not a protocol failure.
28
+ #
29
+ # If an error occurred while handling the output, it can be passed as an argument. This may be propagated to the client, for example the client may be informed that the stream was not fully read correctly.
26
30
  #
27
31
  # Invoking {read} after {close} will return `nil`.
28
32
  #
@@ -1,7 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  # Released under the MIT License.
4
- # Copyright, 2019-2025, by Samuel Williams.
4
+ # Copyright, 2019-2026, by Samuel Williams.
5
5
  # Copyright, 2023, by Genki Takiuchi.
6
6
  # Copyright, 2025, by William T. Nelson.
7
7
 
@@ -347,7 +347,13 @@ module Protocol
347
347
  def flush
348
348
  end
349
349
 
350
- # Close the input body.
350
+ # Close the application-facing input body. This does not close the output body, which may continue to be written independently.
351
+ #
352
+ # If the input has not reached end-of-file, any remaining data is abandoned. The protocol implementation must ensure that unread data cannot interfere with subsequent exchanges. Depending on the protocol, it may discard the remaining data, terminate the current exchange, or make the connection non-reusable.
353
+ #
354
+ # Closing without an error represents orderly application-level abandonment, not a protocol failure.
355
+ #
356
+ # This method is idempotent. After the first call, subsequent calls have no effect.
351
357
  #
352
358
  # If, while processing the data that was read from this stream, an error is encountered, it should be passed to this method.
353
359
  #
@@ -362,7 +368,11 @@ module Protocol
362
368
  end
363
369
  end
364
370
 
365
- # Close the output body.
371
+ # Close the application-facing output body. This does not close the input body, which may continue to be read independently.
372
+ #
373
+ # Closing without an error indicates that no more output will be produced. Previously written data remains part of the output and should be followed by a normal end-of-stream from the protocol implementation. If an error is provided, the protocol implementation may terminate the exchange instead.
374
+ #
375
+ # This method is idempotent. After the first call, subsequent calls have no effect.
366
376
  #
367
377
  # If, while generating the data that is written to this stream, an error is encountered, it should be passed to this method.
368
378
  #
@@ -377,6 +387,10 @@ module Protocol
377
387
 
378
388
  # Close the input and output bodies.
379
389
  #
390
+ # Closing without an error represents orderly completion or abandonment of both application-facing directions, not cancellation. If the peer has not completed the exchange, the protocol implementation may need to terminate it without reporting an application error.
391
+ #
392
+ # Repeated calls are safe; each underlying direction will be closed at most once.
393
+ #
380
394
  # @parameter error [Exception | Nil] The error that caused this stream to be closed, if any.
381
395
  def close(error = nil)
382
396
  self.close_read(error)
@@ -15,7 +15,7 @@ module Protocol
15
15
  #
16
16
  # In some cases, it's advantageous to directly read and write to the underlying stream if possible. For example, HTTP/1 upgrade requests, WebSockets, and similar. To handle that case, response bodies can implement {stream?} and return `true`. When {stream?} returns true, the body **should** be consumed by calling `call(stream)`. Server implementations may choose to always invoke `call(stream)` if it's efficient to do so. Bodies that don't support it will fall back to using {each}.
17
17
  #
18
- # When invoking `call(stream)`, the stream can be read from and written to, and closed. However, the stream is only guaranteed to be open for the duration of the `call(stream)` call. Once the method returns, the stream **should** be closed by the server.
18
+ # Invoking `call(stream)` transfers ownership of the stream to the called body. The body may read from and write to the stream, and **must** close it before returning. The caller must consider the stream closed and unusable after `call(stream)` returns.
19
19
  module Streamable
20
20
  # Generate a new streaming request body using the given block to generate the body.
21
21
  #
@@ -113,7 +113,7 @@ module Protocol
113
113
  @output.read
114
114
  end
115
115
 
116
- # Invoke the block with the given stream. The block can read and write to the stream, and must close the stream when finishing.
116
+ # Invoke the block with the given stream. The block owns the stream for the duration of the call and must close it before returning.
117
117
  #
118
118
  # @parameter stream [Stream] The stream to read and write to.
119
119
  def call(stream)
@@ -22,15 +22,17 @@ module Protocol
22
22
  # @deprecated Use {RefusedError} instead.
23
23
  RequestRefusedError = RefusedError
24
24
 
25
- # Represents a bad request error (as opposed to a server error).
26
- # This is used to indicate that the request was malformed or invalid.
25
+ # Marks errors which may indicate a malformed or invalid request when raised while processing an incoming request.
27
26
  module BadRequest
28
27
  end
29
28
 
30
- # Raised when a singleton (e.g. `content-length`) header is duplicated in a request or response.
31
- class DuplicateHeaderError < Error
29
+ # Raised when an HTTP header is malformed or invalid. When raised while processing an incoming request, it may be treated as a bad request.
30
+ class InvalidHeaderError < Error
32
31
  include BadRequest
33
-
32
+ end
33
+
34
+ # Raised when a singleton (e.g. `content-length`) header is duplicated in a request or response.
35
+ class DuplicateHeaderError < InvalidHeaderError
34
36
  # @parameter key [String] The header key that was duplicated.
35
37
  def initialize(key, existing_value, new_value)
36
38
  super("Duplicate singleton header key: #{key.inspect}")
@@ -62,9 +64,7 @@ module Protocol
62
64
  end
63
65
 
64
66
  # Raised when an invalid trailer header is encountered in headers.
65
- class InvalidTrailerError < Error
66
- include BadRequest
67
-
67
+ class InvalidTrailerError < InvalidHeaderError
68
68
  # @parameter key [String] The trailer key that is invalid.
69
69
  def initialize(key)
70
70
  super("Invalid trailer key: #{key.inspect}")
@@ -23,7 +23,7 @@ module Protocol
23
23
  (?=,|\z) # Match until a comma or end of string
24
24
  /x
25
25
 
26
- ParseError = Class.new(Error)
26
+ ParseError = Class.new(InvalidHeaderError)
27
27
 
28
28
  MEDIA_RANGE = /\A(?<type>#{TOKEN})\/(?<subtype>#{TOKEN})(?<parameters>.*)\z/
29
29
 
@@ -12,7 +12,7 @@ module Protocol
12
12
  module Header
13
13
  # The `accept-charset` header represents a list of character sets that the client can accept.
14
14
  class AcceptCharset < Split
15
- ParseError = Class.new(Error)
15
+ ParseError = Class.new(InvalidHeaderError)
16
16
 
17
17
  # https://tools.ietf.org/html/rfc7231#section-5.3.3
18
18
  CHARSET = /\A(?<name>#{TOKEN})(;q=(?<q>#{QVALUE}))?\z/
@@ -12,7 +12,7 @@ module Protocol
12
12
  module Header
13
13
  # The `accept-encoding` header represents a list of encodings that the client can accept.
14
14
  class AcceptEncoding < Split
15
- ParseError = Class.new(Error)
15
+ ParseError = Class.new(InvalidHeaderError)
16
16
 
17
17
  # https://tools.ietf.org/html/rfc7231#section-5.3.1
18
18
  QVALUE = /0(\.[0-9]{0,3})?|1(\.[0]{0,3})?/
@@ -12,7 +12,7 @@ module Protocol
12
12
  module Header
13
13
  # The `accept-language` header represents a list of languages that the client can accept.
14
14
  class AcceptLanguage < Split
15
- ParseError = Class.new(Error)
15
+ ParseError = Class.new(InvalidHeaderError)
16
16
 
17
17
  # https://tools.ietf.org/html/rfc3066#section-2.1
18
18
  NAME = /\*|[A-Z]{1,8}(-[A-Z0-9]{1,8})*/i
@@ -23,7 +23,7 @@ module Protocol
23
23
  # # => "sha-256=X48E9qOokqqrvdts8nOJRJN3OWDUoyWxBf7kbu9DBPE=, md5=9bb58f26192e4ba00f01e2e7b136bbd8"
24
24
  # ```
25
25
  class Digest < Split
26
- ParseError = Class.new(Error)
26
+ ParseError = Class.new(InvalidHeaderError)
27
27
 
28
28
  # https://tools.ietf.org/html/rfc3230#section-4.3.2
29
29
  ENTRY = /\A(?<algorithm>[a-zA-Z0-9][a-zA-Z0-9\-]*)\s*=\s*(?<value>.*)\z/
@@ -10,7 +10,7 @@ module Protocol
10
10
  module Header
11
11
  # Represents a `range` request header.
12
12
  class Range
13
- ParseError = Class.new(Error)
13
+ ParseError = Class.new(InvalidHeaderError)
14
14
 
15
15
  TOKEN = /[!#$%&'*+\-.0-9A-Z^_`a-z|~]+/
16
16
  HEADER = /\A(?<unit>#{TOKEN})=(?<ranges>.*)\z/
@@ -23,7 +23,7 @@ module Protocol
23
23
  # # => "db;dur=53.2, cache;dur=12.1;desc=\"Redis lookup\""
24
24
  # ```
25
25
  class ServerTiming < Split
26
- ParseError = Class.new(Error)
26
+ ParseError = Class.new(InvalidHeaderError)
27
27
 
28
28
  # https://www.w3.org/TR/server-timing/
29
29
  METRIC = /\A(?<name>[a-zA-Z0-9][a-zA-Z0-9_\-]*)(;(?<parameters>.*))?\z/
@@ -14,7 +14,7 @@ module Protocol
14
14
  #
15
15
  # The `te` header allows a client to indicate which transfer encodings it can handle, and in what order of preference using quality factors.
16
16
  class TE < Split
17
- ParseError = Class.new(Error)
17
+ ParseError = Class.new(InvalidHeaderError)
18
18
 
19
19
  # Transfer encoding token pattern
20
20
  TOKEN = /[!#$%&'*+\-.0-9A-Z^_`a-z|~]+/
@@ -5,6 +5,6 @@
5
5
 
6
6
  module Protocol
7
7
  module HTTP
8
- VERSION = "0.71.0"
8
+ VERSION = "0.72.0"
9
9
  end
10
10
  end
data/readme.md CHANGED
@@ -30,6 +30,16 @@ Please see the [project documentation](https://socketry.github.io/protocol-http/
30
30
 
31
31
  Please see the [project releases](https://socketry.github.io/protocol-http/releases/index) for all releases.
32
32
 
33
+ ### v0.72.0
34
+
35
+ - Clarified body stream lifecycle and ownership, including the directional semantics of `Protocol::HTTP::Body::Stream#close_read`, `#close_write`, and `#close`, how premature input closure affects the associated HTTP exchange, and ownership of streams passed to `Streamable#call`.
36
+ - Introduce `Protocol::HTTP::InvalidHeaderError` for malformed or invalid headers, which can be treated as bad requests.
37
+
38
+ ### v0.71.0
39
+
40
+ - Parse all cookie pairs from `Cookie` header fields, including multiple semicolon-separated pairs within each field.
41
+ - Preserve `Set-Cookie` parsing as one cookie plus attributes/directives per header field.
42
+
33
43
  ### v0.70.0
34
44
 
35
45
  - Add stable preference ordering for weighted `Accept`, `Accept-Charset`, `Accept-Encoding`, `Accept-Language`, and `TE` values.
@@ -64,14 +74,6 @@ Please see the [project releases](https://socketry.github.io/protocol-http/relea
64
74
 
65
75
  - Add support for the HTTP `QUERY` method.
66
76
 
67
- ### v0.62.1
68
-
69
- - Fix handling of `Stream#read(0)`, it must return a mutable string (or clear the given buffer).
70
-
71
- ### v0.61.0
72
-
73
- - Introduce `Protocol::HTTP::RefusedError` for indicating a stream or request was refused before processing and can be safely retried. `RequestRefusedError` is provided as an alias for backwards compatibility.
74
-
75
77
  ## See Also
76
78
 
77
79
  - [protocol-http1](https://github.com/socketry/protocol-http1) — HTTP/1 client/server implementation using this
data/releases.md CHANGED
@@ -1,5 +1,15 @@
1
1
  # Releases
2
2
 
3
+ ## v0.72.0
4
+
5
+ - Clarified body stream lifecycle and ownership, including the directional semantics of `Protocol::HTTP::Body::Stream#close_read`, `#close_write`, and `#close`, how premature input closure affects the associated HTTP exchange, and ownership of streams passed to `Streamable#call`.
6
+ - Introduce `Protocol::HTTP::InvalidHeaderError` for malformed or invalid headers, which can be treated as bad requests.
7
+
8
+ ## v0.71.0
9
+
10
+ - Parse all cookie pairs from `Cookie` header fields, including multiple semicolon-separated pairs within each field.
11
+ - Preserve `Set-Cookie` parsing as one cookie plus attributes/directives per header field.
12
+
3
13
  ## v0.70.0
4
14
 
5
15
  - Add stable preference ordering for weighted `Accept`, `Accept-Charset`, `Accept-Encoding`, `Accept-Language`, and `TE` values.
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.71.0
4
+ version: 0.72.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Samuel Williams
metadata.gz.sig CHANGED
Binary file