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 +4 -4
- checksums.yaml.gz.sig +0 -0
- data/lib/protocol/http2/client.rb +3 -5
- data/lib/protocol/http2/connection.rb +14 -0
- data/lib/protocol/http2/framer.rb +9 -1
- data/lib/protocol/http2/priority_update_frame.rb +41 -0
- data/lib/protocol/http2/server.rb +0 -4
- data/lib/protocol/http2/settings_frame.rb +26 -44
- data/lib/protocol/http2/stream.rb +5 -4
- data/lib/protocol/http2/version.rb +1 -1
- data/lib/traces/provider/protocol/http2/framer.rb +11 -1
- data/license.md +2 -1
- data/readme.md +5 -78
- data/releases.md +5 -3
- data.tar.gz.sig +0 -0
- metadata +7 -10
- metadata.gz.sig +0 -0
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: b23c19aa916a6d23a22b7f49fddc73fa91dd86957222178866a1389f74b4a037
|
4
|
+
data.tar.gz: b957666cdcf46c542baaebce6ec86d29d449ee80de4b4c98ca0409956b14b909
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
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
|
-
|
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
|
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
|
@@ -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
|
-
|
95
|
-
|
96
|
-
|
97
|
-
|
98
|
-
|
99
|
-
|
100
|
-
|
101
|
-
|
102
|
-
|
103
|
-
|
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
|
|
@@ -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-
|
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
|
[](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
|
-
|
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
|
-
|
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
|
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
|
-
##
|
3
|
+
## v0.22.0
|
4
4
|
|
5
|
-
###
|
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
|
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.
|
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:
|
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.
|
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.
|
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.
|
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
|