protocol-http2 0.21.0 → 0.22.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: 90e7e77b799044d635c41f870e4d852fb8b9c3e51e5e637e70400420fa318c4f
4
- data.tar.gz: f2d9106c992e4503e36c5f3b4d57859744cd0281bcd8f261b7d902b7574e7cfd
3
+ metadata.gz: b23c19aa916a6d23a22b7f49fddc73fa91dd86957222178866a1389f74b4a037
4
+ data.tar.gz: b957666cdcf46c542baaebce6ec86d29d449ee80de4b4c98ca0409956b14b909
5
5
  SHA512:
6
- metadata.gz: 2b181f107bb843771deeb1d546a58315643faea3abcfa8f5a37198e2bf301a25f33d58d140879e676ac3928f932ed9b4a64325613b62e6b9e1297cc31264ef17
7
- data.tar.gz: 89ed0a08306f0e6a1835afc1867fac8c9f0c01c28aeb4828eec3f2611f90244ddbf2be01faaf15f775ed30457b044f7a8956bede2cb0f83de913d1945524eccf
6
+ metadata.gz: 2673eba3eafe1131725b5212beb8a3fc2c2cfd98c0b62753aecf6f1b8729f6c7e402a4d6b194ec4719e60d7489ec4c007b76e034c09573d972806aa6f9ac81ca
7
+ data.tar.gz: 2d8473793aced6539d345c58274dc48665aaa29160a08fdca034d839036ef201d0dd2e05c90c33d54564e157200151fc36469a9a5feb9f679884e5ebc323d6ac
checksums.yaml.gz.sig CHANGED
Binary file
@@ -28,16 +28,14 @@ module Protocol
28
28
  if @state == :new
29
29
  @framer.write_connection_preface
30
30
 
31
- # We don't support RFC7540 priorities:
32
- settings = settings.to_a
33
- settings << [Settings::NO_RFC7540_PRIORITIES, 1]
34
-
35
31
  send_settings(settings)
36
32
 
37
33
  yield if block_given?
38
34
 
39
35
  read_frame do |frame|
40
- raise ProtocolError, "First frame must be #{SettingsFrame}, but got #{frame.class}" unless frame.is_a? SettingsFrame
36
+ unless frame.is_a? SettingsFrame
37
+ raise ProtocolError, "First frame must be #{SettingsFrame}, but got #{frame.class}"
38
+ end
41
39
  end
42
40
  else
43
41
  raise ProtocolError, "Cannot send connection preface in state #{@state}"
@@ -8,6 +8,7 @@ require_relative "framer"
8
8
  require_relative "flow_controlled"
9
9
 
10
10
  require "protocol/hpack"
11
+ require "protocol/http/header/priority"
11
12
 
12
13
  module Protocol
13
14
  module HTTP2
@@ -400,6 +401,19 @@ module Protocol
400
401
  raise ProtocolError, "Unable to receive push promise!"
401
402
  end
402
403
 
404
+ def receive_priority_update(frame)
405
+ if frame.stream_id != 0
406
+ raise ProtocolError, "Invalid stream id: #{frame.stream_id}"
407
+ end
408
+
409
+ stream_id, value = frame.unpack
410
+
411
+ # Apparently you can set the priority of idle streams, but I'm not sure why that makes sense, so for now let's ignore it.
412
+ if stream = @streams[stream_id]
413
+ stream.priority = Protocol::HTTP::Header::Priority.new(value)
414
+ end
415
+ end
416
+
403
417
  def client_stream_id?(id)
404
418
  id.odd?
405
419
  end
@@ -14,6 +14,7 @@ require_relative "ping_frame"
14
14
  require_relative "goaway_frame"
15
15
  require_relative "window_update_frame"
16
16
  require_relative "continuation_frame"
17
+ require_relative "priority_update_frame"
17
18
 
18
19
  module Protocol
19
20
  module HTTP2
@@ -21,7 +22,7 @@ module Protocol
21
22
  FRAMES = [
22
23
  DataFrame,
23
24
  HeadersFrame,
24
- nil, # PriorityFrame is deprecated / removed.
25
+ nil, # PriorityFrame is deprecated and ignored, instead consider using PriorityUpdateFrame instead.
25
26
  ResetStreamFrame,
26
27
  SettingsFrame,
27
28
  PushPromiseFrame,
@@ -29,6 +30,13 @@ module Protocol
29
30
  GoawayFrame,
30
31
  WindowUpdateFrame,
31
32
  ContinuationFrame,
33
+ nil,
34
+ nil,
35
+ nil,
36
+ nil,
37
+ nil,
38
+ nil,
39
+ PriorityUpdateFrame,
32
40
  ].freeze
33
41
 
34
42
  # Default connection "fast-fail" preamble string as defined by the spec.
@@ -0,0 +1,41 @@
1
+ # frozen_string_literal: true
2
+
3
+ # Released under the MIT License.
4
+ # Copyright, 2024-2025, by Samuel Williams.
5
+
6
+ require_relative "frame"
7
+ require_relative "padded"
8
+ require_relative "continuation_frame"
9
+
10
+ module Protocol
11
+ module HTTP2
12
+ # The PRIORITY_UPDATE frame is used by clients to signal the initial priority of a response, or to reprioritize a response or push stream. It carries the stream ID of the response and the priority in ASCII text, using the same representation as the Priority header field value.
13
+ #
14
+ # +-+-------------+-----------------------------------------------+
15
+ # |R| Prioritized Stream ID (31) |
16
+ # +-+-----------------------------+-------------------------------+
17
+ # | Priority Field Value (*) ...
18
+ # +---------------------------------------------------------------+
19
+ #
20
+ class PriorityUpdateFrame < Frame
21
+ TYPE = 0x10
22
+ FORMAT = "N".freeze
23
+
24
+ def unpack
25
+ data = super
26
+
27
+ prioritized_stream_id = data.unpack1(FORMAT)
28
+
29
+ return prioritized_stream_id, data.byteslice(4, data.bytesize - 4)
30
+ end
31
+
32
+ def pack(prioritized_stream_id, data, **options)
33
+ super([prioritized_stream_id].pack(FORMAT) + data, **options)
34
+ end
35
+
36
+ def apply(connection)
37
+ connection.receive_priority_update(self)
38
+ end
39
+ end
40
+ end
41
+ end
@@ -28,10 +28,6 @@ module Protocol
28
28
  if @state == :new
29
29
  @framer.read_connection_preface
30
30
 
31
- # We don't support RFC7540 priorities:
32
- settings = settings.to_a
33
- settings << [Settings::NO_RFC7540_PRIORITIES, 1]
34
-
35
31
  send_settings(settings)
36
32
 
37
33
  read_frame do |frame|
@@ -27,8 +27,22 @@ module Protocol
27
27
  :maximum_header_list_size=,
28
28
  nil,
29
29
  :enable_connect_protocol=,
30
+ :no_rfc7540_priorities=,
30
31
  ]
31
32
 
33
+ def initialize
34
+ # These limits are taken from the RFC:
35
+ # https://tools.ietf.org/html/rfc7540#section-6.5.2
36
+ @header_table_size = 4096
37
+ @enable_push = 1
38
+ @maximum_concurrent_streams = 0xFFFFFFFF
39
+ @initial_window_size = 0xFFFF # 2**16 - 1
40
+ @maximum_frame_size = 0x4000 # 2**14
41
+ @maximum_header_list_size = 0xFFFFFFFF
42
+ @enable_connect_protocol = 0
43
+ @no_rfc7540_priorities = 0
44
+ end
45
+
32
46
  # Allows the sender to inform the remote endpoint of the maximum size of the header compression table used to decode header blocks, in octets.
33
47
  attr_accessor :header_table_size
34
48
 
@@ -91,16 +105,18 @@ module Protocol
91
105
  @enable_connect_protocol == 1
92
106
  end
93
107
 
94
- def initialize
95
- # These limits are taken from the RFC:
96
- # https://tools.ietf.org/html/rfc7540#section-6.5.2
97
- @header_table_size = 4096
98
- @enable_push = 1
99
- @maximum_concurrent_streams = 0xFFFFFFFF
100
- @initial_window_size = 0xFFFF # 2**16 - 1
101
- @maximum_frame_size = 0x4000 # 2**14
102
- @maximum_header_list_size = 0xFFFFFFFF
103
- @enable_connect_protocol = 0
108
+ attr :no_rfc7540_priorities
109
+
110
+ def no_rfc7540_priorities= value
111
+ if value == 0 or value == 1
112
+ @no_rfc7540_priorities = value
113
+ else
114
+ raise ProtocolError, "Invalid value for no_rfc7540_priorities: #{value}"
115
+ end
116
+ end
117
+
118
+ def no_rfc7540_priorities?
119
+ @no_rfc7540_priorities == 1
104
120
  end
105
121
 
106
122
  def update(changes)
@@ -110,40 +126,6 @@ module Protocol
110
126
  end
111
127
  end
112
128
  end
113
-
114
- def difference(other)
115
- changes = []
116
-
117
- if @header_table_size != other.header_table_size
118
- changes << [HEADER_TABLE_SIZE, @header_table_size]
119
- end
120
-
121
- if @enable_push != other.enable_push
122
- changes << [ENABLE_PUSH, @enable_push]
123
- end
124
-
125
- if @maximum_concurrent_streams != other.maximum_concurrent_streams
126
- changes << [MAXIMUM_CONCURRENT_STREAMS, @maximum_concurrent_streams]
127
- end
128
-
129
- if @initial_window_size != other.initial_window_size
130
- changes << [INITIAL_WINDOW_SIZE, @initial_window_size]
131
- end
132
-
133
- if @maximum_frame_size != other.maximum_frame_size
134
- changes << [MAXIMUM_FRAME_SIZE, @maximum_frame_size]
135
- end
136
-
137
- if @maximum_header_list_size != other.maximum_header_list_size
138
- changes << [MAXIMUM_HEADER_LIST_SIZE, @maximum_header_list_size]
139
- end
140
-
141
- if @enable_connect_protocol != other.enable_connect_protocol
142
- changes << [ENABLE_CONNECT_PROTOCOL, @enable_connect_protocol]
143
- end
144
-
145
- return changes
146
- end
147
129
  end
148
130
 
149
131
  class PendingSettings
@@ -76,6 +76,8 @@ module Protocol
76
76
 
77
77
  @local_window = Window.new(@connection.local_settings.initial_window_size)
78
78
  @remote_window = Window.new(@connection.remote_settings.initial_window_size)
79
+
80
+ @priority = nil
79
81
  end
80
82
 
81
83
  # The connection this stream belongs to.
@@ -90,6 +92,9 @@ module Protocol
90
92
  attr :local_window
91
93
  attr :remote_window
92
94
 
95
+ # @attribute [Protocol::HTTP::Header::Priority | Nil] the priority of the stream.
96
+ attr_accessor :priority
97
+
93
98
  def maximum_frame_size
94
99
  @connection.available_frame_size
95
100
  end
@@ -136,10 +141,6 @@ module Protocol
136
141
 
137
142
  # The HEADERS frame is used to open a stream, and additionally carries a header block fragment. HEADERS frames can be sent on a stream in the "idle", "reserved (local)", "open", or "half-closed (remote)" state.
138
143
  def send_headers(*arguments)
139
- if arguments.first.nil?
140
- arguments.shift # Remove nil priority.
141
- end
142
-
143
144
  if @state == :idle
144
145
  frame = write_headers(*arguments)
145
146
 
@@ -5,6 +5,6 @@
5
5
 
6
6
  module Protocol
7
7
  module HTTP2
8
- VERSION = "0.21.0"
8
+ VERSION = "0.22.1"
9
9
  end
10
10
  end
@@ -1,25 +1,31 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  # Released under the MIT License.
4
- # Copyright, 2024, by Samuel Williams.
4
+ # Copyright, 2024-2025, by Samuel Williams.
5
5
 
6
6
  require "traces/provider"
7
7
  require_relative "../../../../protocol/http2/framer"
8
8
 
9
9
  Traces::Provider(Protocol::HTTP2::Framer) do
10
10
  def write_connection_preface
11
+ return super unless Traces.active?
12
+
11
13
  Traces.trace("protocol.http2.framer.write_connection_preface") do
12
14
  super
13
15
  end
14
16
  end
15
17
 
16
18
  def read_connection_preface
19
+ return super unless Traces.active?
20
+
17
21
  Traces.trace("protocol.http2.framer.read_connection_preface") do
18
22
  super
19
23
  end
20
24
  end
21
25
 
22
26
  def write_frame(frame)
27
+ return super unless Traces.active?
28
+
23
29
  attributes = {
24
30
  "frame.length" => frame.length,
25
31
  "frame.class" => frame.class.name,
@@ -34,6 +40,8 @@ Traces::Provider(Protocol::HTTP2::Framer) do
34
40
  end
35
41
 
36
42
  def read_frame(...)
43
+ return super unless Traces.active?
44
+
37
45
  Traces.trace("protocol.http2.framer.read_frame") do |span|
38
46
  super.tap do |frame|
39
47
  span["frame.length"] = frame.length
@@ -45,6 +53,8 @@ Traces::Provider(Protocol::HTTP2::Framer) do
45
53
  end
46
54
 
47
55
  def flush
56
+ return super unless Traces.active?
57
+
48
58
  Traces.trace("protocol.http2.framer.flush") do
49
59
  super
50
60
  end
data/license.md CHANGED
@@ -1,9 +1,10 @@
1
1
  # MIT License
2
2
 
3
- Copyright, 2019-2024, by Samuel Williams.
3
+ Copyright, 2019-2025, by Samuel Williams.
4
4
  Copyright, 2019, by Yuta Iwama.
5
5
  Copyright, 2020, by Olle Jonsson.
6
6
  Copyright, 2023, by Marco Concetto Rudilosso.
7
+ Copyright, 2024, by Adam Petro.
7
8
 
8
9
  Permission is hereby granted, free of charge, to any person obtaining a copy
9
10
  of this software and associated documentation files (the "Software"), to deal
data/readme.md CHANGED
@@ -4,88 +4,15 @@ Provides a low-level implementation of the HTTP/2 protocol.
4
4
 
5
5
  [![Development Status](https://github.com/socketry/protocol-http2/workflows/Test/badge.svg)](https://github.com/socketry/protocol-http2/actions?workflow=Test)
6
6
 
7
- ## Installation
8
-
9
- Add this line to your application's Gemfile:
10
-
11
- ``` ruby
12
- gem 'protocol-http2'
13
- ```
14
-
15
- And then execute:
16
-
17
- $ bundle
18
-
19
- Or install it yourself as:
20
-
21
- $ gem install protocol-http2
22
-
23
7
  ## Usage
24
8
 
25
- Here is a basic HTTP/2 client:
9
+ Please see the [project documentation](https://socketry.github.io/protocol-http2/) for more details.
10
+
11
+ - [Getting Started](https://socketry.github.io/protocol-http2/guides/getting-started/index) - This guide explains how to use the `protocol-http2` gem to implement a basic HTTP/2 client.
26
12
 
27
- ``` ruby
28
- require 'async'
29
- require 'async/io/stream'
30
- require 'async/http/endpoint'
31
- require 'protocol/http2/client'
13
+ ## See Also
32
14
 
33
- Async do
34
- endpoint = Async::HTTP::Endpoint.parse("https://www.google.com/search?q=kittens")
35
-
36
- peer = endpoint.connect
37
-
38
- puts "Connected to #{peer.inspect}"
39
-
40
- # IO Buffering...
41
- stream = Async::IO::Stream.new(peer)
42
-
43
- framer = Protocol::HTTP2::Framer.new(stream)
44
- client = Protocol::HTTP2::Client.new(framer)
45
-
46
- puts "Sending connection preface..."
47
- client.send_connection_preface
48
-
49
- puts "Creating stream..."
50
- stream = client.create_stream
51
-
52
- headers = [
53
- [":scheme", endpoint.scheme],
54
- [":method", "GET"],
55
- [":authority", "www.google.com"],
56
- [":path", endpoint.path],
57
- ["accept", "*/*"],
58
- ]
59
-
60
- puts "Sending request on stream id=#{stream.id} state=#{stream.state}..."
61
- stream.send_headers(headers, Protocol::HTTP2::END_STREAM)
62
-
63
- puts "Waiting for response..."
64
- $count = 0
65
-
66
- def stream.process_headers(frame)
67
- headers = super
68
- puts "Got response headers: #{headers} (#{frame.end_stream?})"
69
- end
70
-
71
- def stream.receive_data(frame)
72
- data = super
73
-
74
- $count += data.scan(/kittens/).count
75
-
76
- puts "Got response data: #{data.bytesize}"
77
- end
78
-
79
- until stream.closed?
80
- frame = client.read_frame
81
- end
82
-
83
- puts "Got #{$count} kittens!"
84
-
85
- puts "Closing client..."
86
- client.close
87
- end
88
- ```
15
+ - [Async::HTTP](https://github.com/socketry/async-http) - A high-level HTTP client and server implementation.
89
16
 
90
17
  ## Contributing
91
18
 
data/releases.md CHANGED
@@ -1,7 +1,9 @@
1
1
  # Releases
2
2
 
3
- ## Unreleased
3
+ ## v0.22.0
4
4
 
5
- ### Remove Priority Frame and Dependency Tracking
5
+ ### Added Priority Update Frame and Stream Priority
6
6
 
7
- HTTP/2 has deprecated the priority frame and stream dependency tracking. This feature has been effectively removed from the protocol. As a consequence, the internal implementation is greatly simplified. The `Protocol::HTTP2::Stream` class no longer tracks dependencies or priorities, and this includes `Stream#send_headers` which no longer takes `priority` as the first argument.
7
+ HTTP/2 has deprecated the priority frame and stream dependency tracking. This feature has been effectively removed from the protocol. As a consequence, the internal implementation is greatly simplified. The `Protocol::HTTP2::Stream` class no longer tracks dependencies, and this includes `Stream#send_headers` which no longer takes `priority` as the first argument.
8
+
9
+ Optional per-request priority can be set using the `priority` header instead, and this value can be manipulated using the priority update frame.
data.tar.gz.sig CHANGED
Binary file
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: protocol-http2
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.21.0
4
+ version: 0.22.1
5
5
  platform: ruby
6
6
  authors:
7
7
  - Samuel Williams
8
8
  - Yuta Iwama
9
+ - Adam Petro
9
10
  - Marco Concetto Rudilosso
10
11
  - Olle Jonsson
11
- autorequire:
12
12
  bindir: bin
13
13
  cert_chain:
14
14
  - |
@@ -40,7 +40,7 @@ cert_chain:
40
40
  Q2K9NVun/S785AP05vKkXZEFYxqG6EW012U4oLcFl5MySFajYXRYbuUpH6AY+HP8
41
41
  voD0MPg1DssDLKwXyt1eKD/+Fq0bFWhwVM/1XiAXL7lyYUyOq24KHgQ2Csg=
42
42
  -----END CERTIFICATE-----
43
- date: 2024-11-28 00:00:00.000000000 Z
43
+ date: 2025-02-01 00:00:00.000000000 Z
44
44
  dependencies:
45
45
  - !ruby/object:Gem::Dependency
46
46
  name: protocol-hpack
@@ -62,16 +62,14 @@ dependencies:
62
62
  requirements:
63
63
  - - "~>"
64
64
  - !ruby/object:Gem::Version
65
- version: '0.18'
65
+ version: '0.47'
66
66
  type: :runtime
67
67
  prerelease: false
68
68
  version_requirements: !ruby/object:Gem::Requirement
69
69
  requirements:
70
70
  - - "~>"
71
71
  - !ruby/object:Gem::Version
72
- version: '0.18'
73
- description:
74
- email:
72
+ version: '0.47'
75
73
  executables: []
76
74
  extensions: []
77
75
  extra_rdoc_files: []
@@ -89,6 +87,7 @@ files:
89
87
  - lib/protocol/http2/headers_frame.rb
90
88
  - lib/protocol/http2/padded.rb
91
89
  - lib/protocol/http2/ping_frame.rb
90
+ - lib/protocol/http2/priority_update_frame.rb
92
91
  - lib/protocol/http2/push_promise_frame.rb
93
92
  - lib/protocol/http2/reset_stream_frame.rb
94
93
  - lib/protocol/http2/server.rb
@@ -108,7 +107,6 @@ licenses:
108
107
  metadata:
109
108
  documentation_uri: https://socketry.github.io/protocol-http2/
110
109
  source_code_uri: https://github.com/socketry/protocol-http2.git
111
- post_install_message:
112
110
  rdoc_options: []
113
111
  require_paths:
114
112
  - lib
@@ -123,8 +121,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
123
121
  - !ruby/object:Gem::Version
124
122
  version: '0'
125
123
  requirements: []
126
- rubygems_version: 3.5.22
127
- signing_key:
124
+ rubygems_version: 3.6.2
128
125
  specification_version: 4
129
126
  summary: A low level implementation of the HTTP/2 protocol.
130
127
  test_files: []
metadata.gz.sig CHANGED
Binary file