biryani 0.0.6 → 0.0.7
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
- data/.github/workflows/conformance.yml +46 -0
- data/.gitignore +1 -0
- data/.rubocop.yml +1 -0
- data/conformance/server_spec.rb +9 -2
- data/conformance/spec_helper.rb +1 -0
- data/lib/biryani/connection.rb +10 -12
- data/lib/biryani/http_request.rb +24 -14
- data/lib/biryani/state.rb +94 -76
- data/lib/biryani/streams_context.rb +20 -5
- data/lib/biryani/version.rb +1 -1
- data/spec/connection/send_spec.rb +4 -4
- data/spec/connection/transition_stream_state_send_spec.rb +4 -4
- data/spec/{http_request_builder_spec.rb → http_request_spec.rb} +18 -1
- data/spec/streams_context_spec.rb +2 -2
- metadata +4 -3
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 24efc38cdd56ee46ac5e48dcbf52f7ddb73a38c73872c8b05be3d8b26aea5105
|
|
4
|
+
data.tar.gz: f0d0e70ef0dfc20587711637856f8a7ffe7a310d437d3e53401e81f8ef014391
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 2c3001f6c1f679690c63bcfe45baa1d85fedf764850c6fa2ca44debe71e89c286ca066715592632b8d19b03a1957f98f2a362aace2e8e171533e3595580a3b8a
|
|
7
|
+
data.tar.gz: 84a1b0ee18b7eaad1a7f8336ffe912038bd18c8a229c119dac83b7cb9539daee79ab25ea13286db2706e707955b23f6a7801b96c2acf09dbbdc889fc38ddc4a3
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
name: h2spec
|
|
2
|
+
|
|
3
|
+
on:
|
|
4
|
+
push:
|
|
5
|
+
branches:
|
|
6
|
+
- main
|
|
7
|
+
pull_request:
|
|
8
|
+
branches:
|
|
9
|
+
- '*'
|
|
10
|
+
|
|
11
|
+
jobs:
|
|
12
|
+
conformance:
|
|
13
|
+
runs-on: ubuntu-latest
|
|
14
|
+
permissions:
|
|
15
|
+
contents: read
|
|
16
|
+
checks: write
|
|
17
|
+
strategy:
|
|
18
|
+
matrix:
|
|
19
|
+
ruby-version: ['4.0']
|
|
20
|
+
steps:
|
|
21
|
+
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
|
|
22
|
+
- name: Set up Ruby
|
|
23
|
+
uses: ruby/setup-ruby@ae195bbe749a7cef685ac729197124a48305c1cb # v1.276.0
|
|
24
|
+
with:
|
|
25
|
+
ruby-version: ${{ matrix.ruby-version }}
|
|
26
|
+
- name: Install dependencies
|
|
27
|
+
run: |
|
|
28
|
+
gem --version
|
|
29
|
+
gem install bundler
|
|
30
|
+
bundle --version
|
|
31
|
+
bundle install
|
|
32
|
+
- name: Install h2spec
|
|
33
|
+
run: |
|
|
34
|
+
set -eu
|
|
35
|
+
curl -Ls https://github.com/summerwind/h2spec/releases/download/v2.6.0/h2spec_linux_amd64.tar.gz | tar xz
|
|
36
|
+
mkdir -p $GITHUB_WORKSPACE/bin
|
|
37
|
+
mv h2spec $GITHUB_WORKSPACE/bin/h2spec
|
|
38
|
+
chmod +x $GITHUB_WORKSPACE/bin/h2spec
|
|
39
|
+
echo $GITHUB_WORKSPACE/bin >> $GITHUB_PATH
|
|
40
|
+
- name: Run conformance test
|
|
41
|
+
run: bundle exec rake conformance
|
|
42
|
+
- uses: mikepenz/action-junit-report@a294a61c909bd8a4b563024a2faa28897fd53ebc # v6.1.0
|
|
43
|
+
if: success()
|
|
44
|
+
with:
|
|
45
|
+
report_paths: conformance/reports/h2spec.xml
|
|
46
|
+
detailed_summary: true
|
data/.gitignore
CHANGED
data/.rubocop.yml
CHANGED
data/conformance/server_spec.rb
CHANGED
|
@@ -6,7 +6,7 @@ RSpec.describe Server do
|
|
|
6
6
|
|
|
7
7
|
Ractor.new(@tcpserver) do |socket|
|
|
8
8
|
server = Server.new(
|
|
9
|
-
Ractor.shareable_proc do |
|
|
9
|
+
Ractor.shareable_proc do |_, res|
|
|
10
10
|
res.status = 200
|
|
11
11
|
res.content = 'OK'
|
|
12
12
|
end
|
|
@@ -16,9 +16,16 @@ RSpec.describe Server do
|
|
|
16
16
|
end
|
|
17
17
|
end
|
|
18
18
|
|
|
19
|
+
let(:junit_report_file_path) do
|
|
20
|
+
Dir.mkdir(JUNIT_REPORT_DIR) unless Dir.exist?(JUNIT_REPORT_DIR)
|
|
21
|
+
|
|
22
|
+
"#{JUNIT_REPORT_DIR}/h2spec.xml"
|
|
23
|
+
end
|
|
24
|
+
|
|
19
25
|
let(:client) do
|
|
20
26
|
which('h2spec')
|
|
21
|
-
|
|
27
|
+
|
|
28
|
+
"h2spec --port #{PORT} --verbose --junit-report #{junit_report_file_path}"
|
|
22
29
|
end
|
|
23
30
|
|
|
24
31
|
after do
|
data/conformance/spec_helper.rb
CHANGED
data/lib/biryani/connection.rb
CHANGED
|
@@ -118,6 +118,10 @@ module Biryani
|
|
|
118
118
|
#
|
|
119
119
|
# @return [Array<Object>, Array<ConnectionError>, Array<StreamError>] frames or errors
|
|
120
120
|
def recv_dispatch(frame)
|
|
121
|
+
receiving_continuation_stream_id = @streams_ctx.receiving_continuation_stream_id
|
|
122
|
+
return [ConnectionError.new(ErrorCode::PROTOCOL_ERROR, "invalid frame type #{format('0x%02x', typ)} for stream identifier #{format('0x%02x', stream_id)}")] \
|
|
123
|
+
if !receiving_continuation_stream_id.nil? && frame.stream_id != receiving_continuation_stream_id
|
|
124
|
+
|
|
121
125
|
if frame.stream_id.zero?
|
|
122
126
|
handle_connection_frame(frame)
|
|
123
127
|
else
|
|
@@ -129,13 +133,8 @@ module Biryani
|
|
|
129
133
|
#
|
|
130
134
|
# @return [Array<Object>, Array<ConnectionError>, Array<StreamError>] frames or errors
|
|
131
135
|
# rubocop: disable Metrics/CyclomaticComplexity
|
|
132
|
-
# rubocop: disable Metrics/PerceivedComplexity
|
|
133
136
|
def handle_connection_frame(frame)
|
|
134
|
-
|
|
135
|
-
return [ConnectionError.new(ErrorCode::PROTOCOL_ERROR, "invalid frame type #{format('0x%02x', typ)} for stream identifier #{format('0x%02x', stream_id)}")] \
|
|
136
|
-
if @streams_ctx.receiving_continuation? && ([FrameType::SETTINGS, FrameType::PING, FrameType::WINDOW_UPDATE].include?(typ) || FrameType.unknown?(typ))
|
|
137
|
-
|
|
138
|
-
case typ
|
|
137
|
+
case frame.f_type
|
|
139
138
|
when FrameType::DATA, FrameType::HEADERS, FrameType::PRIORITY, FrameType::RST_STREAM, FrameType::PUSH_PROMISE, FrameType::CONTINUATION
|
|
140
139
|
[ConnectionError.new(ErrorCode::PROTOCOL_ERROR, "invalid frame type #{format('0x%02x', typ)} for stream identifier 0x00")]
|
|
141
140
|
when FrameType::SETTINGS
|
|
@@ -167,7 +166,6 @@ module Biryani
|
|
|
167
166
|
end
|
|
168
167
|
end
|
|
169
168
|
# rubocop: enable Metrics/CyclomaticComplexity
|
|
170
|
-
# rubocop: enable Metrics/PerceivedComplexity
|
|
171
169
|
|
|
172
170
|
# @param frame [Object]
|
|
173
171
|
#
|
|
@@ -253,7 +251,7 @@ module Biryani
|
|
|
253
251
|
return ConnectionError.new(ErrorCode::PROTOCOL_ERROR, 'new stream identifier is less than the existing stream identifiers') if ctx.nil? && streams_ctx.last_stream_id > stream_id
|
|
254
252
|
|
|
255
253
|
ctx = streams_ctx.new_context(stream_id, send_initial_window_size, recv_initial_window_size) if ctx.nil?
|
|
256
|
-
obj = ctx.
|
|
254
|
+
obj = ctx.state_transition!(recv_frame, :recv)
|
|
257
255
|
return obj if Biryani.err?(obj)
|
|
258
256
|
|
|
259
257
|
ctx
|
|
@@ -275,7 +273,7 @@ module Biryani
|
|
|
275
273
|
streams_ctx.close_all
|
|
276
274
|
true
|
|
277
275
|
else
|
|
278
|
-
streams_ctx[stream_id].
|
|
276
|
+
streams_ctx[stream_id].state_transition!(send_frame, :send) unless stream_id.zero?
|
|
279
277
|
false
|
|
280
278
|
end
|
|
281
279
|
end
|
|
@@ -357,7 +355,7 @@ module Biryani
|
|
|
357
355
|
obj = http_request(ctx.fragment, ctx.content, decoder)
|
|
358
356
|
return obj if Biryani.err?(obj)
|
|
359
357
|
|
|
360
|
-
ctx
|
|
358
|
+
ctx << obj
|
|
361
359
|
end
|
|
362
360
|
|
|
363
361
|
window_updates = []
|
|
@@ -378,7 +376,7 @@ module Biryani
|
|
|
378
376
|
obj = http_request(ctx.fragment, ctx.content, decoder)
|
|
379
377
|
return obj if Biryani.err?(obj)
|
|
380
378
|
|
|
381
|
-
ctx
|
|
379
|
+
ctx << obj
|
|
382
380
|
end
|
|
383
381
|
|
|
384
382
|
nil
|
|
@@ -387,7 +385,7 @@ module Biryani
|
|
|
387
385
|
# @param _rst_stream [RstStream]
|
|
388
386
|
# @param ctx [StreamContext]
|
|
389
387
|
def self.handle_rst_stream(_rst_stream, ctx)
|
|
390
|
-
ctx.
|
|
388
|
+
ctx.close
|
|
391
389
|
end
|
|
392
390
|
|
|
393
391
|
# @param settings [Settings]
|
data/lib/biryani/http_request.rb
CHANGED
|
@@ -4,7 +4,7 @@ module Biryani
|
|
|
4
4
|
|
|
5
5
|
# @param method [String]
|
|
6
6
|
# @param uri [URI]
|
|
7
|
-
# @param fields [Hash<String, String
|
|
7
|
+
# @param fields [Hash<String, Array<String>>]
|
|
8
8
|
# @param content [String]
|
|
9
9
|
def initialize(method, uri, fields, content)
|
|
10
10
|
@method = method
|
|
@@ -12,6 +12,13 @@ module Biryani
|
|
|
12
12
|
@fields = fields
|
|
13
13
|
@content = content
|
|
14
14
|
end
|
|
15
|
+
|
|
16
|
+
# @return [Array<String>, nil]
|
|
17
|
+
def trailers
|
|
18
|
+
# https://datatracker.ietf.org/doc/html/rfc9110#section-6.6.2-4
|
|
19
|
+
keys = (@fields['trailer'] || []).flat_map { |x| x.split(',').map(&:strip) }
|
|
20
|
+
@fields.slice(*keys)
|
|
21
|
+
end
|
|
15
22
|
end
|
|
16
23
|
|
|
17
24
|
class HTTPRequestBuilder
|
|
@@ -26,6 +33,7 @@ module Biryani
|
|
|
26
33
|
# @param value [String]
|
|
27
34
|
#
|
|
28
35
|
# @return [nil, ConnectioError]
|
|
36
|
+
# rubocop: disable Metrics/AbcSize
|
|
29
37
|
# rubocop: disable Metrics/CyclomaticComplexity
|
|
30
38
|
# rubocop: disable Metrics/PerceivedComplexity
|
|
31
39
|
def field(name, value)
|
|
@@ -35,17 +43,14 @@ module Biryani
|
|
|
35
43
|
return ConnectionError.new(ErrorCode::PROTOCOL_ERROR, 'duplicated pseudo-header fields') if PSEUDO_HEADER_FIELDS.include?(name) && @h.key?(name)
|
|
36
44
|
return ConnectionError.new(ErrorCode::PROTOCOL_ERROR, "invalid `#{name}` field") if PSEUDO_HEADER_FIELDS.include?(name) && value.empty?
|
|
37
45
|
return ConnectionError.new(ErrorCode::PROTOCOL_ERROR, 'connection-specific field is forbidden') if name == 'connection'
|
|
46
|
+
return ConnectionError.new(ErrorCode::PROTOCOL_ERROR, '`TE` field has a value other than `trailers`') if name == 'te' && value != 'trailers'
|
|
38
47
|
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
if name == 'cookie' && @h.key?('cookie')
|
|
42
|
-
@h[name] << "; #{value}"
|
|
43
|
-
else
|
|
44
|
-
@h[name] = value
|
|
45
|
-
end
|
|
48
|
+
@h[name] = [] unless @h.key?(name)
|
|
49
|
+
@h[name] << value
|
|
46
50
|
|
|
47
51
|
nil
|
|
48
52
|
end
|
|
53
|
+
# rubocop: enable Metrics/AbcSize
|
|
49
54
|
# rubocop: enable Metrics/CyclomaticComplexity
|
|
50
55
|
# rubocop: enable Metrics/PerceivedComplexity
|
|
51
56
|
|
|
@@ -69,16 +74,21 @@ module Biryani
|
|
|
69
74
|
self.class.http_request(h, s)
|
|
70
75
|
end
|
|
71
76
|
|
|
72
|
-
# @param
|
|
77
|
+
# @param h [Hash<String, Array<String>>]
|
|
73
78
|
# @param s [String]
|
|
74
79
|
#
|
|
75
80
|
# @return [HTTPRequest, ConnectionError]
|
|
76
|
-
def self.http_request(
|
|
77
|
-
return ConnectionError.new(ErrorCode::PROTOCOL_ERROR, 'missing pseudo-header fields') unless PSEUDO_HEADER_FIELDS.all? { |x|
|
|
78
|
-
return ConnectionError.new(ErrorCode::PROTOCOL_ERROR, 'invalid content-length') if
|
|
81
|
+
def self.http_request(h, s)
|
|
82
|
+
return ConnectionError.new(ErrorCode::PROTOCOL_ERROR, 'missing pseudo-header fields') unless PSEUDO_HEADER_FIELDS.all? { |x| h.key?(x) }
|
|
83
|
+
return ConnectionError.new(ErrorCode::PROTOCOL_ERROR, 'invalid content-length') if h.key?('content-length') && !s.empty? && s.length != h['content-length'].to_i
|
|
79
84
|
|
|
80
|
-
|
|
81
|
-
|
|
85
|
+
scheme = h[':scheme'][0]
|
|
86
|
+
domain = h[':authority'][0]
|
|
87
|
+
path = h[':path'][0]
|
|
88
|
+
uri = URI("#{scheme}://#{domain}#{path}")
|
|
89
|
+
method = h[':method'][0]
|
|
90
|
+
h['cookie'] = [h['cookie'].join('; ')] if h.key?('cookie')
|
|
91
|
+
HTTPRequest.new(method, uri, h, s)
|
|
82
92
|
end
|
|
83
93
|
end
|
|
84
94
|
end
|
data/lib/biryani/state.rb
CHANGED
|
@@ -56,58 +56,82 @@ module Biryani
|
|
|
56
56
|
# rubocop: disable Metrics/PerceivedComplexity
|
|
57
57
|
def self.next(state, frame, direction)
|
|
58
58
|
typ = frame.f_type
|
|
59
|
-
case [state,
|
|
59
|
+
case [state, direction, typ]
|
|
60
60
|
# idle
|
|
61
|
-
in [:idle, FrameType::
|
|
61
|
+
in [:idle, :send, FrameType::PUSH_PROMISE]
|
|
62
|
+
:reserved_local
|
|
63
|
+
in [:idle, :recv, FrameType::HEADERS] if frame.end_stream? && frame.end_headers?
|
|
62
64
|
:half_closed_remote
|
|
63
|
-
in [:idle, FrameType::HEADERS
|
|
65
|
+
in [:idle, :recv, FrameType::HEADERS] if frame.end_headers?
|
|
64
66
|
:receiving_data
|
|
65
|
-
in [:idle, FrameType::HEADERS
|
|
67
|
+
in [:idle, :recv, FrameType::HEADERS] if frame.end_stream?
|
|
66
68
|
:receiving_continuation
|
|
67
|
-
in [:idle, FrameType::HEADERS
|
|
69
|
+
in [:idle, :recv, FrameType::HEADERS]
|
|
68
70
|
:receiving_continuation_and_data
|
|
69
|
-
in [:idle, FrameType::PRIORITY
|
|
71
|
+
in [:idle, :recv, FrameType::PRIORITY]
|
|
70
72
|
state
|
|
71
|
-
in [:idle, FrameType::PUSH_PROMISE, :send]
|
|
72
|
-
:reserved_local
|
|
73
|
-
in [:idle, _, _]
|
|
74
|
-
unexpected(ErrorCode::PROTOCOL_ERROR, state, typ, direction)
|
|
75
73
|
|
|
76
74
|
# receiving_continuation_and_data
|
|
77
|
-
in [:receiving_continuation_and_data, FrameType::
|
|
78
|
-
:closed
|
|
79
|
-
in [:receiving_continuation_and_data, FrameType::WINDOW_UPDATE, :recv]
|
|
75
|
+
in [:receiving_continuation_and_data, :recv, FrameType::WINDOW_UPDATE]
|
|
80
76
|
state
|
|
81
|
-
in [:receiving_continuation_and_data, FrameType::CONTINUATION
|
|
77
|
+
in [:receiving_continuation_and_data, :recv, FrameType::CONTINUATION] if frame.end_headers?
|
|
82
78
|
:receiving_data
|
|
83
|
-
in [:receiving_continuation_and_data, FrameType::CONTINUATION
|
|
79
|
+
in [:receiving_continuation_and_data, :recv, FrameType::CONTINUATION]
|
|
84
80
|
state
|
|
85
|
-
in [:receiving_continuation_and_data, _,
|
|
86
|
-
|
|
81
|
+
in [:receiving_continuation_and_data, _, FrameType::RST_STREAM]
|
|
82
|
+
:closed
|
|
87
83
|
|
|
88
84
|
# receiving_continuation
|
|
89
|
-
in [:receiving_continuation, FrameType::
|
|
90
|
-
:
|
|
91
|
-
in [:receiving_continuation, FrameType::
|
|
85
|
+
in [:receiving_continuation, :recv, FrameType::DATA] if frame.end_stream?
|
|
86
|
+
:half_closed_remote
|
|
87
|
+
in [:receiving_continuation, :recv, FrameType::DATA]
|
|
88
|
+
:receiving_data
|
|
89
|
+
in [:receiving_continuation, :recv, FrameType::WINDOW_UPDATE]
|
|
92
90
|
state
|
|
93
|
-
in [:receiving_continuation, FrameType::CONTINUATION
|
|
91
|
+
in [:receiving_continuation, :recv, FrameType::CONTINUATION] if frame.end_headers?
|
|
94
92
|
:half_closed_remote
|
|
95
|
-
in [:receiving_continuation, FrameType::CONTINUATION
|
|
93
|
+
in [:receiving_continuation, :recv, FrameType::CONTINUATION]
|
|
96
94
|
state
|
|
97
|
-
in [:receiving_continuation, _,
|
|
98
|
-
|
|
95
|
+
in [:receiving_continuation, _, FrameType::RST_STREAM]
|
|
96
|
+
:closed
|
|
99
97
|
|
|
100
98
|
# receiving_data
|
|
101
|
-
in [:receiving_data, FrameType::DATA
|
|
99
|
+
in [:receiving_data, :recv, FrameType::DATA] if frame.end_stream?
|
|
102
100
|
:half_closed_remote
|
|
103
|
-
in [:receiving_data, FrameType::DATA
|
|
101
|
+
in [:receiving_data, :recv, FrameType::DATA]
|
|
104
102
|
state
|
|
105
|
-
in [:receiving_data, FrameType::
|
|
103
|
+
in [:receiving_data, :recv, FrameType::HEADERS] if !frame.end_stream?
|
|
104
|
+
unexpected(ErrorCode::PROTOCOL_ERROR, state, typ, direction)
|
|
105
|
+
in [:receiving_data, :recv, FrameType::HEADERS] if frame.end_headers?
|
|
106
|
+
:half_closed_remote
|
|
107
|
+
in [:receiving_data, :recv, FrameType::HEADERS]
|
|
108
|
+
:receiving_trailer_continuation
|
|
109
|
+
in [:receiving_data, :recv, FrameType::PRIORITY]
|
|
110
|
+
state
|
|
111
|
+
in [:receiving_data, _, FrameType::RST_STREAM]
|
|
106
112
|
:closed
|
|
107
|
-
in [:receiving_data, FrameType::WINDOW_UPDATE
|
|
113
|
+
in [:receiving_data, _, FrameType::WINDOW_UPDATE]
|
|
108
114
|
state
|
|
109
|
-
|
|
115
|
+
|
|
116
|
+
# receiving_trailer_headers
|
|
117
|
+
in [:receiving_trailer_headers, :recv, FrameType::HEADERS] if !frame.end_stream?
|
|
110
118
|
unexpected(ErrorCode::PROTOCOL_ERROR, state, typ, direction)
|
|
119
|
+
in [:receiving_trailer_headers, :recv, FrameType::HEADERS] if frame.end_headers?
|
|
120
|
+
:half_closed_remote
|
|
121
|
+
in [:receiving_trailer_headers, :recv, FrameType::HEADERS]
|
|
122
|
+
:receiving_trailer_continuation
|
|
123
|
+
in [:receiving_trailer_headers, :recv, FrameType::WINDOW_UPDATE]
|
|
124
|
+
state
|
|
125
|
+
|
|
126
|
+
# receiving_trailer_continuation
|
|
127
|
+
in [:receiving_trailer_continuation, :recv, FrameType::WINDOW_UPDATE]
|
|
128
|
+
state
|
|
129
|
+
in [:receiving_trailer_continuation, :recv, FrameType::CONTINUATION] if frame.end_headers?
|
|
130
|
+
:half_closed_remote
|
|
131
|
+
in [:receiving_trailer_continuation, :recv, FrameType::CONTINUATION]
|
|
132
|
+
:receiving_trailer_continuation
|
|
133
|
+
in [:receiving_trailer_continuation, _, FrameType::RST_STREAM]
|
|
134
|
+
:closed
|
|
111
135
|
|
|
112
136
|
# reserved_remote
|
|
113
137
|
in [:reserved_remote, _, _]
|
|
@@ -120,81 +144,75 @@ module Biryani
|
|
|
120
144
|
state
|
|
121
145
|
|
|
122
146
|
# half_closed_remote
|
|
123
|
-
in [:half_closed_remote, FrameType::HEADERS
|
|
147
|
+
in [:half_closed_remote, :send, FrameType::HEADERS] if frame.end_stream? && frame.end_headers?
|
|
124
148
|
:closed
|
|
125
|
-
in [:half_closed_remote, FrameType::HEADERS
|
|
149
|
+
in [:half_closed_remote, :send, FrameType::HEADERS] if frame.end_headers?
|
|
126
150
|
:sending_data
|
|
127
|
-
in [:half_closed_remote, FrameType::HEADERS
|
|
151
|
+
in [:half_closed_remote, :send, FrameType::HEADERS] if frame.end_stream?
|
|
128
152
|
:sending_continuation
|
|
129
|
-
in [:half_closed_remote, FrameType::HEADERS
|
|
130
|
-
:
|
|
131
|
-
in [:half_closed_remote, FrameType::PRIORITY
|
|
153
|
+
in [:half_closed_remote, :send, FrameType::HEADERS]
|
|
154
|
+
:sending_continuation_and_data
|
|
155
|
+
in [:half_closed_remote, :recv, FrameType::PRIORITY]
|
|
132
156
|
state
|
|
133
|
-
in [:half_closed_remote, FrameType::RST_STREAM
|
|
157
|
+
in [:half_closed_remote, _, FrameType::RST_STREAM]
|
|
134
158
|
:closed
|
|
135
|
-
in [:half_closed_remote, FrameType::WINDOW_UPDATE
|
|
159
|
+
in [:half_closed_remote, _, FrameType::WINDOW_UPDATE]
|
|
136
160
|
state
|
|
137
|
-
in [:half_closed_remote, _, :recv]
|
|
138
|
-
unexpected(ErrorCode::STREAM_CLOSED, state, typ, direction)
|
|
139
|
-
in [:half_closed_local, _, :send]
|
|
140
|
-
unreachable(state, typ, direction)
|
|
141
161
|
|
|
142
|
-
#
|
|
143
|
-
in [:
|
|
144
|
-
:closed
|
|
145
|
-
in [:sending_continuation_data, FrameType::WINDOW_UPDATE, :recv]
|
|
146
|
-
state
|
|
147
|
-
in [:sending_continuation_data, FrameType::CONTINUATION, :send] if frame.end_headers?
|
|
162
|
+
# sending_continuation_and_data
|
|
163
|
+
in [:sending_continuation_and_data, :send, FrameType::CONTINUATION] if frame.end_headers?
|
|
148
164
|
:sending_data
|
|
149
|
-
in [:
|
|
165
|
+
in [:sending_continuation_and_data, :send, FrameType::CONTINUATION]
|
|
150
166
|
state
|
|
151
|
-
in [:
|
|
167
|
+
in [:sending_continuation_and_data, :send, _]
|
|
152
168
|
unreachable(state, typ, direction)
|
|
153
|
-
in [:
|
|
154
|
-
|
|
169
|
+
in [:sending_continuation_and_data, :recv, FrameType::PRIORITY]
|
|
170
|
+
state
|
|
171
|
+
in [:sending_continuation_and_data, :recv, FrameType::WINDOW_UPDATE]
|
|
172
|
+
state
|
|
173
|
+
in [:sending_continuation_and_data, _, FrameType::RST_STREAM]
|
|
174
|
+
:closed
|
|
155
175
|
|
|
156
176
|
# sending_continuation
|
|
157
|
-
in [:sending_continuation, FrameType::
|
|
177
|
+
in [:sending_continuation, :send, FrameType::CONTINUATION] if frame.end_headers?
|
|
158
178
|
:closed
|
|
159
|
-
in [:sending_continuation, FrameType::
|
|
179
|
+
in [:sending_continuation, :send, FrameType::CONTINUATION]
|
|
160
180
|
state
|
|
161
|
-
in [:sending_continuation,
|
|
162
|
-
:closed
|
|
163
|
-
in [:sending_continuation, FrameType::CONTINUATION, :send]
|
|
164
|
-
state
|
|
165
|
-
in [:sending_continuation, _, :send]
|
|
181
|
+
in [:sending_continuation, :send, _]
|
|
166
182
|
unreachable(state, typ, direction)
|
|
167
|
-
in [:sending_continuation,
|
|
168
|
-
|
|
183
|
+
in [:sending_continuation, :recv, FrameType::PRIORITY]
|
|
184
|
+
state
|
|
185
|
+
in [:sending_continuation, :recv, FrameType::WINDOW_UPDATE]
|
|
186
|
+
state
|
|
187
|
+
in [:sending_continuation, _, FrameType::RST_STREAM]
|
|
188
|
+
:closed
|
|
169
189
|
|
|
170
190
|
# sending_data
|
|
171
|
-
in [:sending_data, FrameType::DATA
|
|
191
|
+
in [:sending_data, :send, FrameType::DATA] if frame.end_stream?
|
|
172
192
|
:closed
|
|
173
|
-
in [:sending_data, FrameType::
|
|
193
|
+
in [:sending_data, :send, FrameType::DATA]
|
|
194
|
+
state
|
|
195
|
+
in [:sending_data, :send, _]
|
|
196
|
+
unreachable(state, typ, direction)
|
|
197
|
+
in [:sending_data, :recv, FrameType::PRIORITY]
|
|
174
198
|
state
|
|
175
|
-
in [:sending_data, FrameType::
|
|
199
|
+
in [:sending_data, :recv, FrameType::WINDOW_UPDATE]
|
|
176
200
|
state
|
|
177
|
-
in [:sending_data, FrameType::RST_STREAM
|
|
201
|
+
in [:sending_data, _, FrameType::RST_STREAM]
|
|
178
202
|
:closed
|
|
179
|
-
in [:sending_continuation, _, :send]
|
|
180
|
-
unreachable(state, typ, direction)
|
|
181
|
-
in [:sending_continuation, _, :recv]
|
|
182
|
-
unexpected(ErrorCode::PROTOCOL_ERROR, state, typ, direction)
|
|
183
203
|
|
|
184
204
|
# closed
|
|
185
|
-
in [:closed,
|
|
205
|
+
in [:closed, :send, _]
|
|
206
|
+
unreachable(state, typ, direction)
|
|
207
|
+
in [:closed, :recv, FrameType::PRIORITY]
|
|
186
208
|
state
|
|
187
|
-
in [:closed, FrameType::RST_STREAM
|
|
209
|
+
in [:closed, _, FrameType::RST_STREAM]
|
|
188
210
|
state
|
|
189
|
-
in [:closed, _, :send]
|
|
190
|
-
unreachable(state, typ, direction)
|
|
191
|
-
in [:closed, _, :recv]
|
|
192
|
-
unexpected(ErrorCode::STREAM_CLOSED, state, typ, direction)
|
|
193
211
|
|
|
194
212
|
# other
|
|
195
|
-
in [_,
|
|
213
|
+
in [_, :send, _]
|
|
196
214
|
unreachable(state, typ, direction)
|
|
197
|
-
in [_,
|
|
215
|
+
in [_, :recv, _]
|
|
198
216
|
unexpected(ErrorCode::STREAM_CLOSED, state, typ, direction)
|
|
199
217
|
end
|
|
200
218
|
end
|
|
@@ -47,9 +47,9 @@ module Biryani
|
|
|
47
47
|
@h.values.filter(&:active?).length
|
|
48
48
|
end
|
|
49
49
|
|
|
50
|
-
# @return [
|
|
51
|
-
def
|
|
52
|
-
@h.
|
|
50
|
+
# @return [Integer, nil]
|
|
51
|
+
def receiving_continuation_stream_id
|
|
52
|
+
@h.find { |_, ctx| ctx.receiving_continuation? }&.first
|
|
53
53
|
end
|
|
54
54
|
|
|
55
55
|
# @return [Array<Integer>]
|
|
@@ -95,13 +95,13 @@ module Biryani
|
|
|
95
95
|
def close_all
|
|
96
96
|
each do |ctx|
|
|
97
97
|
ctx.tx.close
|
|
98
|
-
ctx.
|
|
98
|
+
ctx.close
|
|
99
99
|
end
|
|
100
100
|
end
|
|
101
101
|
end
|
|
102
102
|
|
|
103
103
|
class StreamContext
|
|
104
|
-
attr_accessor :
|
|
104
|
+
attr_accessor :tx, :send_window, :recv_window, :fragment, :content
|
|
105
105
|
|
|
106
106
|
# @param stream_id [Integer]
|
|
107
107
|
# @param send_initial_window_size [Integer]
|
|
@@ -117,6 +117,21 @@ module Biryani
|
|
|
117
117
|
@state = State.new
|
|
118
118
|
end
|
|
119
119
|
|
|
120
|
+
# @param req [HTTPRequest]
|
|
121
|
+
def <<(req)
|
|
122
|
+
@stream.rx.send(req, move: true)
|
|
123
|
+
end
|
|
124
|
+
|
|
125
|
+
def close
|
|
126
|
+
@state.close
|
|
127
|
+
end
|
|
128
|
+
|
|
129
|
+
# @param frame [Object]
|
|
130
|
+
# @param direction [:send, :recv]
|
|
131
|
+
def state_transition!(frame, direction)
|
|
132
|
+
@state.transition!(frame, direction)
|
|
133
|
+
end
|
|
134
|
+
|
|
120
135
|
# @return [Boolean]
|
|
121
136
|
def closed?
|
|
122
137
|
@state.closed?
|
data/lib/biryani/version.rb
CHANGED
|
@@ -22,7 +22,7 @@ RSpec.describe Connection do
|
|
|
22
22
|
streams_ctx
|
|
23
23
|
end
|
|
24
24
|
it 'should send' do
|
|
25
|
-
streams_ctx1[2].
|
|
25
|
+
streams_ctx1[2].state_transition!(headers1, :recv)
|
|
26
26
|
Connection.send_headers(io, 2, "\x88".b, false, 16_384, streams_ctx1)
|
|
27
27
|
Connection.send_data(io, 2, 'Hello, world!'.b, send_window1, 16_384, streams_ctx1, data_buffer)
|
|
28
28
|
expect(io.string.force_encoding(Encoding::ASCII_8BIT)).to eq "\x00\x00\x01\x01\x04\x00\x00\x00\x02\x88\x00\x00\x0d\x00\x01\x00\x00\x00\x02Hello, world!".b
|
|
@@ -45,7 +45,7 @@ RSpec.describe Connection do
|
|
|
45
45
|
streams_ctx
|
|
46
46
|
end
|
|
47
47
|
it 'should send' do
|
|
48
|
-
streams_ctx2[1].
|
|
48
|
+
streams_ctx2[1].state_transition!(headers2, :recv)
|
|
49
49
|
Connection.send_headers(io, 1, "\x88".b, true, 16_384, streams_ctx2)
|
|
50
50
|
expect(io.string.force_encoding(Encoding::ASCII_8BIT)).to eq "\x00\x00\x01\x01\x05\x00\x00\x00\x01\x88".b
|
|
51
51
|
expect(send_window2.length).to eq 65_535
|
|
@@ -68,7 +68,7 @@ RSpec.describe Connection do
|
|
|
68
68
|
streams_ctx
|
|
69
69
|
end
|
|
70
70
|
it 'should send' do
|
|
71
|
-
streams_ctx3[2].
|
|
71
|
+
streams_ctx3[2].state_transition!(headers3, :recv)
|
|
72
72
|
Connection.send_data(io, 2, 'Hello, world!'.b, send_window3, 16_384, streams_ctx3, data_buffer)
|
|
73
73
|
expect(io.string.force_encoding(Encoding::ASCII_8BIT)).to eq ''.b
|
|
74
74
|
expect(send_window3.length).to eq 65_535
|
|
@@ -92,7 +92,7 @@ RSpec.describe Connection do
|
|
|
92
92
|
streams_ctx
|
|
93
93
|
end
|
|
94
94
|
it 'should send' do
|
|
95
|
-
streams_ctx4[2].
|
|
95
|
+
streams_ctx4[2].state_transition!(headers4, :recv)
|
|
96
96
|
Connection.send_data(io, 2, 'Hello, world!'.b, send_window4, 16_384, streams_ctx4, data_buffer)
|
|
97
97
|
expect(io.string.force_encoding(Encoding::ASCII_8BIT)).to eq ''.b
|
|
98
98
|
expect(send_window4.length).to eq 0
|
|
@@ -13,14 +13,14 @@ RSpec.describe Connection do
|
|
|
13
13
|
Frame::Headers.new(true, true, 2, nil, nil, 'this is dummy', nil)
|
|
14
14
|
end
|
|
15
15
|
it 'should transition' do
|
|
16
|
-
streams_ctx[2].
|
|
16
|
+
streams_ctx[2].state_transition!(headers, :recv)
|
|
17
17
|
Connection.transition_stream_state_send(headers, streams_ctx)
|
|
18
18
|
expect(streams_ctx.length).to eq 2
|
|
19
19
|
end
|
|
20
20
|
|
|
21
21
|
it 'should transition' do
|
|
22
|
-
streams_ctx[1].
|
|
23
|
-
streams_ctx[2].
|
|
22
|
+
streams_ctx[1].state_transition!(headers, :recv)
|
|
23
|
+
streams_ctx[2].state_transition!(headers, :recv)
|
|
24
24
|
streams_ctx.close_all
|
|
25
25
|
expect { streams_ctx[1].tx << nil }.to raise_error Ractor::ClosedError
|
|
26
26
|
expect { streams_ctx[2].tx << nil }.to raise_error Ractor::ClosedError
|
|
@@ -30,7 +30,7 @@ RSpec.describe Connection do
|
|
|
30
30
|
Frame::RstStream.new(2, 0)
|
|
31
31
|
end
|
|
32
32
|
it 'should transition' do
|
|
33
|
-
streams_ctx[2].
|
|
33
|
+
streams_ctx[2].state_transition!(headers, :recv)
|
|
34
34
|
Connection.transition_stream_state_send(rst_stream, streams_ctx)
|
|
35
35
|
expect { streams_ctx[1].tx << nil }.to_not raise_error
|
|
36
36
|
expect { streams_ctx[2].tx << nil }.to_not raise_error
|
|
@@ -1,5 +1,22 @@
|
|
|
1
1
|
require_relative 'spec_helper'
|
|
2
2
|
|
|
3
|
+
RSpec.describe HTTPRequest do
|
|
4
|
+
context 'trailers' do
|
|
5
|
+
it 'should be empty' do
|
|
6
|
+
expect(HTTPRequest.new('get', 'http://localhost:8888/', {}, '').trailers.empty?).to eq true
|
|
7
|
+
end
|
|
8
|
+
|
|
9
|
+
let(:req) do
|
|
10
|
+
HTTPRequest.new('get', 'http://localhost:8888/', { 'trailer' => %w[a b], 'a' => ['1'], 'b' => ['2'], 'c' => ['3'] }, '')
|
|
11
|
+
end
|
|
12
|
+
it 'should return' do
|
|
13
|
+
expect(req.trailers['a']).to eq ['1']
|
|
14
|
+
expect(req.trailers['b']).to eq ['2']
|
|
15
|
+
expect(req.trailers['c']).to eq nil
|
|
16
|
+
end
|
|
17
|
+
end
|
|
18
|
+
end
|
|
19
|
+
|
|
3
20
|
RSpec.describe HTTPRequestBuilder do
|
|
4
21
|
context 'field' do
|
|
5
22
|
let(:builder) do
|
|
@@ -39,7 +56,7 @@ RSpec.describe HTTPRequestBuilder do
|
|
|
39
56
|
builder.build('')
|
|
40
57
|
end
|
|
41
58
|
it 'should field' do
|
|
42
|
-
expect(request.fields['cookie']).to eq 'a=1; b=2; c=3; d=4'
|
|
59
|
+
expect(request.fields['cookie']).to eq ['a=1; b=2; c=3; d=4']
|
|
43
60
|
end
|
|
44
61
|
end
|
|
45
62
|
end
|
|
@@ -48,7 +48,7 @@ RSpec.describe StreamsContext do
|
|
|
48
48
|
streams_ctx = StreamsContext.new(do_nothing_proc)
|
|
49
49
|
streams_ctx.new_context(1, 65_535, 65_535)
|
|
50
50
|
streams_ctx.new_context(2, 65_535, 65_535)
|
|
51
|
-
streams_ctx[2].
|
|
51
|
+
streams_ctx[2].close
|
|
52
52
|
streams_ctx
|
|
53
53
|
end
|
|
54
54
|
let(:data_buffer2) do
|
|
@@ -63,7 +63,7 @@ RSpec.describe StreamsContext do
|
|
|
63
63
|
streams_ctx = StreamsContext.new(do_nothing_proc)
|
|
64
64
|
streams_ctx.new_context(1, 65_535, 65_535)
|
|
65
65
|
streams_ctx.new_context(2, 65_535, 65_535)
|
|
66
|
-
streams_ctx[2].
|
|
66
|
+
streams_ctx[2].close
|
|
67
67
|
streams_ctx
|
|
68
68
|
end
|
|
69
69
|
let(:data_buffer3) do
|
metadata
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: biryani
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.0.
|
|
4
|
+
version: 0.0.7
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- thekuwayama
|
|
@@ -31,6 +31,7 @@ extensions: []
|
|
|
31
31
|
extra_rdoc_files: []
|
|
32
32
|
files:
|
|
33
33
|
- ".github/workflows/ci.yml"
|
|
34
|
+
- ".github/workflows/conformance.yml"
|
|
34
35
|
- ".gitignore"
|
|
35
36
|
- ".rubocop.yml"
|
|
36
37
|
- ".ruby-version"
|
|
@@ -111,7 +112,7 @@ files:
|
|
|
111
112
|
- spec/hpack/huffman_spec.rb
|
|
112
113
|
- spec/hpack/integer_spec.rb
|
|
113
114
|
- spec/hpack/string_spec.rb
|
|
114
|
-
- spec/
|
|
115
|
+
- spec/http_request_spec.rb
|
|
115
116
|
- spec/http_response_spec.rb
|
|
116
117
|
- spec/spec_helper.rb
|
|
117
118
|
- spec/streams_context_spec.rb
|
|
@@ -167,7 +168,7 @@ test_files:
|
|
|
167
168
|
- spec/hpack/huffman_spec.rb
|
|
168
169
|
- spec/hpack/integer_spec.rb
|
|
169
170
|
- spec/hpack/string_spec.rb
|
|
170
|
-
- spec/
|
|
171
|
+
- spec/http_request_spec.rb
|
|
171
172
|
- spec/http_response_spec.rb
|
|
172
173
|
- spec/spec_helper.rb
|
|
173
174
|
- spec/streams_context_spec.rb
|