plum 0.0.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 +7 -0
- data/.gitignore +11 -0
- data/.travis.yml +14 -0
- data/Gemfile +4 -0
- data/Guardfile +7 -0
- data/LICENSE +21 -0
- data/README.md +14 -0
- data/Rakefile +12 -0
- data/bin/.gitkeep +0 -0
- data/examples/local_server.rb +206 -0
- data/examples/static_server.rb +157 -0
- data/lib/plum.rb +21 -0
- data/lib/plum/binary_string.rb +74 -0
- data/lib/plum/connection.rb +201 -0
- data/lib/plum/connection_utils.rb +38 -0
- data/lib/plum/errors.rb +35 -0
- data/lib/plum/event_emitter.rb +19 -0
- data/lib/plum/flow_control.rb +97 -0
- data/lib/plum/frame.rb +163 -0
- data/lib/plum/frame_factory.rb +53 -0
- data/lib/plum/frame_utils.rb +50 -0
- data/lib/plum/hpack/constants.rb +331 -0
- data/lib/plum/hpack/context.rb +55 -0
- data/lib/plum/hpack/decoder.rb +145 -0
- data/lib/plum/hpack/encoder.rb +105 -0
- data/lib/plum/hpack/huffman.rb +42 -0
- data/lib/plum/http_connection.rb +33 -0
- data/lib/plum/https_connection.rb +24 -0
- data/lib/plum/stream.rb +217 -0
- data/lib/plum/stream_utils.rb +58 -0
- data/lib/plum/version.rb +3 -0
- data/plum.gemspec +29 -0
- data/test/plum/connection/test_handle_frame.rb +70 -0
- data/test/plum/hpack/test_context.rb +63 -0
- data/test/plum/hpack/test_decoder.rb +291 -0
- data/test/plum/hpack/test_encoder.rb +49 -0
- data/test/plum/hpack/test_huffman.rb +36 -0
- data/test/plum/stream/test_handle_frame.rb +262 -0
- data/test/plum/test_binary_string.rb +64 -0
- data/test/plum/test_connection.rb +96 -0
- data/test/plum/test_connection_utils.rb +29 -0
- data/test/plum/test_error.rb +13 -0
- data/test/plum/test_flow_control.rb +167 -0
- data/test/plum/test_frame.rb +59 -0
- data/test/plum/test_frame_factory.rb +56 -0
- data/test/plum/test_frame_utils.rb +46 -0
- data/test/plum/test_https_connection.rb +37 -0
- data/test/plum/test_stream.rb +32 -0
- data/test/plum/test_stream_utils.rb +16 -0
- data/test/server.crt +19 -0
- data/test/server.csr +16 -0
- data/test/server.key +27 -0
- data/test/test_helper.rb +28 -0
- data/test/utils/assertions.rb +60 -0
- data/test/utils/server.rb +63 -0
- metadata +234 -0
@@ -0,0 +1,58 @@
|
|
1
|
+
using Plum::BinaryString
|
2
|
+
|
3
|
+
module Plum
|
4
|
+
module StreamUtils
|
5
|
+
# Responds to HTTP request.
|
6
|
+
#
|
7
|
+
# @param headers [Hash<String, String>] The response headers.
|
8
|
+
# @param body [String, IO] The response body.
|
9
|
+
def respond(headers, body = nil, end_stream: true) # TODO: priority, padding
|
10
|
+
if body
|
11
|
+
send_headers(headers, end_stream: false)
|
12
|
+
send_data(body, end_stream: end_stream)
|
13
|
+
else
|
14
|
+
send_headers(headers, end_stream: end_stream)
|
15
|
+
end
|
16
|
+
end
|
17
|
+
|
18
|
+
# Reserves a stream to server push. Sends PUSH_PROMISE and create new stream.
|
19
|
+
#
|
20
|
+
# @param headers [Hash<String, String>] The *request* headers. It must contain all of them: ':authority', ':method', ':scheme' and ':path'.
|
21
|
+
# @return [Stream] The stream to send push response.
|
22
|
+
def promise(headers)
|
23
|
+
stream = @connection.reserve_stream(weight: self.weight + 1, parent: self)
|
24
|
+
encoded = @connection.hpack_encoder.encode(headers)
|
25
|
+
original = Frame.push_promise(id, stream.id, encoded, :end_headers)
|
26
|
+
original.split_headers(@connection.remote_settings[:max_frame_size]).each do |frame|
|
27
|
+
send frame
|
28
|
+
end
|
29
|
+
stream
|
30
|
+
end
|
31
|
+
|
32
|
+
private
|
33
|
+
def send_headers(headers, end_stream:)
|
34
|
+
max = @connection.remote_settings[:max_frame_size]
|
35
|
+
encoded = @connection.hpack_encoder.encode(headers)
|
36
|
+
original_frame = Frame.headers(id, encoded, :end_headers, (end_stream && :end_stream || nil))
|
37
|
+
original_frame.split_headers(max).each do |frame|
|
38
|
+
send frame
|
39
|
+
end
|
40
|
+
@state = :half_closed_local if end_stream
|
41
|
+
end
|
42
|
+
|
43
|
+
def send_data(data, end_stream: true)
|
44
|
+
max = @connection.remote_settings[:max_frame_size]
|
45
|
+
if data.is_a?(IO)
|
46
|
+
while !data.eof? && fragment = data.readpartial(max)
|
47
|
+
send Frame.data(id, fragment, (end_stream && data.eof? && :end_stream))
|
48
|
+
end
|
49
|
+
else
|
50
|
+
original = Frame.data(id, data, (end_stream && :end_stream))
|
51
|
+
original.split_data(max).each do |frame|
|
52
|
+
send frame
|
53
|
+
end
|
54
|
+
end
|
55
|
+
@state = :half_closed_local if end_stream
|
56
|
+
end
|
57
|
+
end
|
58
|
+
end
|
data/lib/plum/version.rb
ADDED
data/plum.gemspec
ADDED
@@ -0,0 +1,29 @@
|
|
1
|
+
lib = File.expand_path("../lib", __FILE__)
|
2
|
+
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
|
3
|
+
require "plum/version"
|
4
|
+
|
5
|
+
Gem::Specification.new do |spec|
|
6
|
+
spec.name = "plum"
|
7
|
+
spec.version = Plum::VERSION
|
8
|
+
spec.authors = ["rhenium"]
|
9
|
+
spec.email = ["k@rhe.jp"]
|
10
|
+
|
11
|
+
spec.summary = %q{A minimal implementation of HTTP/2 server.}
|
12
|
+
spec.description = spec.summary
|
13
|
+
spec.homepage = "https://github.com/rhenium/plum"
|
14
|
+
spec.license = "MIT"
|
15
|
+
|
16
|
+
spec.files = `git ls-files -z`.split("\x00")
|
17
|
+
spec.executables = spec.files.grep(%r{^bin/[^.]}) { |f| File.basename(f) }
|
18
|
+
spec.test_files = spec.files.grep(%r{^test/})
|
19
|
+
spec.require_paths = ["lib"]
|
20
|
+
|
21
|
+
spec.add_development_dependency "bundler", "~> 1.10"
|
22
|
+
spec.add_development_dependency "rake"
|
23
|
+
spec.add_development_dependency "yard"
|
24
|
+
spec.add_development_dependency "minitest", "~> 5.7.0"
|
25
|
+
spec.add_development_dependency "simplecov"
|
26
|
+
spec.add_development_dependency "codeclimate-test-reporter"
|
27
|
+
spec.add_development_dependency "guard"
|
28
|
+
spec.add_development_dependency "guard-minitest"
|
29
|
+
end
|
@@ -0,0 +1,70 @@
|
|
1
|
+
require "test_helper"
|
2
|
+
|
3
|
+
using Plum::BinaryString
|
4
|
+
|
5
|
+
class ServerConnectionHandleFrameTest < Minitest::Test
|
6
|
+
## SETTINGS
|
7
|
+
def test_server_handle_settings
|
8
|
+
open_server_connection {|con|
|
9
|
+
assert_equal(4096, con.remote_settings[:header_table_size])
|
10
|
+
con << Frame.new(type: :settings, stream_id: 0, payload: "\x00\x01\x00\x00\x10\x10").assemble
|
11
|
+
assert_equal(0x1010, con.remote_settings[:header_table_size])
|
12
|
+
}
|
13
|
+
end
|
14
|
+
|
15
|
+
def test_server_handle_settings
|
16
|
+
open_server_connection {|con|
|
17
|
+
assert_no_error {
|
18
|
+
con << Frame.new(type: :settings, stream_id: 0, flags: [:ack], payload: "").assemble
|
19
|
+
}
|
20
|
+
assert_connection_error(:frame_size_error) {
|
21
|
+
con << Frame.new(type: :settings, stream_id: 0, flags: [:ack], payload: "\x00").assemble
|
22
|
+
}
|
23
|
+
}
|
24
|
+
end
|
25
|
+
|
26
|
+
def test_server_handle_settings_invalid
|
27
|
+
open_server_connection {|con|
|
28
|
+
assert_no_error {
|
29
|
+
con << Frame.new(type: :settings, stream_id: 0, payload: "\xff\x01\x00\x00\x10\x10").assemble
|
30
|
+
}
|
31
|
+
}
|
32
|
+
end
|
33
|
+
|
34
|
+
## PING
|
35
|
+
def test_server_handle_ping
|
36
|
+
open_server_connection {|con|
|
37
|
+
con << Frame.new(type: :ping, flags: [], stream_id: 0, payload: "AAAAAAAA").assemble
|
38
|
+
last = sent_frames.last
|
39
|
+
assert_equal(:ping, last.type)
|
40
|
+
assert_equal([:ack], last.flags)
|
41
|
+
assert_equal("AAAAAAAA", last.payload)
|
42
|
+
}
|
43
|
+
end
|
44
|
+
|
45
|
+
def test_server_handle_ping_error
|
46
|
+
open_server_connection {|con|
|
47
|
+
assert_connection_error(:frame_size_error) {
|
48
|
+
con << Frame.new(type: :ping, stream_id: 0, payload: "A" * 7).assemble
|
49
|
+
}
|
50
|
+
}
|
51
|
+
end
|
52
|
+
|
53
|
+
def test_server_handle_ping_ack
|
54
|
+
open_server_connection {|con|
|
55
|
+
con << Frame.new(type: :ping, flags: [:ack], stream_id: 0, payload: "A" * 8).assemble
|
56
|
+
last = sent_frames.last
|
57
|
+
refute_equal(:ping, last.type) if last
|
58
|
+
}
|
59
|
+
end
|
60
|
+
|
61
|
+
## GOAWAY
|
62
|
+
def test_server_handle_goaway_reply
|
63
|
+
open_server_connection {|con|
|
64
|
+
assert_no_error {
|
65
|
+
con << Frame.goaway(1234, :stream_closed).assemble
|
66
|
+
}
|
67
|
+
assert_equal(:goaway, sent_frames.last.type)
|
68
|
+
}
|
69
|
+
end
|
70
|
+
end
|
@@ -0,0 +1,63 @@
|
|
1
|
+
require "test_helper"
|
2
|
+
|
3
|
+
class HPACKContextTest < Minitest::Test
|
4
|
+
def test_store
|
5
|
+
context = new_context
|
6
|
+
context.store("あああ", "いい")
|
7
|
+
assert_equal([["あああ", "いい"]], context.dynamic_table)
|
8
|
+
assert_equal("あああいい".bytesize + 32, context.size)
|
9
|
+
end
|
10
|
+
|
11
|
+
def test_store_eviction
|
12
|
+
context = new_context(1)
|
13
|
+
context.store("あああ", "いい")
|
14
|
+
assert_equal([], context.dynamic_table)
|
15
|
+
assert_equal(0, context.size)
|
16
|
+
end
|
17
|
+
|
18
|
+
def test_fetch_static
|
19
|
+
context = new_context
|
20
|
+
assert_equal([":method", "POST"], context.fetch(3))
|
21
|
+
end
|
22
|
+
|
23
|
+
def test_fetch_dynamic
|
24
|
+
context = new_context
|
25
|
+
context.store("あああ", "いい")
|
26
|
+
assert_equal(["あああ", "いい"], context.fetch(62))
|
27
|
+
end
|
28
|
+
|
29
|
+
def test_fetch_error
|
30
|
+
context = new_context
|
31
|
+
context.store("あああ", "いい")
|
32
|
+
assert_raises(Plum::HPACKError) {
|
33
|
+
context.fetch(64)
|
34
|
+
}
|
35
|
+
end
|
36
|
+
|
37
|
+
def test_search_static
|
38
|
+
context = new_context
|
39
|
+
i1 = context.search(":method", "POST")
|
40
|
+
assert_equal(3, i1)
|
41
|
+
end
|
42
|
+
|
43
|
+
def test_search_dynamic
|
44
|
+
context = new_context
|
45
|
+
context.store("あああ", "abc")
|
46
|
+
context.store("あああ", "いい")
|
47
|
+
i1 = context.search("あああ", "abc")
|
48
|
+
assert_equal(63, i1)
|
49
|
+
i2 = context.search("あああ", "AAA")
|
50
|
+
assert_equal(nil, i2)
|
51
|
+
i3 = context.search("あああ", nil)
|
52
|
+
assert_equal(62, i3)
|
53
|
+
end
|
54
|
+
|
55
|
+
private
|
56
|
+
def new_context(limit = 1 << 31)
|
57
|
+
@c ||= Class.new {
|
58
|
+
include Plum::HPACK::Context
|
59
|
+
public *Plum::HPACK::Context.private_instance_methods
|
60
|
+
}
|
61
|
+
@c.new(limit)
|
62
|
+
end
|
63
|
+
end
|
@@ -0,0 +1,291 @@
|
|
1
|
+
require "test_helper"
|
2
|
+
|
3
|
+
class HPACKDecoderTest < Minitest::Test
|
4
|
+
# C.1.1
|
5
|
+
def test_hpack_read_integer_small
|
6
|
+
buf = [0b11001010, 0b00001111].pack("C*")
|
7
|
+
result = new_decoder.__send__(:read_integer!, buf, 5)
|
8
|
+
assert_equal(10, result)
|
9
|
+
assert_equal([0b00001111].pack("C*"), buf)
|
10
|
+
end
|
11
|
+
|
12
|
+
# C.1.2
|
13
|
+
def test_hpack_read_integer_big
|
14
|
+
buf = [0b11011111, 0b10011010, 0b00001010, 0b00001111].pack("C*")
|
15
|
+
result = new_decoder.__send__(:read_integer!, buf, 5)
|
16
|
+
assert_equal(1337, result)
|
17
|
+
assert_equal([0b00001111].pack("C*"), buf)
|
18
|
+
end
|
19
|
+
|
20
|
+
# C.1.3
|
21
|
+
def test_hpack_read_integer_8prefix
|
22
|
+
buf = [0b00101010, 0b00001111].pack("C*")
|
23
|
+
result = new_decoder.__send__(:read_integer!, buf, 8)
|
24
|
+
assert_equal(42, result)
|
25
|
+
assert_equal([0b00001111].pack("C*"), buf)
|
26
|
+
end
|
27
|
+
|
28
|
+
def test_hpack_read_integer_too_big
|
29
|
+
buf = [0b11011111, 0b10011010, 0b10001010, 0b10001111, 0b11111111, 0b00000011].pack("C*")
|
30
|
+
assert_raises(HPACKError) {
|
31
|
+
new_decoder.__send__(:read_integer!, buf, 5)
|
32
|
+
}
|
33
|
+
end
|
34
|
+
|
35
|
+
def test_hpack_read_integer_incomplete
|
36
|
+
buf = [0b11011111, 0b10011010].pack("C*")
|
37
|
+
assert_raises(HPACKError) {
|
38
|
+
new_decoder.__send__(:read_integer!, buf, 5)
|
39
|
+
}
|
40
|
+
end
|
41
|
+
|
42
|
+
# C.2.1
|
43
|
+
def test_hpack_decode_indexing
|
44
|
+
encoded = "\x40\x0acustom-key\x0dcustom-header"
|
45
|
+
result = new_decoder.decode(encoded)
|
46
|
+
assert_equal([["custom-key", "custom-header"]], result)
|
47
|
+
end
|
48
|
+
|
49
|
+
def test_hpack_decode_indexing_imcomplete
|
50
|
+
encoded = "\x40\x0acus"
|
51
|
+
assert_raises(HTTPError) {
|
52
|
+
new_decoder.decode(encoded)
|
53
|
+
}
|
54
|
+
end
|
55
|
+
|
56
|
+
# C.2.2
|
57
|
+
def test_hpack_decode_without_indexing
|
58
|
+
encoded = "\x04\x0c/sample/path"
|
59
|
+
result = new_decoder.decode(encoded)
|
60
|
+
assert_equal([[":path", "/sample/path"]], result)
|
61
|
+
end
|
62
|
+
|
63
|
+
# C.2.3
|
64
|
+
def test_hpack_decode_without_indexing2
|
65
|
+
encoded = "\x10\x08password\x06secret"
|
66
|
+
result = new_decoder.decode(encoded)
|
67
|
+
assert_equal([["password", "secret"]], result)
|
68
|
+
end
|
69
|
+
|
70
|
+
# C.2.4
|
71
|
+
def test_hpack_decode_index
|
72
|
+
encoded = "\x82"
|
73
|
+
result = new_decoder.decode(encoded)
|
74
|
+
assert_equal([[":method", "GET"]], result)
|
75
|
+
end
|
76
|
+
|
77
|
+
# C.3.1
|
78
|
+
def test_hpack_decode_headers_without_huffman
|
79
|
+
decoder = new_decoder
|
80
|
+
encoded = "\x82\x86\x84\x41\x0f\x77\x77\x77\x2e\x65\x78\x61\x6d\x70\x6c\x65\x2e\x63\x6f\x6d"
|
81
|
+
result = decoder.decode(encoded)
|
82
|
+
expected = [
|
83
|
+
[":method", "GET"],
|
84
|
+
[":scheme", "http"],
|
85
|
+
[":path", "/"],
|
86
|
+
[":authority", "www.example.com"]
|
87
|
+
]
|
88
|
+
assert_equal(expected, result)
|
89
|
+
|
90
|
+
decoder # for C.3.2
|
91
|
+
end
|
92
|
+
|
93
|
+
# C.3.2
|
94
|
+
def test_hpack_decode_headers_without_huffman2
|
95
|
+
decoder = test_hpack_decode_headers_without_huffman
|
96
|
+
encoded = "\x82\x86\x84\xbe\x58\x08\x6e\x6f\x2d\x63\x61\x63\x68\x65"
|
97
|
+
result = decoder.decode(encoded)
|
98
|
+
expected = [
|
99
|
+
[":method", "GET"],
|
100
|
+
[":scheme", "http"],
|
101
|
+
[":path", "/"],
|
102
|
+
[":authority", "www.example.com"],
|
103
|
+
["cache-control", "no-cache"],
|
104
|
+
]
|
105
|
+
assert_equal(expected, result)
|
106
|
+
|
107
|
+
decoder # for C.3.3
|
108
|
+
end
|
109
|
+
|
110
|
+
# C.3.3
|
111
|
+
def test_hpack_decode_headers_without_huffman3
|
112
|
+
decoder = test_hpack_decode_headers_without_huffman2
|
113
|
+
encoded = "\x82\x87\x85\xbf\x40\x0a\x63\x75\x73\x74\x6f\x6d\x2d\x6b\x65\x79\x0c\x63\x75\x73\x74\x6f\x6d\x2d\x76\x61\x6c\x75\x65"
|
114
|
+
result = decoder.decode(encoded)
|
115
|
+
expected = [
|
116
|
+
[":method", "GET"],
|
117
|
+
[":scheme", "https"],
|
118
|
+
[":path", "/index.html"],
|
119
|
+
[":authority", "www.example.com"],
|
120
|
+
["custom-key", "custom-value"],
|
121
|
+
]
|
122
|
+
assert_equal(expected, result)
|
123
|
+
end
|
124
|
+
|
125
|
+
# C.4.1
|
126
|
+
def test_hpack_decode_headers_with_huffman
|
127
|
+
decoder = new_decoder
|
128
|
+
encoded = "\x82\x86\x84\x41\x8c\xf1\xe3\xc2\xe5\xf2\x3a\x6b\xa0\xab\x90\xf4\xff"
|
129
|
+
result = decoder.decode(encoded)
|
130
|
+
expected = [
|
131
|
+
[":method", "GET"],
|
132
|
+
[":scheme", "http"],
|
133
|
+
[":path", "/"],
|
134
|
+
[":authority", "www.example.com"]
|
135
|
+
]
|
136
|
+
assert_equal(expected, result)
|
137
|
+
|
138
|
+
decoder # for C.4.2
|
139
|
+
end
|
140
|
+
|
141
|
+
# C.4.2
|
142
|
+
def test_hpack_decode_headers_with_huffman2
|
143
|
+
decoder = test_hpack_decode_headers_with_huffman
|
144
|
+
encoded = "\x82\x86\x84\xbe\x58\x86\xa8\xeb\x10\x64\x9c\xbf"
|
145
|
+
result = decoder.decode(encoded)
|
146
|
+
expected = [
|
147
|
+
[":method", "GET"],
|
148
|
+
[":scheme", "http"],
|
149
|
+
[":path", "/"],
|
150
|
+
[":authority", "www.example.com"],
|
151
|
+
["cache-control", "no-cache"],
|
152
|
+
]
|
153
|
+
assert_equal(expected, result)
|
154
|
+
|
155
|
+
decoder # for C.4.3
|
156
|
+
end
|
157
|
+
|
158
|
+
# C.4.3
|
159
|
+
def test_hpack_decode_headers_with_huffman3
|
160
|
+
decoder = test_hpack_decode_headers_with_huffman2
|
161
|
+
encoded = "\x82\x87\x85\xbf\x40\x88\x25\xa8\x49\xe9\x5b\xa9\x7d\x7f\x89\x25\xa8\x49\xe9\x5b\xb8\xe8\xb4\xbf"
|
162
|
+
result = decoder.decode(encoded)
|
163
|
+
expected = [
|
164
|
+
[":method", "GET"],
|
165
|
+
[":scheme", "https"],
|
166
|
+
[":path", "/index.html"],
|
167
|
+
[":authority", "www.example.com"],
|
168
|
+
["custom-key", "custom-value"],
|
169
|
+
]
|
170
|
+
assert_equal(expected, result)
|
171
|
+
end
|
172
|
+
|
173
|
+
# C.5.1
|
174
|
+
def test_hpack_decode_response_without_huffman
|
175
|
+
decoder = new_decoder(256)
|
176
|
+
encoded = "\x48\x03\x33\x30\x32\x58\x07\x70\x72\x69\x76\x61\x74\x65\x61\x1d\x4d\x6f\x6e\x2c\x20\x32\x31\x20\x4f\x63\x74\x20\x32\x30\x31\x33\x20\x32\x30\x3a\x31\x33\x3a\x32\x31\x20\x47\x4d\x54\x6e\x17\x68\x74\x74\x70\x73\x3a\x2f\x2f\x77\x77\x77\x2e\x65\x78\x61\x6d\x70\x6c\x65\x2e\x63\x6f\x6d"
|
177
|
+
result = decoder.decode(encoded)
|
178
|
+
expected = [
|
179
|
+
[":status", "302"],
|
180
|
+
["cache-control", "private"],
|
181
|
+
["date", "Mon, 21 Oct 2013 20:13:21 GMT"],
|
182
|
+
["location", "https://www.example.com"]
|
183
|
+
]
|
184
|
+
assert_equal(expected, result)
|
185
|
+
|
186
|
+
decoder # for C.5.2
|
187
|
+
end
|
188
|
+
|
189
|
+
# C.5.2
|
190
|
+
def test_hpack_decode_response_without_huffman2
|
191
|
+
decoder = test_hpack_decode_response_without_huffman
|
192
|
+
encoded = "\x48\x03\x33\x30\x37\xc1\xc0\xbf"
|
193
|
+
result = decoder.decode(encoded)
|
194
|
+
expected = [
|
195
|
+
[":status", "307"],
|
196
|
+
["cache-control", "private"],
|
197
|
+
["date", "Mon, 21 Oct 2013 20:13:21 GMT"],
|
198
|
+
["location", "https://www.example.com"]
|
199
|
+
]
|
200
|
+
assert_equal(expected, result)
|
201
|
+
refute_includes(decoder.dynamic_table, [":status", "302"]) # evicted
|
202
|
+
|
203
|
+
decoder # for C.5.3
|
204
|
+
end
|
205
|
+
|
206
|
+
# C.5.3
|
207
|
+
def test_hpack_decode_response_without_huffman3
|
208
|
+
decoder = test_hpack_decode_response_without_huffman2
|
209
|
+
encoded = "\x88\xc1\x61\x1d\x4d\x6f\x6e\x2c\x20\x32\x31\x20\x4f\x63\x74\x20\x32\x30\x31\x33\x20\x32\x30\x3a\x31\x33\x3a\x32\x32\x20\x47\x4d\x54\xc0\x5a\x04\x67\x7a\x69\x70\x77\x38\x66\x6f\x6f\x3d\x41\x53\x44\x4a\x4b\x48\x51\x4b\x42\x5a\x58\x4f\x51\x57\x45\x4f\x50\x49\x55\x41\x58\x51\x57\x45\x4f\x49\x55\x3b\x20\x6d\x61\x78\x2d\x61\x67\x65\x3d\x33\x36\x30\x30\x3b\x20\x76\x65\x72\x73\x69\x6f\x6e\x3d\x31"
|
210
|
+
result = decoder.decode(encoded)
|
211
|
+
expected = [
|
212
|
+
[":status", "200"],
|
213
|
+
["cache-control", "private"],
|
214
|
+
["date", "Mon, 21 Oct 2013 20:13:22 GMT"],
|
215
|
+
["location", "https://www.example.com"],
|
216
|
+
["content-encoding", "gzip"],
|
217
|
+
["set-cookie", "foo=ASDJKHQKBZXOQWEOPIUAXQWEOIU; max-age=3600; version=1"]
|
218
|
+
]
|
219
|
+
assert_equal(expected, result)
|
220
|
+
end
|
221
|
+
|
222
|
+
# C.6.1
|
223
|
+
def test_hpack_decode_response_with_huffman
|
224
|
+
decoder = new_decoder(256)
|
225
|
+
encoded = "\x48\x82\x64\x02\x58\x85\xae\xc3\x77\x1a\x4b\x61\x96\xd0\x7a\xbe\x94\x10\x54\xd4\x44\xa8\x20\x05\x95\x04\x0b\x81\x66\xe0\x82\xa6\x2d\x1b\xff\x6e\x91\x9d\x29\xad\x17\x18\x63\xc7\x8f\x0b\x97\xc8\xe9\xae\x82\xae\x43\xd3"
|
226
|
+
result = decoder.decode(encoded)
|
227
|
+
expected = [
|
228
|
+
[":status", "302"],
|
229
|
+
["cache-control", "private"],
|
230
|
+
["date", "Mon, 21 Oct 2013 20:13:21 GMT"],
|
231
|
+
["location", "https://www.example.com"]
|
232
|
+
]
|
233
|
+
assert_equal(expected, result)
|
234
|
+
|
235
|
+
decoder # for C.6.2
|
236
|
+
end
|
237
|
+
|
238
|
+
# C.6.2
|
239
|
+
def test_hpack_decode_response_with_huffman2
|
240
|
+
decoder = test_hpack_decode_response_with_huffman
|
241
|
+
encoded = "\x48\x83\x64\x0e\xff\xc1\xc0\xbf"
|
242
|
+
result = decoder.decode(encoded)
|
243
|
+
expected = [
|
244
|
+
[":status", "307"],
|
245
|
+
["cache-control", "private"],
|
246
|
+
["date", "Mon, 21 Oct 2013 20:13:21 GMT"],
|
247
|
+
["location", "https://www.example.com"]
|
248
|
+
]
|
249
|
+
assert_equal(expected, result)
|
250
|
+
refute_includes(decoder.dynamic_table, [":status", "302"]) # evicted
|
251
|
+
|
252
|
+
decoder # for C.6.3
|
253
|
+
end
|
254
|
+
|
255
|
+
# C.6.3
|
256
|
+
def test_hpack_decode_response_with_huffman3
|
257
|
+
decoder = test_hpack_decode_response_with_huffman2
|
258
|
+
encoded = "\x88\xc1\x61\x96\xd0\x7a\xbe\x94\x10\x54\xd4\x44\xa8\x20\x05\x95\x04\x0b\x81\x66\xe0\x84\xa6\x2d\x1b\xff\xc0\x5a\x83\x9b\xd9\xab\x77\xad\x94\xe7\x82\x1d\xd7\xf2\xe6\xc7\xb3\x35\xdf\xdf\xcd\x5b\x39\x60\xd5\xaf\x27\x08\x7f\x36\x72\xc1\xab\x27\x0f\xb5\x29\x1f\x95\x87\x31\x60\x65\xc0\x03\xed\x4e\xe5\xb1\x06\x3d\x50\x07"
|
259
|
+
result = decoder.decode(encoded)
|
260
|
+
expected = [
|
261
|
+
[":status", "200"],
|
262
|
+
["cache-control", "private"],
|
263
|
+
["date", "Mon, 21 Oct 2013 20:13:22 GMT"],
|
264
|
+
["location", "https://www.example.com"],
|
265
|
+
["content-encoding", "gzip"],
|
266
|
+
["set-cookie", "foo=ASDJKHQKBZXOQWEOPIUAXQWEOIU; max-age=3600; version=1"]
|
267
|
+
]
|
268
|
+
assert_equal(expected, result)
|
269
|
+
end
|
270
|
+
|
271
|
+
def test_hpack_decode_index_zero
|
272
|
+
decoder = new_decoder(256)
|
273
|
+
encoded = "\x80"
|
274
|
+
assert_raises(HPACKError) {
|
275
|
+
decoder.decode(encoded)
|
276
|
+
}
|
277
|
+
end
|
278
|
+
|
279
|
+
def test_hpack_decode_changing_limit
|
280
|
+
decoder = new_decoder(256)
|
281
|
+
encoded = "\x34"
|
282
|
+
assert_equal(256, decoder.limit)
|
283
|
+
decoder.decode(encoded)
|
284
|
+
assert_equal(0b00010100, decoder.limit)
|
285
|
+
end
|
286
|
+
|
287
|
+
private
|
288
|
+
def new_decoder(settings_header_table_size = 1 << 31)
|
289
|
+
Plum::HPACK::Decoder.new(settings_header_table_size)
|
290
|
+
end
|
291
|
+
end
|