http-protocol 0.1.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/.gitignore +11 -0
- data/.rspec +3 -0
- data/.travis.yml +23 -0
- data/Gemfile +11 -0
- data/README.md +64 -0
- data/Rakefile +6 -0
- data/http-protocol.gemspec +24 -0
- data/lib/http/protocol.rb +21 -0
- data/lib/http/protocol/error.rb +80 -0
- data/lib/http/protocol/headers.rb +203 -0
- data/lib/http/protocol/http2/client.rb +49 -0
- data/lib/http/protocol/http2/connection.rb +309 -0
- data/lib/http/protocol/http2/continuation_frame.rb +86 -0
- data/lib/http/protocol/http2/data_frame.rb +64 -0
- data/lib/http/protocol/http2/flow_control.rb +85 -0
- data/lib/http/protocol/http2/frame.rb +187 -0
- data/lib/http/protocol/http2/framer.rb +105 -0
- data/lib/http/protocol/http2/goaway_frame.rb +62 -0
- data/lib/http/protocol/http2/headers_frame.rb +95 -0
- data/lib/http/protocol/http2/padded.rb +91 -0
- data/lib/http/protocol/http2/ping_frame.rb +82 -0
- data/lib/http/protocol/http2/priority_frame.rb +82 -0
- data/lib/http/protocol/http2/push_promise_frame.rb +67 -0
- data/lib/http/protocol/http2/reset_stream_frame.rb +74 -0
- data/lib/http/protocol/http2/server.rb +47 -0
- data/lib/http/protocol/http2/settings_frame.rb +242 -0
- data/lib/http/protocol/http2/stream.rb +313 -0
- data/lib/http/protocol/http2/window_update_frame.rb +89 -0
- data/lib/http/protocol/version.rb +25 -0
- metadata +128 -0
@@ -0,0 +1,91 @@
|
|
1
|
+
# Copyright, 2018, by Samuel G. D. Williams. <http://www.codeotaku.com>
|
2
|
+
# Copyright, 2013, by Ilya Grigorik.
|
3
|
+
#
|
4
|
+
# Permission is hereby granted, free of charge, to any person obtaining a copy
|
5
|
+
# of this software and associated documentation files (the "Software"), to deal
|
6
|
+
# in the Software without restriction, including without limitation the rights
|
7
|
+
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
8
|
+
# copies of the Software, and to permit persons to whom the Software is
|
9
|
+
# furnished to do so, subject to the following conditions:
|
10
|
+
#
|
11
|
+
# The above copyright notice and this permission notice shall be included in
|
12
|
+
# all copies or substantial portions of the Software.
|
13
|
+
#
|
14
|
+
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
15
|
+
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
16
|
+
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
17
|
+
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
18
|
+
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
19
|
+
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
20
|
+
# THE SOFTWARE.
|
21
|
+
|
22
|
+
require_relative 'frame'
|
23
|
+
|
24
|
+
module HTTP
|
25
|
+
module Protocol
|
26
|
+
module HTTP2
|
27
|
+
# Certain frames can have padding:
|
28
|
+
# https://http2.github.io/http2-spec/#padding
|
29
|
+
#
|
30
|
+
# +---------------+
|
31
|
+
# |Pad Length? (8)|
|
32
|
+
# +---------------+-----------------------------------------------+
|
33
|
+
# | Data (*) ...
|
34
|
+
# +---------------------------------------------------------------+
|
35
|
+
# | Padding (*) ...
|
36
|
+
# +---------------------------------------------------------------+
|
37
|
+
#
|
38
|
+
module Padded
|
39
|
+
def padded?
|
40
|
+
flag_set?(PADDED)
|
41
|
+
end
|
42
|
+
|
43
|
+
# We will round up frames to the given length:
|
44
|
+
MODULUS = 0x0F
|
45
|
+
|
46
|
+
def pack(data, modulus: MODULUS, padding_size: nil, maximum_size: nil)
|
47
|
+
padding_size ||= (MODULUS - data.bytesize) % MODULUS
|
48
|
+
|
49
|
+
if maximum_size
|
50
|
+
maximum_padding_size = maximum_size - data.bytesize
|
51
|
+
|
52
|
+
if padding_size > maximum_padding_size
|
53
|
+
padding_size = maximum_padding_size
|
54
|
+
end
|
55
|
+
end
|
56
|
+
|
57
|
+
if padding_size > 0
|
58
|
+
set_flags(PADDED)
|
59
|
+
|
60
|
+
buffer = String.new.b
|
61
|
+
|
62
|
+
buffer << padding_size.chr
|
63
|
+
buffer << data
|
64
|
+
buffer << "\0" * padding_size
|
65
|
+
|
66
|
+
super buffer
|
67
|
+
else
|
68
|
+
clear_flags(PADDED)
|
69
|
+
|
70
|
+
super data
|
71
|
+
end
|
72
|
+
end
|
73
|
+
|
74
|
+
def unpack
|
75
|
+
if padded?
|
76
|
+
padding_size = @payload[0].ord
|
77
|
+
data_size = @payload.bytesize - 1 - padding_size
|
78
|
+
|
79
|
+
if data_size < 0
|
80
|
+
raise ProtocolError, "Invalid padding length: #{padding_size}"
|
81
|
+
end
|
82
|
+
|
83
|
+
return @payload.byteslice(1, data_size)
|
84
|
+
else
|
85
|
+
return @payload
|
86
|
+
end
|
87
|
+
end
|
88
|
+
end
|
89
|
+
end
|
90
|
+
end
|
91
|
+
end
|
@@ -0,0 +1,82 @@
|
|
1
|
+
# Copyright, 2018, by Samuel G. D. Williams. <http://www.codeotaku.com>
|
2
|
+
#
|
3
|
+
# Permission is hereby granted, free of charge, to any person obtaining a copy
|
4
|
+
# of this software and associated documentation files (the "Software"), to deal
|
5
|
+
# in the Software without restriction, including without limitation the rights
|
6
|
+
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
7
|
+
# copies of the Software, and to permit persons to whom the Software is
|
8
|
+
# furnished to do so, subject to the following conditions:
|
9
|
+
#
|
10
|
+
# The above copyright notice and this permission notice shall be included in
|
11
|
+
# all copies or substantial portions of the Software.
|
12
|
+
#
|
13
|
+
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
14
|
+
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
15
|
+
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
16
|
+
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
17
|
+
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
18
|
+
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
19
|
+
# THE SOFTWARE.
|
20
|
+
|
21
|
+
require_relative 'frame'
|
22
|
+
|
23
|
+
module HTTP
|
24
|
+
module Protocol
|
25
|
+
module HTTP2
|
26
|
+
ACKNOWLEDGEMENT = 0x1
|
27
|
+
|
28
|
+
module Acknowledgement
|
29
|
+
def acknowledgement?
|
30
|
+
flag_set?(ACKNOWLEDGEMENT)
|
31
|
+
end
|
32
|
+
|
33
|
+
def acknowledge
|
34
|
+
frame = self.class.new
|
35
|
+
|
36
|
+
frame.length = 0
|
37
|
+
frame.set_flags(ACKNOWLEDGEMENT)
|
38
|
+
|
39
|
+
return frame
|
40
|
+
end
|
41
|
+
end
|
42
|
+
|
43
|
+
# The PING frame is a mechanism for measuring a minimal round-trip time from the sender, as well as determining whether an idle connection is still functional. PING frames can be sent from any endpoint.
|
44
|
+
#
|
45
|
+
# +---------------------------------------------------------------+
|
46
|
+
# | |
|
47
|
+
# | Opaque Data (64) |
|
48
|
+
# | |
|
49
|
+
# +---------------------------------------------------------------+
|
50
|
+
#
|
51
|
+
class PingFrame < Frame
|
52
|
+
TYPE = 0x6
|
53
|
+
|
54
|
+
include Acknowledgement
|
55
|
+
|
56
|
+
def connection?
|
57
|
+
true
|
58
|
+
end
|
59
|
+
|
60
|
+
def apply(connection)
|
61
|
+
connection.receive_ping(self)
|
62
|
+
end
|
63
|
+
|
64
|
+
def acknowledge
|
65
|
+
frame = super
|
66
|
+
|
67
|
+
frame.pack self.unpack
|
68
|
+
|
69
|
+
return frame
|
70
|
+
end
|
71
|
+
|
72
|
+
def read_payload(io)
|
73
|
+
super
|
74
|
+
|
75
|
+
if @length > 8
|
76
|
+
raise FrameSizeError, "Invalid frame length"
|
77
|
+
end
|
78
|
+
end
|
79
|
+
end
|
80
|
+
end
|
81
|
+
end
|
82
|
+
end
|
@@ -0,0 +1,82 @@
|
|
1
|
+
# Copyright, 2018, by Samuel G. D. Williams. <http://www.codeotaku.com>
|
2
|
+
#
|
3
|
+
# Permission is hereby granted, free of charge, to any person obtaining a copy
|
4
|
+
# of this software and associated documentation files (the "Software"), to deal
|
5
|
+
# in the Software without restriction, including without limitation the rights
|
6
|
+
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
7
|
+
# copies of the Software, and to permit persons to whom the Software is
|
8
|
+
# furnished to do so, subject to the following conditions:
|
9
|
+
#
|
10
|
+
# The above copyright notice and this permission notice shall be included in
|
11
|
+
# all copies or substantial portions of the Software.
|
12
|
+
#
|
13
|
+
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
14
|
+
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
15
|
+
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
16
|
+
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
17
|
+
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
18
|
+
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
19
|
+
# THE SOFTWARE.
|
20
|
+
|
21
|
+
require_relative 'frame'
|
22
|
+
|
23
|
+
module HTTP
|
24
|
+
module Protocol
|
25
|
+
module HTTP2
|
26
|
+
Priority = Struct.new(:exclusive, :stream_dependency, :weight) do
|
27
|
+
FORMAT = "NC".freeze
|
28
|
+
EXCLUSIVE = 1 << 31
|
29
|
+
|
30
|
+
def self.unpack(data)
|
31
|
+
stream_dependency, weight = data.unpack(FORMAT)
|
32
|
+
|
33
|
+
return self.new(stream_dependency & EXCLUSIVE != 0, stream_dependency & ~EXCLUSIVE, weight)
|
34
|
+
end
|
35
|
+
|
36
|
+
def pack
|
37
|
+
if exclusive
|
38
|
+
stream_dependency = self.stream_dependency | EXCLUSIVE
|
39
|
+
end
|
40
|
+
|
41
|
+
return [stream_dependency, self.weight].pack(FORMAT)
|
42
|
+
end
|
43
|
+
end
|
44
|
+
|
45
|
+
# The PRIORITY frame specifies the sender-advised priority of a stream. It can be sent in any stream state, including idle or closed streams.
|
46
|
+
#
|
47
|
+
# +-+-------------------------------------------------------------+
|
48
|
+
# |E| Stream Dependency (31) |
|
49
|
+
# +-+-------------+-----------------------------------------------+
|
50
|
+
# | Weight (8) |
|
51
|
+
# +-+-------------+
|
52
|
+
#
|
53
|
+
class PriorityFrame < Frame
|
54
|
+
TYPE = 0x2
|
55
|
+
|
56
|
+
def priority
|
57
|
+
Priority.unpack(@payload)
|
58
|
+
end
|
59
|
+
|
60
|
+
def pack priority
|
61
|
+
super priority.pack
|
62
|
+
end
|
63
|
+
|
64
|
+
def unpack
|
65
|
+
Priority.unpack(super)
|
66
|
+
end
|
67
|
+
|
68
|
+
def apply(connection)
|
69
|
+
connection.receive_priority(self)
|
70
|
+
end
|
71
|
+
|
72
|
+
def read_payload(io)
|
73
|
+
super
|
74
|
+
|
75
|
+
if @length != 5
|
76
|
+
raise FrameSizeError, "Invalid frame length"
|
77
|
+
end
|
78
|
+
end
|
79
|
+
end
|
80
|
+
end
|
81
|
+
end
|
82
|
+
end
|
@@ -0,0 +1,67 @@
|
|
1
|
+
# Copyright, 2018, by Samuel G. D. Williams. <http://www.codeotaku.com>
|
2
|
+
#
|
3
|
+
# Permission is hereby granted, free of charge, to any person obtaining a copy
|
4
|
+
# of this software and associated documentation files (the "Software"), to deal
|
5
|
+
# in the Software without restriction, including without limitation the rights
|
6
|
+
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
7
|
+
# copies of the Software, and to permit persons to whom the Software is
|
8
|
+
# furnished to do so, subject to the following conditions:
|
9
|
+
#
|
10
|
+
# The above copyright notice and this permission notice shall be included in
|
11
|
+
# all copies or substantial portions of the Software.
|
12
|
+
#
|
13
|
+
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
14
|
+
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
15
|
+
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
16
|
+
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
17
|
+
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
18
|
+
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
19
|
+
# THE SOFTWARE.
|
20
|
+
|
21
|
+
require_relative 'frame'
|
22
|
+
require_relative 'padded'
|
23
|
+
|
24
|
+
module HTTP
|
25
|
+
module Protocol
|
26
|
+
module HTTP2
|
27
|
+
# The PUSH_PROMISE frame is used to notify the peer endpoint in advance of streams the sender intends to initiate. The PUSH_PROMISE frame includes the unsigned 31-bit identifier of the stream the endpoint plans to create along with a set of headers that provide additional context for the stream.
|
28
|
+
#
|
29
|
+
# +---------------+
|
30
|
+
# |Pad Length? (8)|
|
31
|
+
# +-+-------------+-----------------------------------------------+
|
32
|
+
# |R| Promised Stream ID (31) |
|
33
|
+
# +-+-----------------------------+-------------------------------+
|
34
|
+
# | Header Block Fragment (*) ...
|
35
|
+
# +---------------------------------------------------------------+
|
36
|
+
# | Padding (*) ...
|
37
|
+
# +---------------------------------------------------------------+
|
38
|
+
#
|
39
|
+
class PushPromiseFrame < Frame
|
40
|
+
include Continued, Padded
|
41
|
+
|
42
|
+
TYPE = 0x5
|
43
|
+
FORMAT = "N".freeze
|
44
|
+
|
45
|
+
def end_headers?
|
46
|
+
flag_set?(END_HEADERS)
|
47
|
+
end
|
48
|
+
|
49
|
+
def unpack
|
50
|
+
data = super
|
51
|
+
|
52
|
+
stream_id = data.unpack(FORMAT).first
|
53
|
+
|
54
|
+
return stream_id, data.byteslice(4, data.bytesize - 4)
|
55
|
+
end
|
56
|
+
|
57
|
+
def pack(stream_id, data, *args)
|
58
|
+
super([stream_id].pack(FORMAT) + data, *args)
|
59
|
+
end
|
60
|
+
|
61
|
+
def apply(connection)
|
62
|
+
connection.receive_push_promise(self)
|
63
|
+
end
|
64
|
+
end
|
65
|
+
end
|
66
|
+
end
|
67
|
+
end
|
@@ -0,0 +1,74 @@
|
|
1
|
+
# Copyright, 2018, by Samuel G. D. Williams. <http://www.codeotaku.com>
|
2
|
+
#
|
3
|
+
# Permission is hereby granted, free of charge, to any person obtaining a copy
|
4
|
+
# of this software and associated documentation files (the "Software"), to deal
|
5
|
+
# in the Software without restriction, including without limitation the rights
|
6
|
+
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
7
|
+
# copies of the Software, and to permit persons to whom the Software is
|
8
|
+
# furnished to do so, subject to the following conditions:
|
9
|
+
#
|
10
|
+
# The above copyright notice and this permission notice shall be included in
|
11
|
+
# all copies or substantial portions of the Software.
|
12
|
+
#
|
13
|
+
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
14
|
+
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
15
|
+
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
16
|
+
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
17
|
+
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
18
|
+
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
19
|
+
# THE SOFTWARE.
|
20
|
+
|
21
|
+
require_relative 'frame'
|
22
|
+
|
23
|
+
module HTTP
|
24
|
+
module Protocol
|
25
|
+
module HTTP2
|
26
|
+
NO_ERROR = 0
|
27
|
+
PROTOCOL_ERROR = 1
|
28
|
+
INTERNAL_ERROR = 2
|
29
|
+
FLOW_CONTROL_ERROR = 3
|
30
|
+
TIMEOUT = 4
|
31
|
+
STREAM_CLOSED = 5
|
32
|
+
FRAME_SIZE_ERROR = 6
|
33
|
+
REFUSED_STREAM = 7
|
34
|
+
CANCEL = 8
|
35
|
+
COMPRESSION_ERROR = 9
|
36
|
+
CONNECT_ERROR = 10
|
37
|
+
ENHANCE_YOUR_CALM = 11
|
38
|
+
INADEQUATE_SECURITY = 12
|
39
|
+
HTTP_1_1_REQUIRED = 13
|
40
|
+
|
41
|
+
# The RST_STREAM frame allows for immediate termination of a stream. RST_STREAM is sent to request cancellation of a stream or to indicate that an error condition has occurred.
|
42
|
+
#
|
43
|
+
# +---------------------------------------------------------------+
|
44
|
+
# | Error Code (32) |
|
45
|
+
# +---------------------------------------------------------------+
|
46
|
+
#
|
47
|
+
class ResetStreamFrame < Frame
|
48
|
+
TYPE = 0x3
|
49
|
+
FORMAT = "N".freeze
|
50
|
+
|
51
|
+
def unpack
|
52
|
+
@payload.unpack(FORMAT).first
|
53
|
+
end
|
54
|
+
|
55
|
+
def pack(error_code = NO_ERROR)
|
56
|
+
@payload = [error_code].pack(FORMAT)
|
57
|
+
@length = @payload.bytesize
|
58
|
+
end
|
59
|
+
|
60
|
+
def apply(connection)
|
61
|
+
connection.receive_reset_stream(self)
|
62
|
+
end
|
63
|
+
|
64
|
+
def read_payload(io)
|
65
|
+
super
|
66
|
+
|
67
|
+
if @length != 4
|
68
|
+
raise FrameSizeError, "Invalid frame length"
|
69
|
+
end
|
70
|
+
end
|
71
|
+
end
|
72
|
+
end
|
73
|
+
end
|
74
|
+
end
|
@@ -0,0 +1,47 @@
|
|
1
|
+
# Copyright, 2018, by Samuel G. D. Williams. <http://www.codeotaku.com>
|
2
|
+
#
|
3
|
+
# Permission is hereby granted, free of charge, to any person obtaining a copy
|
4
|
+
# of this software and associated documentation files (the "Software"), to deal
|
5
|
+
# in the Software without restriction, including without limitation the rights
|
6
|
+
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
7
|
+
# copies of the Software, and to permit persons to whom the Software is
|
8
|
+
# furnished to do so, subject to the following conditions:
|
9
|
+
#
|
10
|
+
# The above copyright notice and this permission notice shall be included in
|
11
|
+
# all copies or substantial portions of the Software.
|
12
|
+
#
|
13
|
+
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
14
|
+
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
15
|
+
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
16
|
+
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
17
|
+
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
18
|
+
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
19
|
+
# THE SOFTWARE.
|
20
|
+
|
21
|
+
require_relative 'connection'
|
22
|
+
|
23
|
+
module HTTP
|
24
|
+
module Protocol
|
25
|
+
module HTTP2
|
26
|
+
class Server < Connection
|
27
|
+
def initialize(framer, *args)
|
28
|
+
super(framer, 2, *args)
|
29
|
+
end
|
30
|
+
|
31
|
+
def read_connection_preface(settings)
|
32
|
+
if @state == :new
|
33
|
+
@framer.read_connection_preface
|
34
|
+
|
35
|
+
send_settings(settings)
|
36
|
+
|
37
|
+
read_frame do |frame|
|
38
|
+
raise ProtocolError, "First frame must be SettingsFrame, but got #{frame.class}" unless frame.is_a? SettingsFrame
|
39
|
+
end
|
40
|
+
else
|
41
|
+
raise ProtocolError, "Cannot send connection preface in state #{@state}"
|
42
|
+
end
|
43
|
+
end
|
44
|
+
end
|
45
|
+
end
|
46
|
+
end
|
47
|
+
end
|