protocol-htty 0.3.0 → 0.4.0
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/htty/stream.rb +9 -53
- data/lib/protocol/htty/version.rb +1 -1
- data/readme.md +5 -0
- data/releases.md +5 -0
- data/test/protocol/htty/stream.rb +1 -32
- data.tar.gz.sig +0 -0
- metadata +1 -15
- 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: 957aa78a655ceb3136db0acd1614010a25d1cda14b6e0861e6ebfb371870b739
|
|
4
|
+
data.tar.gz: d70de3f0375aef142f90beca94c2bd50f9986bbc6bfaf2920ea64dae13185467
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: cdc24469f4d8bad151261849af0a4bd85b5ca86d2f6f007404e131cf90ab18f6fd279a82634a46565f3ffa19337c698741103c64220b7c89a8c1261d44652eea
|
|
7
|
+
data.tar.gz: 750995dc4c72f1eb9026bbba25795c8b5489d734d48e93edae8cabad4af12b4bc6a69746550e044f10ceaac2eab3f15fc6e2dddb08a919eccbc953c12c833ef3
|
checksums.yaml.gz.sig
CHANGED
|
Binary file
|
data/lib/protocol/htty/stream.rb
CHANGED
|
@@ -3,8 +3,6 @@
|
|
|
3
3
|
# Released under the MIT License.
|
|
4
4
|
# Copyright, 2026, by Samuel Williams.
|
|
5
5
|
|
|
6
|
-
require "io/stream"
|
|
7
|
-
|
|
8
6
|
module Protocol
|
|
9
7
|
module HTTY
|
|
10
8
|
# Transport an opaque byte stream after the HTTY bootstrap handshake.
|
|
@@ -15,11 +13,13 @@ module Protocol
|
|
|
15
13
|
BOOTSTRAP_PREFIX = "+H"
|
|
16
14
|
RAW_MODE = "raw"
|
|
17
15
|
|
|
18
|
-
HTTP2_FRAME_HEADER_SIZE = 9
|
|
19
|
-
|
|
20
16
|
def self.open(input, output, bootstrap: nil, mode: RAW_MODE)
|
|
21
17
|
stream = self.new(input, output)
|
|
22
18
|
|
|
19
|
+
# Disable buffering:
|
|
20
|
+
input.sync = true
|
|
21
|
+
output.sync = true
|
|
22
|
+
|
|
23
23
|
case bootstrap
|
|
24
24
|
when :write
|
|
25
25
|
stream.write_bootstrap(mode)
|
|
@@ -39,8 +39,7 @@ module Protocol
|
|
|
39
39
|
# @parameter output [IO] The writable endpoint.
|
|
40
40
|
def initialize(input, output)
|
|
41
41
|
@input = input
|
|
42
|
-
@output =
|
|
43
|
-
@frame_remaining = nil
|
|
42
|
+
@output = output
|
|
44
43
|
@local_closed = false
|
|
45
44
|
end
|
|
46
45
|
|
|
@@ -73,27 +72,9 @@ module Protocol
|
|
|
73
72
|
end
|
|
74
73
|
|
|
75
74
|
# Read application bytes from the HTTY transport.
|
|
75
|
+
# The HTTP/2 framer always requests exact byte counts (header size, then payload length), so we delegate directly to the underlying input.
|
|
76
76
|
def read(length = nil)
|
|
77
|
-
|
|
78
|
-
@frame_remaining = nil if @frame_remaining == 0
|
|
79
|
-
return +"".b
|
|
80
|
-
end
|
|
81
|
-
|
|
82
|
-
requested_length = length
|
|
83
|
-
length = [length, @frame_remaining].min if length && @frame_remaining && @frame_remaining > 0
|
|
84
|
-
buffer = read_exact(length)
|
|
85
|
-
|
|
86
|
-
if buffer && requested_length == HTTP2_FRAME_HEADER_SIZE && !@frame_remaining
|
|
87
|
-
if buffer.bytesize == HTTP2_FRAME_HEADER_SIZE
|
|
88
|
-
frame_length = self.class.frame_length(buffer)
|
|
89
|
-
@frame_remaining = frame_length if frame_length > 0
|
|
90
|
-
end
|
|
91
|
-
elsif buffer && @frame_remaining
|
|
92
|
-
@frame_remaining -= buffer.bytesize
|
|
93
|
-
@frame_remaining = nil if @frame_remaining <= 0
|
|
94
|
-
end
|
|
95
|
-
|
|
96
|
-
return buffer
|
|
77
|
+
@input.read(length)
|
|
97
78
|
end
|
|
98
79
|
|
|
99
80
|
# Write application bytes after bootstrap.
|
|
@@ -140,33 +121,8 @@ module Protocol
|
|
|
140
121
|
|
|
141
122
|
private
|
|
142
123
|
|
|
143
|
-
def self.frame_length(buffer)
|
|
144
|
-
length_high, length_low = buffer.unpack("Cn")
|
|
145
|
-
return (length_high << 16) | length_low
|
|
146
|
-
end
|
|
147
|
-
|
|
148
|
-
def read_exact(length)
|
|
149
|
-
return @input.read if length.nil?
|
|
150
|
-
|
|
151
|
-
buffer = +"".b
|
|
152
|
-
|
|
153
|
-
while buffer.bytesize < length
|
|
154
|
-
chunk = read_some(length - buffer.bytesize)
|
|
155
|
-
break unless chunk
|
|
156
|
-
|
|
157
|
-
buffer << chunk.b
|
|
158
|
-
end
|
|
159
|
-
|
|
160
|
-
return nil if buffer.empty?
|
|
161
|
-
return buffer
|
|
162
|
-
end
|
|
163
|
-
|
|
164
124
|
def read_some(length)
|
|
165
|
-
|
|
166
|
-
@input.readpartial(length)
|
|
167
|
-
else
|
|
168
|
-
@input.read(length)
|
|
169
|
-
end
|
|
125
|
+
@input.read(length)
|
|
170
126
|
rescue EOFError, Errno::EIO
|
|
171
127
|
return nil
|
|
172
128
|
end
|
|
@@ -186,7 +142,7 @@ module Protocol
|
|
|
186
142
|
end
|
|
187
143
|
|
|
188
144
|
def consume_packet
|
|
189
|
-
buffer =
|
|
145
|
+
buffer = String.new.b
|
|
190
146
|
|
|
191
147
|
while chunk = read_some(1)
|
|
192
148
|
if chunk == ESC
|
data/readme.md
CHANGED
|
@@ -36,6 +36,11 @@ Please see the [project documentation](https://socketry.github.io/protocol-htty/
|
|
|
36
36
|
|
|
37
37
|
Please see the [project releases](https://socketry.github.io/protocol-htty/releases/index) for all releases.
|
|
38
38
|
|
|
39
|
+
### v0.4.0
|
|
40
|
+
|
|
41
|
+
- **Breaking**: Drop the `io-stream` dependency. `Stream.new` and `Stream.open` no longer coerce or wrap their `input` and `output` arguments; raw IO objects are used as-is. Callers that relied on `stream.io` returning an `IO::Stream::Buffered` should wrap the IO themselves before passing it in.
|
|
42
|
+
- Simplify `Stream#read` to delegate directly to the underlying input, since the HTTP/2 framer always requests exact byte counts (header size, then payload length).
|
|
43
|
+
|
|
39
44
|
### v0.3.0
|
|
40
45
|
|
|
41
46
|
- Change `Protocol::HTTY::Stream` to take explicit input and output endpoints using `Stream.new(input, output)` and `Stream.open(input, output, **options)`.
|
data/releases.md
CHANGED
|
@@ -1,5 +1,10 @@
|
|
|
1
1
|
# Releases
|
|
2
2
|
|
|
3
|
+
## v0.4.0
|
|
4
|
+
|
|
5
|
+
- **Breaking**: Drop the `io-stream` dependency. `Stream.new` and `Stream.open` no longer coerce or wrap their `input` and `output` arguments; raw IO objects are used as-is. Callers that relied on `stream.io` returning an `IO::Stream::Buffered` should wrap the IO themselves before passing it in.
|
|
6
|
+
- Simplify `Stream#read` to delegate directly to the underlying input, since the HTTP/2 framer always requests exact byte counts (header size, then payload length).
|
|
7
|
+
|
|
3
8
|
## v0.3.0
|
|
4
9
|
|
|
5
10
|
- Change `Protocol::HTTY::Stream` to take explicit input and output endpoints using `Stream.new(input, output)` and `Stream.open(input, output, **options)`.
|
|
@@ -4,7 +4,6 @@
|
|
|
4
4
|
# Copyright, 2026, by Samuel Williams.
|
|
5
5
|
|
|
6
6
|
require "stringio"
|
|
7
|
-
require "tempfile"
|
|
8
7
|
require "protocol/http2/framer"
|
|
9
8
|
require "protocol/htty"
|
|
10
9
|
|
|
@@ -49,7 +48,7 @@ describe Protocol::HTTY::Stream do
|
|
|
49
48
|
end
|
|
50
49
|
|
|
51
50
|
it "exposes the underlying output stream" do
|
|
52
|
-
expect(stream.io).to be(:
|
|
51
|
+
expect(stream.io).to be(:equal?, writer)
|
|
53
52
|
end
|
|
54
53
|
|
|
55
54
|
it "flushes through the underlying stream" do
|
|
@@ -86,16 +85,6 @@ describe Protocol::HTTY::Stream do
|
|
|
86
85
|
expect(stream).to be(:readable?)
|
|
87
86
|
end
|
|
88
87
|
|
|
89
|
-
it "reads HTTP/2 frame headers and payloads without reading ahead" do
|
|
90
|
-
header = [0, 5, 0, 0, 1].pack("CnCCN")
|
|
91
|
-
input = StringIO.new("#{header}helloextra")
|
|
92
|
-
reader = subject.open(input, StringIO.new)
|
|
93
|
-
|
|
94
|
-
expect(reader.read(9)).to be == header
|
|
95
|
-
expect(reader.read(16)).to be == "hello"
|
|
96
|
-
expect(reader.read(5)).to be == "extra"
|
|
97
|
-
end
|
|
98
|
-
|
|
99
88
|
it "rejects writes after the local side is closed" do
|
|
100
89
|
stream.close
|
|
101
90
|
|
|
@@ -103,24 +92,4 @@ describe Protocol::HTTY::Stream do
|
|
|
103
92
|
stream.write("hello")
|
|
104
93
|
end.to raise_exception(IOError)
|
|
105
94
|
end
|
|
106
|
-
|
|
107
|
-
it "wraps raw IO handles using IO::Stream" do
|
|
108
|
-
Tempfile.create("protocol-htty") do |file|
|
|
109
|
-
io_stream = subject.open(file, file).io
|
|
110
|
-
|
|
111
|
-
expect(io_stream).to be(:is_a?, ::IO::Stream::Buffered)
|
|
112
|
-
io_stream.close
|
|
113
|
-
end
|
|
114
|
-
end
|
|
115
|
-
|
|
116
|
-
it "does not close wrapped raw IO handles when closed" do
|
|
117
|
-
Tempfile.create("protocol-htty") do |file|
|
|
118
|
-
wrapped_stream = subject.open(file, file)
|
|
119
|
-
|
|
120
|
-
wrapped_stream.close
|
|
121
|
-
|
|
122
|
-
expect(file).not.to be(:closed?)
|
|
123
|
-
file.close
|
|
124
|
-
end
|
|
125
|
-
end
|
|
126
95
|
end
|
data.tar.gz.sig
CHANGED
|
Binary file
|
metadata
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: protocol-htty
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.
|
|
4
|
+
version: 0.4.0
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Samuel Williams
|
|
@@ -52,20 +52,6 @@ dependencies:
|
|
|
52
52
|
- - ">="
|
|
53
53
|
- !ruby/object:Gem::Version
|
|
54
54
|
version: '0'
|
|
55
|
-
- !ruby/object:Gem::Dependency
|
|
56
|
-
name: io-stream
|
|
57
|
-
requirement: !ruby/object:Gem::Requirement
|
|
58
|
-
requirements:
|
|
59
|
-
- - ">="
|
|
60
|
-
- !ruby/object:Gem::Version
|
|
61
|
-
version: 0.13.0
|
|
62
|
-
type: :runtime
|
|
63
|
-
prerelease: false
|
|
64
|
-
version_requirements: !ruby/object:Gem::Requirement
|
|
65
|
-
requirements:
|
|
66
|
-
- - ">="
|
|
67
|
-
- !ruby/object:Gem::Version
|
|
68
|
-
version: 0.13.0
|
|
69
55
|
executables: []
|
|
70
56
|
extensions: []
|
|
71
57
|
extra_rdoc_files: []
|
metadata.gz.sig
CHANGED
|
Binary file
|