http-2-next 0.2.6 → 0.3.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/README.md +1 -1
- data/lib/http/2/next.rb +1 -3
- data/lib/http/2/next/buffer.rb +7 -1
- data/lib/http/2/next/connection.rb +27 -16
- data/lib/http/2/next/emitter.rb +2 -3
- data/lib/http/2/next/error.rb +1 -0
- data/lib/http/2/next/extensions.rb +11 -1
- data/lib/http/2/next/flow_buffer.rb +1 -0
- data/lib/http/2/next/framer.rb +12 -10
- data/lib/http/2/next/header.rb +35 -0
- data/lib/http/2/next/header/compressor.rb +137 -0
- data/lib/http/2/next/header/decompressor.rb +141 -0
- data/lib/http/2/next/{compressor.rb → header/encoding_context.rb} +7 -298
- data/lib/http/2/next/{huffman.rb → header/huffman.rb} +5 -2
- data/lib/http/2/next/{huffman_statemachine.rb → header/huffman_statemachine.rb} +0 -0
- data/lib/http/2/next/server.rb +6 -4
- data/lib/http/2/next/stream.rb +3 -4
- data/lib/http/2/next/version.rb +1 -1
- data/lib/tasks/generate_huffman_table.rb +4 -4
- data/sig/buffer.rbs +21 -0
- data/sig/client.rbs +9 -0
- data/sig/connection.rbs +73 -0
- data/sig/emitter.rbs +13 -0
- data/sig/flow_buffer.rbs +23 -0
- data/sig/frame_buffer.rbs +13 -0
- data/sig/framer.rbs +25 -0
- data/sig/header.rbs +15 -0
- data/sig/header/compressor.rbs +23 -0
- data/sig/header/decompressor.rbs +22 -0
- data/sig/header/encoding_context.rbs +34 -0
- data/sig/header/huffman.rbs +9 -0
- data/sig/next.rbs +56 -0
- data/sig/server.rbs +12 -0
- data/sig/stream.rbs +77 -0
- metadata +27 -9
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: c4ce4e3302e32d2c860cf38d25f82186a233ae9bb446bde932d185ca398d4eec
|
4
|
+
data.tar.gz: c2428f11638405fa7c92390745f4c21022627500fe54d564a46f06e83967d126
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 5a10531d34c3d37989a6de029b4bfdca1b9742b7614a7785179bea4d8794568b58bd88641c259721520f803b1f6577c2e41ddb49bff75564edfbdf9d422124fc
|
7
|
+
data.tar.gz: 8d9d7e74f52d079a7f9021bcd7679bd7544b1e30d17cdd5f42de24326812c46f4c3f8208f3132ba0e9d0df46b6c947238781a48abbf6d1cacc95da69b3227293
|
data/README.md
CHANGED
@@ -2,7 +2,7 @@
|
|
2
2
|
|
3
3
|
[![Gem Version](https://badge.fury.io/rb/http-2-next.svg)](http://rubygems.org/gems/http-2-next)
|
4
4
|
[![Build status](https://gitlab.com/honeyryderchuck/http-2-next/badges/master/pipeline.svg)](https://gitlab.com/honeyryderchuck/http-2-next/commits/master)
|
5
|
-
[![coverage report](https://gitlab.com/honeyryderchuck/http-2-next/badges/master/coverage.svg)](https://honeyryderchuck.gitlab.io/http-2-next/coverage/#_AllFiles)
|
5
|
+
[![coverage report](https://gitlab.com/honeyryderchuck/http-2-next/badges/master/coverage.svg?job=coverage)](https://honeyryderchuck.gitlab.io/http-2-next/coverage/#_AllFiles)
|
6
6
|
|
7
7
|
**Attention!** This is a fork of the [http-2](https://github.com/igrigorik/http-2) gem.
|
8
8
|
|
data/lib/http/2/next.rb
CHANGED
@@ -6,9 +6,7 @@ require "http/2/next/error"
|
|
6
6
|
require "http/2/next/emitter"
|
7
7
|
require "http/2/next/buffer"
|
8
8
|
require "http/2/next/flow_buffer"
|
9
|
-
require "http/2/next/
|
10
|
-
require "http/2/next/huffman_statemachine"
|
11
|
-
require "http/2/next/compressor"
|
9
|
+
require "http/2/next/header"
|
12
10
|
require "http/2/next/framer"
|
13
11
|
require "http/2/next/connection"
|
14
12
|
require "http/2/next/client"
|
data/lib/http/2/next/buffer.rb
CHANGED
@@ -8,6 +8,8 @@ module HTTP2Next
|
|
8
8
|
class Buffer
|
9
9
|
extend Forwardable
|
10
10
|
|
11
|
+
using StringExtensions
|
12
|
+
|
11
13
|
def_delegators :@buffer, :ord, :encoding, :setbyte, :unpack,
|
12
14
|
:size, :each_byte, :to_str, :to_s, :length, :inspect,
|
13
15
|
:[], :[]=, :empty?, :bytesize, :include?
|
@@ -28,6 +30,10 @@ module HTTP2Next
|
|
28
30
|
Buffer.new(@buffer.slice!(0, n))
|
29
31
|
end
|
30
32
|
|
33
|
+
def unpack1(*arg)
|
34
|
+
@buffer.unpack1(*arg)
|
35
|
+
end
|
36
|
+
|
31
37
|
# Emulate StringIO#getbyte: slice first byte from buffer.
|
32
38
|
def getbyte
|
33
39
|
read(1).ord
|
@@ -61,7 +67,7 @@ module HTTP2Next
|
|
61
67
|
# Slice unsigned 32-bit integer from buffer.
|
62
68
|
# @return [Integer]
|
63
69
|
def read_uint32
|
64
|
-
read(4).
|
70
|
+
read(4).unpack1(UINT32)
|
65
71
|
end
|
66
72
|
|
67
73
|
# Ensures that data that is added is binary encoded as well,
|
@@ -57,12 +57,11 @@ module HTTP2Next
|
|
57
57
|
# Size of current connection flow control window (by default, set to
|
58
58
|
# infinity, but is automatically updated on receipt of peer settings).
|
59
59
|
attr_reader :local_window
|
60
|
-
attr_reader :remote_window
|
60
|
+
attr_reader :remote_window, :remote_settings
|
61
61
|
alias window local_window
|
62
62
|
|
63
63
|
# Current settings value for local and peer
|
64
64
|
attr_reader :local_settings
|
65
|
-
attr_reader :remote_settings
|
66
65
|
|
67
66
|
# Pending settings value
|
68
67
|
# Sent but not ack'ed settings
|
@@ -105,6 +104,7 @@ module HTTP2Next
|
|
105
104
|
|
106
105
|
@h2c_upgrade = nil
|
107
106
|
@closed_since = nil
|
107
|
+
@received_frame = false
|
108
108
|
end
|
109
109
|
|
110
110
|
def closed?
|
@@ -176,7 +176,7 @@ module HTTP2Next
|
|
176
176
|
# @param settings [Array or Hash]
|
177
177
|
def settings(payload)
|
178
178
|
payload = payload.to_a
|
179
|
-
|
179
|
+
validate_settings(@local_role, payload)
|
180
180
|
@pending_settings << payload
|
181
181
|
send(type: :settings, stream: 0, payload: payload)
|
182
182
|
@pending_settings << payload
|
@@ -213,6 +213,11 @@ module HTTP2Next
|
|
213
213
|
end
|
214
214
|
|
215
215
|
while (frame = @framer.parse(@recv_buffer))
|
216
|
+
if is_a?(Client) && !@received_frame
|
217
|
+
connection_error(:protocol_error, msg: "didn't receive settings") if frame[:type] != :settings
|
218
|
+
@received_frame = true
|
219
|
+
end
|
220
|
+
|
216
221
|
# Implementations MUST discard frames
|
217
222
|
# that have unknown or unsupported types.
|
218
223
|
if frame[:type].nil?
|
@@ -521,8 +526,6 @@ module HTTP2Next
|
|
521
526
|
def validate_settings(role, settings)
|
522
527
|
settings.each do |key, v|
|
523
528
|
case key
|
524
|
-
when :settings_header_table_size
|
525
|
-
# Any value is valid
|
526
529
|
when :settings_enable_push
|
527
530
|
case role
|
528
531
|
when :server
|
@@ -530,32 +533,41 @@ module HTTP2Next
|
|
530
533
|
# Clients MUST reject any attempt to change the
|
531
534
|
# SETTINGS_ENABLE_PUSH setting to a value other than 0 by treating the
|
532
535
|
# message as a connection error (Section 5.4.1) of type PROTOCOL_ERROR.
|
533
|
-
|
536
|
+
next if v.zero?
|
537
|
+
|
538
|
+
connection_error(:protocol_error, msg: "invalid #{key} value")
|
534
539
|
when :client
|
535
540
|
# Any value other than 0 or 1 MUST be treated as a
|
536
541
|
# connection error (Section 5.4.1) of type PROTOCOL_ERROR.
|
537
|
-
|
542
|
+
next if v.zero? || v == 1
|
543
|
+
|
544
|
+
connection_error(:protocol_error, msg: "invalid #{key} value")
|
538
545
|
end
|
539
|
-
when :settings_max_concurrent_streams
|
540
|
-
# Any value is valid
|
541
546
|
when :settings_initial_window_size
|
542
547
|
# Values above the maximum flow control window size of 2^31-1 MUST
|
543
548
|
# be treated as a connection error (Section 5.4.1) of type
|
544
549
|
# FLOW_CONTROL_ERROR.
|
545
|
-
|
550
|
+
next if v <= 0x7fffffff
|
551
|
+
|
552
|
+
connection_error(:flow_control_error, msg: "invalid #{key} value")
|
546
553
|
when :settings_max_frame_size
|
547
554
|
# The initial value is 2^14 (16,384) octets. The value advertised
|
548
555
|
# by an endpoint MUST be between this initial value and the maximum
|
549
556
|
# allowed frame size (2^24-1 or 16,777,215 octets), inclusive.
|
550
557
|
# Values outside this range MUST be treated as a connection error
|
551
558
|
# (Section 5.4.1) of type PROTOCOL_ERROR.
|
552
|
-
|
553
|
-
|
559
|
+
next if v >= 16_384 && v <= 16_777_215
|
560
|
+
|
561
|
+
connection_error(:protocol_error, msg: "invalid #{key} value")
|
562
|
+
# when :settings_max_concurrent_streams
|
563
|
+
# Any value is valid
|
564
|
+
# when :settings_header_table_size
|
565
|
+
# Any value is valid
|
566
|
+
# when :settings_max_header_list_size
|
554
567
|
# Any value is valid
|
555
568
|
# else # ignore unknown settings
|
556
569
|
end
|
557
570
|
end
|
558
|
-
nil
|
559
571
|
end
|
560
572
|
|
561
573
|
# Update connection settings based on parameters set by the peer.
|
@@ -572,8 +584,7 @@ module HTTP2Next
|
|
572
584
|
# Process pending settings we have sent.
|
573
585
|
[@pending_settings.shift, :local]
|
574
586
|
else
|
575
|
-
|
576
|
-
connection_error(check) if check
|
587
|
+
validate_settings(@remote_role, frame[:payload])
|
577
588
|
[frame[:payload], :remote]
|
578
589
|
end
|
579
590
|
|
@@ -707,7 +718,7 @@ module HTTP2Next
|
|
707
718
|
# @param priority [Integer]
|
708
719
|
# @param window [Integer]
|
709
720
|
# @param parent [Stream]
|
710
|
-
def activate_stream(id
|
721
|
+
def activate_stream(id:, **args)
|
711
722
|
connection_error(msg: "Stream ID already exists") if @streams.key?(id)
|
712
723
|
|
713
724
|
raise StreamLimitExceeded if @active_stream_count >= (@max_streams || @local_settings[:settings_max_concurrent_streams])
|
data/lib/http/2/next/emitter.rb
CHANGED
@@ -9,19 +9,18 @@ module HTTP2Next
|
|
9
9
|
#
|
10
10
|
# @param event [Symbol]
|
11
11
|
# @param block [Proc] callback function
|
12
|
-
def
|
12
|
+
def on(event, &block)
|
13
13
|
raise ArgumentError, "must provide callback" unless block_given?
|
14
14
|
|
15
15
|
listeners(event.to_sym).push block
|
16
16
|
end
|
17
|
-
alias on add_listener
|
18
17
|
|
19
18
|
# Subscribe to next event (at most once) for specified type.
|
20
19
|
#
|
21
20
|
# @param event [Symbol]
|
22
21
|
# @param block [Proc] callback function
|
23
22
|
def once(event, &block)
|
24
|
-
|
23
|
+
on(event) do |*args, &callback|
|
25
24
|
block.call(*args, &callback)
|
26
25
|
:delete
|
27
26
|
end
|
data/lib/http/2/next/error.rb
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
3
|
module HTTP2Next
|
4
|
-
module
|
4
|
+
module RegexpExtensions
|
5
5
|
unless Regexp.method_defined?(:match?)
|
6
6
|
refine Regexp do
|
7
7
|
def match?(*args)
|
@@ -10,4 +10,14 @@ module HTTP2Next
|
|
10
10
|
end
|
11
11
|
end
|
12
12
|
end
|
13
|
+
|
14
|
+
module StringExtensions
|
15
|
+
unless String.method_defined?(:unpack1)
|
16
|
+
refine String do
|
17
|
+
def unpack1(format)
|
18
|
+
unpack(format).first
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
13
23
|
end
|
data/lib/http/2/next/framer.rb
CHANGED
@@ -4,6 +4,8 @@ module HTTP2Next
|
|
4
4
|
# Performs encoding, decoding, and validation of binary HTTP/2 frames.
|
5
5
|
#
|
6
6
|
class Framer
|
7
|
+
using StringExtensions
|
8
|
+
|
7
9
|
include Error
|
8
10
|
|
9
11
|
# Default value of max frame size (16384 bytes)
|
@@ -154,6 +156,7 @@ module HTTP2Next
|
|
154
156
|
# Decodes common 9-byte header.
|
155
157
|
#
|
156
158
|
# @param buf [Buffer]
|
159
|
+
# @return [Hash] the corresponding frame
|
157
160
|
def read_common_header(buf)
|
158
161
|
frame = {}
|
159
162
|
len_hi, len_lo, type, flags, stream = buf.slice(0, 9).unpack(HEADERPACK)
|
@@ -221,7 +224,7 @@ module HTTP2Next
|
|
221
224
|
raise CompressionError, "Invalid stream ID (#{frame[:stream]})" if (frame[:stream]).nonzero?
|
222
225
|
|
223
226
|
frame[:payload].each do |(k, v)|
|
224
|
-
if k.is_a? Integer
|
227
|
+
if k.is_a? Integer # rubocop:disable Style/GuardClause
|
225
228
|
DEFINED_SETTINGS.value?(k) || next
|
226
229
|
else
|
227
230
|
k = DEFINED_SETTINGS[k]
|
@@ -333,10 +336,10 @@ module HTTP2Next
|
|
333
336
|
#
|
334
337
|
# @param buf [Buffer]
|
335
338
|
def parse(buf)
|
336
|
-
return
|
339
|
+
return if buf.size < 9
|
337
340
|
|
338
341
|
frame = read_common_header(buf)
|
339
|
-
return
|
342
|
+
return if buf.size < 9 + frame[:length]
|
340
343
|
|
341
344
|
raise ProtocolError, "payload too large" if frame[:length] > @local_max_frame_size
|
342
345
|
|
@@ -353,7 +356,7 @@ module HTTP2Next
|
|
353
356
|
if FRAME_TYPES_WITH_PADDING.include?(frame[:type])
|
354
357
|
padded = frame[:flags].include?(:padded)
|
355
358
|
if padded
|
356
|
-
padlen = payload.read(1).
|
359
|
+
padlen = payload.read(1).unpack1(UINT8)
|
357
360
|
frame[:padding] = padlen + 1
|
358
361
|
raise ProtocolError, "padding too long" if padlen > payload.bytesize
|
359
362
|
|
@@ -395,7 +398,7 @@ module HTTP2Next
|
|
395
398
|
raise ProtocolError, "Invalid stream ID (#{frame[:stream]})" if (frame[:stream]).nonzero?
|
396
399
|
|
397
400
|
(frame[:length] / 6).times do
|
398
|
-
id = payload.read(2).
|
401
|
+
id = payload.read(2).unpack1(UINT16)
|
399
402
|
val = payload.read_uint32
|
400
403
|
|
401
404
|
# Unsupported or unrecognized settings MUST be ignored.
|
@@ -437,7 +440,7 @@ module HTTP2Next
|
|
437
440
|
origins = []
|
438
441
|
|
439
442
|
until payload.empty?
|
440
|
-
len = payload.read(2).
|
443
|
+
len = payload.read(2).unpack1(UINT16)
|
441
444
|
origins << payload.read(len)
|
442
445
|
end
|
443
446
|
|
@@ -452,17 +455,16 @@ module HTTP2Next
|
|
452
455
|
|
453
456
|
def pack_error(e)
|
454
457
|
unless e.is_a? Integer
|
455
|
-
raise CompressionError, "Unknown error ID for #{e}" if DEFINED_ERRORS[e].nil?
|
456
|
-
|
457
458
|
e = DEFINED_ERRORS[e]
|
459
|
+
|
460
|
+
raise CompressionError, "Unknown error ID for #{e}" unless e
|
458
461
|
end
|
459
462
|
|
460
463
|
[e].pack(UINT32)
|
461
464
|
end
|
462
465
|
|
463
466
|
def unpack_error(error)
|
464
|
-
|
465
|
-
name || error
|
467
|
+
DEFINED_ERRORS.key(error) || error
|
466
468
|
end
|
467
469
|
end
|
468
470
|
end
|
@@ -0,0 +1,35 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module HTTP2Next
|
4
|
+
# Implementation of header compression for HTTP 2.0 (HPACK) format adapted
|
5
|
+
# to efficiently represent HTTP headers in the context of HTTP 2.0.
|
6
|
+
#
|
7
|
+
# - http://tools.ietf.org/html/draft-ietf-httpbis-header-compression-10
|
8
|
+
module Header
|
9
|
+
# Header representation as defined by the spec.
|
10
|
+
HEADREP = {
|
11
|
+
indexed: { prefix: 7, pattern: 0x80 },
|
12
|
+
incremental: { prefix: 6, pattern: 0x40 },
|
13
|
+
noindex: { prefix: 4, pattern: 0x00 },
|
14
|
+
neverindexed: { prefix: 4, pattern: 0x10 },
|
15
|
+
changetablesize: { prefix: 5, pattern: 0x20 }
|
16
|
+
}.each_value(&:freeze).freeze
|
17
|
+
|
18
|
+
# Predefined options set for Compressor
|
19
|
+
# http://mew.org/~kazu/material/2014-hpack.pdf
|
20
|
+
NAIVE = { index: :never, huffman: :never }.freeze
|
21
|
+
LINEAR = { index: :all, huffman: :never }.freeze
|
22
|
+
STATIC = { index: :static, huffman: :never }.freeze
|
23
|
+
SHORTER = { index: :all, huffman: :never }.freeze
|
24
|
+
NAIVEH = { index: :never, huffman: :always }.freeze
|
25
|
+
LINEARH = { index: :all, huffman: :always }.freeze
|
26
|
+
STATICH = { index: :static, huffman: :always }.freeze
|
27
|
+
SHORTERH = { index: :all, huffman: :shorter }.freeze
|
28
|
+
end
|
29
|
+
end
|
30
|
+
|
31
|
+
require "http/2/next/header/huffman"
|
32
|
+
require "http/2/next/header/huffman_statemachine"
|
33
|
+
require "http/2/next/header/encoding_context"
|
34
|
+
require "http/2/next/header/compressor"
|
35
|
+
require "http/2/next/header/decompressor"
|
@@ -0,0 +1,137 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module HTTP2Next
|
4
|
+
module Header
|
5
|
+
# Responsible for encoding header key-value pairs using HPACK algorithm.
|
6
|
+
class Compressor
|
7
|
+
# @param options [Hash] encoding options
|
8
|
+
def initialize(options = {})
|
9
|
+
@cc = EncodingContext.new(options)
|
10
|
+
end
|
11
|
+
|
12
|
+
# Set dynamic table size in EncodingContext
|
13
|
+
# @param size [Integer] new dynamic table size
|
14
|
+
def table_size=(size)
|
15
|
+
@cc.table_size = size
|
16
|
+
end
|
17
|
+
|
18
|
+
# Encodes provided value via integer representation.
|
19
|
+
# - http://tools.ietf.org/html/draft-ietf-httpbis-header-compression-10#section-5.1
|
20
|
+
#
|
21
|
+
# If I < 2^N - 1, encode I on N bits
|
22
|
+
# Else
|
23
|
+
# encode 2^N - 1 on N bits
|
24
|
+
# I = I - (2^N - 1)
|
25
|
+
# While I >= 128
|
26
|
+
# Encode (I % 128 + 128) on 8 bits
|
27
|
+
# I = I / 128
|
28
|
+
# encode (I) on 8 bits
|
29
|
+
#
|
30
|
+
# @param i [Integer] value to encode
|
31
|
+
# @param n [Integer] number of available bits
|
32
|
+
# @return [String] binary string
|
33
|
+
def integer(i, n)
|
34
|
+
limit = 2**n - 1
|
35
|
+
return [i].pack("C") if i < limit
|
36
|
+
|
37
|
+
bytes = []
|
38
|
+
bytes.push limit unless n.zero?
|
39
|
+
|
40
|
+
i -= limit
|
41
|
+
while i >= 128
|
42
|
+
bytes.push((i % 128) + 128)
|
43
|
+
i /= 128
|
44
|
+
end
|
45
|
+
|
46
|
+
bytes.push i
|
47
|
+
bytes.pack("C*")
|
48
|
+
end
|
49
|
+
|
50
|
+
# Encodes provided value via string literal representation.
|
51
|
+
# - http://tools.ietf.org/html/draft-ietf-httpbis-header-compression-10#section-5.2
|
52
|
+
#
|
53
|
+
# * The string length, defined as the number of bytes needed to store
|
54
|
+
# its UTF-8 representation, is represented as an integer with a seven
|
55
|
+
# bits prefix. If the string length is strictly less than 127, it is
|
56
|
+
# represented as one byte.
|
57
|
+
# * If the bit 7 of the first byte is 1, the string value is represented
|
58
|
+
# as a list of Huffman encoded octets
|
59
|
+
# (padded with bit 1's until next octet boundary).
|
60
|
+
# * If the bit 7 of the first byte is 0, the string value is
|
61
|
+
# represented as a list of UTF-8 encoded octets.
|
62
|
+
#
|
63
|
+
# +@options [:huffman]+ controls whether to use Huffman encoding:
|
64
|
+
# :never Do not use Huffman encoding
|
65
|
+
# :always Always use Huffman encoding
|
66
|
+
# :shorter Use Huffman when the result is strictly shorter
|
67
|
+
#
|
68
|
+
# @param str [String]
|
69
|
+
# @return [String] binary string
|
70
|
+
def string(str)
|
71
|
+
plain = nil
|
72
|
+
huffman = nil
|
73
|
+
plain = integer(str.bytesize, 7) << str.dup.force_encoding(Encoding::BINARY) unless @cc.options[:huffman] == :always
|
74
|
+
unless @cc.options[:huffman] == :never
|
75
|
+
huffman = Huffman.new.encode(str)
|
76
|
+
huffman = integer(huffman.bytesize, 7) << huffman
|
77
|
+
huffman.setbyte(0, huffman.ord | 0x80)
|
78
|
+
end
|
79
|
+
case @cc.options[:huffman]
|
80
|
+
when :always
|
81
|
+
huffman
|
82
|
+
when :never
|
83
|
+
plain
|
84
|
+
else
|
85
|
+
huffman.bytesize < plain.bytesize ? huffman : plain
|
86
|
+
end
|
87
|
+
end
|
88
|
+
|
89
|
+
# Encodes header command with appropriate header representation.
|
90
|
+
#
|
91
|
+
# @param h [Hash] header command
|
92
|
+
# @param buffer [String]
|
93
|
+
# @return [Buffer]
|
94
|
+
def header(h, buffer = Buffer.new)
|
95
|
+
rep = HEADREP[h[:type]]
|
96
|
+
|
97
|
+
case h[:type]
|
98
|
+
when :indexed
|
99
|
+
buffer << integer(h[:name] + 1, rep[:prefix])
|
100
|
+
when :changetablesize
|
101
|
+
buffer << integer(h[:value], rep[:prefix])
|
102
|
+
else
|
103
|
+
if h[:name].is_a? Integer
|
104
|
+
buffer << integer(h[:name] + 1, rep[:prefix])
|
105
|
+
else
|
106
|
+
buffer << integer(0, rep[:prefix])
|
107
|
+
buffer << string(h[:name])
|
108
|
+
end
|
109
|
+
|
110
|
+
buffer << string(h[:value])
|
111
|
+
end
|
112
|
+
|
113
|
+
# set header representation pattern on first byte
|
114
|
+
fb = buffer.ord | rep[:pattern]
|
115
|
+
buffer.setbyte(0, fb)
|
116
|
+
|
117
|
+
buffer
|
118
|
+
end
|
119
|
+
|
120
|
+
# Encodes provided list of HTTP headers.
|
121
|
+
#
|
122
|
+
# @param headers [Array] +[[name, value], ...]+
|
123
|
+
# @return [Buffer]
|
124
|
+
def encode(headers)
|
125
|
+
buffer = Buffer.new
|
126
|
+
pseudo_headers, regular_headers = headers.partition { |f, _| f.start_with? ":" }
|
127
|
+
headers = [*pseudo_headers, *regular_headers]
|
128
|
+
commands = @cc.encode(headers)
|
129
|
+
commands.each do |cmd|
|
130
|
+
buffer << header(cmd)
|
131
|
+
end
|
132
|
+
|
133
|
+
buffer
|
134
|
+
end
|
135
|
+
end
|
136
|
+
end
|
137
|
+
end
|