protocol-http2 0.21.0 → 0.22.1

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: 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