protocol-http 0.57.0 → 0.58.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: 46aa99ddc598b23eb7d9670a806ca18d21314fc7e4bb4e42305a4ac4bd92c868
4
- data.tar.gz: 0dd66a51205e73c5d5bd57fca401ce592f31a21fb09d1bc9126fb084b293d378
3
+ metadata.gz: 971bece5e113d3f945faf8b8ce44858e97f0e6282e3a264b6e6fca1e57c8e71c
4
+ data.tar.gz: 8d1139a2022dbe96710d7e0286a5c30c2384e14fcba3e0907428361b25c3a703
5
5
  SHA512:
6
- metadata.gz: 979dec4d8fedfce87917074576493ff6365e897bd6403f409d0ff862ed1517cb4cbfbc65b0f1c95ef83f77ceb20530e4a90131e0530c737fa7532886f6d431d5
7
- data.tar.gz: a52fbe05985d5d6d87d37c46b72cc71ff79b8d4023d765c79efaa5e947681dd53a1d10a083ff1e54ad47bd9358ddb7a8e8ede7313c7db36c4c7d88cfad52d3a8
6
+ metadata.gz: 69d17f997389daf2332cacc0d5ac407897e2dc2a45c3e329f23dfc9b18f7075b425e10c43fff27ff1728f64e92a6843e3291be5103e9ee8c72d1f810c2603c01
7
+ data.tar.gz: '09676f19f6b3bd4d6290b5dccd8f93d2c6409e82926fa74a9b21f39f4fa92df0d289ef8fac6b0460a9de2784d78d7114d262f9a5088f66518ef60b9d60a877f4'
checksums.yaml.gz.sig CHANGED
Binary file
data/context/streaming.md CHANGED
@@ -38,7 +38,7 @@ Async do
38
38
  Protocol::HTTP::Response[200, {}, output]
39
39
  end
40
40
 
41
- server_task = Async {server.run}
41
+ server_task = Async{server.run}
42
42
 
43
43
  client = Async::HTTP::Client.new(endpoint)
44
44
 
@@ -104,7 +104,7 @@ Async do
104
104
  Protocol::HTTP::Response[200, {}, output]
105
105
  end
106
106
 
107
- server_task = Async {server.run}
107
+ server_task = Async{server.run}
108
108
 
109
109
  client = Async::HTTP::Client.new(endpoint)
110
110
 
@@ -0,0 +1,30 @@
1
+ # frozen_string_literal: true
2
+
3
+ # Released under the MIT License.
4
+ # Copyright, 2025, by Samuel Williams.
5
+
6
+ require_relative "split"
7
+
8
+ module Protocol
9
+ module HTTP
10
+ module Header
11
+ # Represents generic or custom headers that can be used in trailers.
12
+ #
13
+ # This class is used as the default policy for headers not explicitly defined in the POLICY hash.
14
+ #
15
+ # It allows generic headers to be used in HTTP trailers, which is important for:
16
+ # - Custom application headers.
17
+ # - gRPC status headers (grpc-status, grpc-message).
18
+ # - Headers used by proxies and middleware.
19
+ # - Future HTTP extensions.
20
+ class Generic < Split
21
+ # Whether this header is acceptable in HTTP trailers.
22
+ # Generic headers are allowed in trailers by default to support extensibility.
23
+ # @returns [Boolean] `true`, generic headers are allowed in trailers.
24
+ def self.trailer?
25
+ true
26
+ end
27
+ end
28
+ end
29
+ end
30
+ end
@@ -20,6 +20,7 @@ require_relative "header/priority"
20
20
  require_relative "header/trailer"
21
21
  require_relative "header/server_timing"
22
22
  require_relative "header/digest"
23
+ require_relative "header/generic"
23
24
 
24
25
  require_relative "header/accept"
25
26
  require_relative "header/accept_charset"
@@ -158,7 +159,26 @@ module Protocol
158
159
  return trailer(&block)
159
160
  end
160
161
 
162
+ # Enumerate all the headers in the header, if there are any.
163
+ #
164
+ # @yields {|key, value| ...} The header key and value.
165
+ # @parameter key [String] The header key.
166
+ # @parameter value [String] The raw header value.
167
+ def header(&block)
168
+ return to_enum(:header) unless block_given?
169
+
170
+ if @tail and @tail < @fields.size
171
+ @fields.first(@tail).each(&block)
172
+ else
173
+ @fields.each(&block)
174
+ end
175
+ end
176
+
161
177
  # Enumerate all headers in the trailer, if there are any.
178
+ #
179
+ # @yields {|key, value| ...} The header key and value.
180
+ # @parameter key [String] The header key.
181
+ # @parameter value [String] The raw header value.
162
182
  def trailer(&block)
163
183
  return to_enum(:trailer) unless block_given?
164
184
 
@@ -191,7 +211,7 @@ module Protocol
191
211
  # @parameter key [String] The header key.
192
212
  # @parameter value [String] The raw header value.
193
213
  def each(&block)
194
- self.to_h.each(&block)
214
+ @fields.each(&block)
195
215
  end
196
216
 
197
217
  # @returns [Boolean] Whether the headers include the specified key.
@@ -227,9 +247,18 @@ module Protocol
227
247
  #
228
248
  # @parameter key [String] the header key.
229
249
  # @parameter value [String] the header value to assign.
230
- def add(key, value)
250
+ # @parameter trailer [Boolean] whether this header is being added as a trailer.
251
+ def add(key, value, trailer: self.trailer?)
231
252
  value = value.to_s
232
253
 
254
+ if trailer
255
+ policy = @policy[key.downcase]
256
+
257
+ if !policy or !policy.trailer?
258
+ raise InvalidTrailerError, key
259
+ end
260
+ end
261
+
233
262
  if @indexed
234
263
  merge_into(@indexed, key.downcase, value)
235
264
  end
@@ -297,6 +326,10 @@ module Protocol
297
326
  end
298
327
 
299
328
  # The policy for various headers, including how they are merged and normalized.
329
+ #
330
+ # A policy may be `false` to indicate that the header may only be specified once and is a simple string.
331
+ #
332
+ # Otherwise, the policy is a class which implements the header normalization logic, including `parse` and `coerce` class methods.
300
333
  POLICY = {
301
334
  # Headers which may only be specified once:
302
335
  "content-disposition" => false,
@@ -315,8 +348,11 @@ module Protocol
315
348
  "user-agent" => false,
316
349
  "trailer" => Header::Trailer,
317
350
 
318
- # Custom headers:
351
+ # Connection handling:
319
352
  "connection" => Header::Connection,
353
+ "upgrade" => Header::Split,
354
+
355
+ # Cache handling:
320
356
  "cache-control" => Header::CacheControl,
321
357
  "te" => Header::TE,
322
358
  "vary" => Header::Vary,
@@ -354,16 +390,21 @@ module Protocol
354
390
 
355
391
  # Accept headers:
356
392
  "accept" => Header::Accept,
393
+ "accept-ranges" => Header::Split,
357
394
  "accept-charset" => Header::AcceptCharset,
358
395
  "accept-encoding" => Header::AcceptEncoding,
359
396
  "accept-language" => Header::AcceptLanguage,
360
397
 
398
+ # Content negotiation headers:
399
+ "content-encoding" => Header::Split,
400
+ "content-range" => false,
401
+
361
402
  # Performance headers:
362
403
  "server-timing" => Header::ServerTiming,
363
404
 
364
405
  # Content integrity headers:
365
406
  "digest" => Header::Digest,
366
- }.tap{|hash| hash.default = Split}
407
+ }.tap{|hash| hash.default = Header::Generic}
367
408
 
368
409
  # Delete all header values for the given key, and return the merged value.
369
410
  #
@@ -403,25 +444,14 @@ module Protocol
403
444
  # @parameter hash [Hash] The hash to merge into.
404
445
  # @parameter key [String] The header key.
405
446
  # @parameter value [String] The raw header value.
406
- # @parameter trailer [Boolean] Whether this header is in the trailer section.
407
- protected def merge_into(hash, key, value, trailer = @tail)
447
+ protected def merge_into(hash, key, value)
408
448
  if policy = @policy[key]
409
- # Check if we're adding to trailers and this header is allowed:
410
- if trailer && !policy.trailer?
411
- raise InvalidTrailerError, key
412
- end
413
-
414
449
  if current_value = hash[key]
415
450
  current_value << value
416
451
  else
417
452
  hash[key] = policy.parse(value)
418
453
  end
419
454
  else
420
- # By default, headers are not allowed in trailers:
421
- if trailer
422
- raise InvalidTrailerError, key
423
- end
424
-
425
455
  if hash.key?(key)
426
456
  raise DuplicateHeaderError, key
427
457
  end
@@ -437,13 +467,14 @@ module Protocol
437
467
  # @returns [Hash] A hash table of `{key, value}` pairs.
438
468
  def to_h
439
469
  unless @indexed
440
- @indexed = {}
470
+ indexed = {}
441
471
 
442
- @fields.each_with_index do |(key, value), index|
443
- trailer = (@tail && index >= @tail)
444
-
445
- merge_into(@indexed, key.downcase, value, trailer)
472
+ @fields.each do |key, value|
473
+ merge_into(indexed, key.downcase, value)
446
474
  end
475
+
476
+ # Deferred assignment so that exceptions in `merge_into` don't leave us in an inconsistent state:
477
+ @indexed = indexed
447
478
  end
448
479
 
449
480
  return @indexed
@@ -5,6 +5,6 @@
5
5
 
6
6
  module Protocol
7
7
  module HTTP
8
- VERSION = "0.57.0"
8
+ VERSION = "0.58.0"
9
9
  end
10
10
  end
data/readme.md CHANGED
@@ -30,6 +30,12 @@ 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.58.0
34
+
35
+ - Move trailer validation to `Headers#add` method to ensure all additions are checked at the time of addition as this is a hard requirement.
36
+ - Introduce `Headers#header` method to enumerate only the main headers, excluding trailers. This can be used after invoking `Headers#trailer!` to avoid race conditions.
37
+ - Fix `Headers#to_h` so that indexed headers are not left in an inconsistent state if errors occur during processing.
38
+
33
39
  ### v0.57.0
34
40
 
35
41
  - Always use `#parse` when parsing header values from strings to ensure proper normalization and validation.
@@ -83,10 +89,6 @@ Please see the [project releases](https://socketry.github.io/protocol-http/relea
83
89
 
84
90
  - Add support for parsing `accept`, `accept-charset`, `accept-encoding` and `accept-language` headers into structured values.
85
91
 
86
- ### v0.46.0
87
-
88
- - Add support for `priority:` header.
89
-
90
92
  ## See Also
91
93
 
92
94
  - [protocol-http1](https://github.com/socketry/protocol-http1) — HTTP/1 client/server implementation using this
data/releases.md CHANGED
@@ -1,5 +1,11 @@
1
1
  # Releases
2
2
 
3
+ ## v0.58.0
4
+
5
+ - Move trailer validation to `Headers#add` method to ensure all additions are checked at the time of addition as this is a hard requirement.
6
+ - Introduce `Headers#header` method to enumerate only the main headers, excluding trailers. This can be used after invoking `Headers#trailer!` to avoid race conditions.
7
+ - Fix `Headers#to_h` so that indexed headers are not left in an inconsistent state if errors occur during processing.
8
+
3
9
  ## v0.57.0
4
10
 
5
11
  - Always use `#parse` when parsing header values from strings to ensure proper normalization and validation.
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.57.0
4
+ version: 0.58.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Samuel Williams
@@ -94,6 +94,7 @@ files:
94
94
  - lib/protocol/http/header/digest.rb
95
95
  - lib/protocol/http/header/etag.rb
96
96
  - lib/protocol/http/header/etags.rb
97
+ - lib/protocol/http/header/generic.rb
97
98
  - lib/protocol/http/header/multiple.rb
98
99
  - lib/protocol/http/header/priority.rb
99
100
  - lib/protocol/http/header/server_timing.rb
metadata.gz.sig CHANGED
@@ -1,4 +1,4 @@
1
- 1k ҟ$l�;�Iͥ�[��&��}L���D�R���m��p_�_>���\�šN��>��Ͱ���Dৎ�Z;���?�*�D���T�kveҩjVvE^N5��j��+�7��d��iӄW�'�Q�[J�)E&
2
- ��ա��D=H[��F�0����[�#K���V{U���� ��Y4��zm����[��GsEyFfix���
3
- yo�.y^�q=�b8 � D�{�g�No�4$�Is��K2_��KFc�6��A�;��������CvegAm��
4
- �VW�}�}�/X������y_[���m=�o�����9�3��_���õ�Kn�:�EEoS��
1
+ ^�eG�{d65o���u����abH��]��+�>�ҁg������j'���S��6
2
+ �nq�(�6j��:��|ʱ����"�p�tD磩.# ac�F����B&Za����^PYL���򌑓��^��w��(YjcRay�� ��ʃ�H|\�]�Μ]dAo08 �\Rî�;�@��J�Ы����IY��1��� Z�/�O=����o6�*��a�� �+���#u�����+6Ml8 ~a�`��=4��K̬� ��ӭ0hN
3
+ @}L��OpS5�������
4
+ :b����