protocol-http 0.57.0 → 0.58.1

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: e755c82bfe5d9098e7fe898dfb19e6963e500ab9410d9c7d04e2b6eb18886689
4
+ data.tar.gz: 23717c1485024e2f324cc069f7684a924f864a9f57c2a6e62b7be72ba532db45
5
5
  SHA512:
6
- metadata.gz: 979dec4d8fedfce87917074576493ff6365e897bd6403f409d0ff862ed1517cb4cbfbc65b0f1c95ef83f77ceb20530e4a90131e0530c737fa7532886f6d431d5
7
- data.tar.gz: a52fbe05985d5d6d87d37c46b72cc71ff79b8d4023d765c79efaa5e947681dd53a1d10a083ff1e54ad47bd9358ddb7a8e8ede7313c7db36c4c7d88cfad52d3a8
6
+ metadata.gz: a70c2c04344069a17fe6cdf1a5d23dcdaffcfbbfad815937d229c8bdeb7299bef0db2eb45c37e0fec6c1ba8cdf729560ebfcbb261910d630fc2d15d89a7b46a9
7
+ data.tar.gz: fc047716362a83605223b9f0107a652ac8dcaa860c10815dd755c55a7ba73dc4a4ef468212d570e6c37340e6d7bfc3ba85ad6623c1620d38b2bbce57a9e3e4be
checksums.yaml.gz.sig CHANGED
@@ -1,2 +1 @@
1
- E��:?[T3-ӿ���5�@5.�ͣ:��Z5X0�@S�]g秱L�����W��(�8���^4�-S�Jml����7������L˸����⦓.���0`+xjjPCq;O�4�vx����_U�ǔ!�r�����Q"��՗!p
2
- J��%pw���3��{Y��� ������G_�!O��s�v�N��]��8�m(���e^�eʘ����H/���<u����Kߧh��4dk)� �[fS��y���0�bC�gf�_��6�k �n�?vV��y7F�1��q�f�p���1�y�� �-kz��
1
+ _��IX ��d2�gT�Ow����h�o��h��.ї�2���s&۰�*j :U��2��5;�aK��5 �:�@|N�/�DMy�����������F��e5���P�󺑙jr���� lֿn��nS1��7��ɰ�)�7y o��4g
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
 
@@ -19,12 +19,30 @@ module Protocol
19
19
  include BadRequest
20
20
 
21
21
  # @parameter key [String] The header key that was duplicated.
22
- def initialize(key)
22
+ def initialize(key, existing_value, new_value)
23
23
  super("Duplicate singleton header key: #{key.inspect}")
24
+
25
+ @key = key
26
+ @existing_value = existing_value
27
+ @new_value = new_value
24
28
  end
25
29
 
26
30
  # @attribute [String] key The header key that was duplicated.
27
31
  attr :key
32
+
33
+ # @attribute [String] existing_value The existing value for the duplicated header.
34
+ attr :existing_value
35
+
36
+ # @attribute [String] new_value The new value for the duplicated header.
37
+ attr :new_value
38
+
39
+ def detailed_message(highlight: false)
40
+ <<~MESSAGE
41
+ #{self.message}
42
+ Existing value: #{@existing_value.inspect}
43
+ New value: #{@new_value.inspect}
44
+ MESSAGE
45
+ end
28
46
  end
29
47
 
30
48
  # Raised when an invalid trailer header is encountered in headers.
@@ -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,27 +444,16 @@ 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
- raise DuplicateHeaderError, key
456
+ raise DuplicateHeaderError.new(key, hash[key], value)
427
457
  end
428
458
 
429
459
  hash[key] = value
@@ -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.1"
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.58.1
34
+
35
+ - `Protocol::HTTP::DuplicateHeaderError` now includes the existing and new values for better debugging.
36
+
37
+ ### v0.58.0
38
+
39
+ - Move trailer validation to `Headers#add` method to ensure all additions are checked at the time of addition as this is a hard requirement.
40
+ - Introduce `Headers#header` method to enumerate only the main headers, excluding trailers. This can be used after invoking `Headers#trailer!` to avoid race conditions.
41
+ - Fix `Headers#to_h` so that indexed headers are not left in an inconsistent state if errors occur during processing.
42
+
33
43
  ### v0.57.0
34
44
 
35
45
  - Always use `#parse` when parsing header values from strings to ensure proper normalization and validation.
@@ -79,14 +89,6 @@ Please see the [project releases](https://socketry.github.io/protocol-http/relea
79
89
 
80
90
  - Drop support for Ruby v3.1.
81
91
 
82
- ### v0.48.0
83
-
84
- - Add support for parsing `accept`, `accept-charset`, `accept-encoding` and `accept-language` headers into structured values.
85
-
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,15 @@
1
1
  # Releases
2
2
 
3
+ ## v0.58.1
4
+
5
+ - `Protocol::HTTP::DuplicateHeaderError` now includes the existing and new values for better debugging.
6
+
7
+ ## v0.58.0
8
+
9
+ - Move trailer validation to `Headers#add` method to ensure all additions are checked at the time of addition as this is a hard requirement.
10
+ - Introduce `Headers#header` method to enumerate only the main headers, excluding trailers. This can be used after invoking `Headers#trailer!` to avoid race conditions.
11
+ - Fix `Headers#to_h` so that indexed headers are not left in an inconsistent state if errors occur during processing.
12
+
3
13
  ## v0.57.0
4
14
 
5
15
  - 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.1
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
Binary file