async-htty 0.1.0 → 0.2.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 +4 -4
- checksums.yaml.gz.sig +0 -0
- data/lib/async/htty/protocol/htty/server.rb +24 -0
- data/lib/async/htty/protocol/htty.rb +2 -1
- data/lib/async/htty/server.rb +28 -4
- data/lib/async/htty/version.rb +1 -1
- data/readme.md +12 -0
- data/releases.md +12 -0
- data/test/async/htty/protocol/htty.rb +148 -0
- data/test/async/htty/server.rb +107 -47
- data.tar.gz.sig +0 -0
- metadata +2 -1
- 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: f033a6514d7f18ec5f7c94b4f547ef2fa00e025eedce18d004387dde4baae5bb
|
|
4
|
+
data.tar.gz: 6d6a4cf76032a9e403c3f38f0ed4a46d1f6af61dd61ca3b712c68c93eabea6f8
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 1f027635ab2786cae7850b213eba3c37e3ac9762409d746a0d54e83d6a9366963d598843f7ccedbd729298c877181062dce7c28f20154c91a6a944cdea2430c3
|
|
7
|
+
data.tar.gz: b3bfefe4d878d7a404ffc3f3d66798f9acd5e6e17b03743849f58a281dde09a3d63c1fc85bb7918f4b8ff6a340c4335e2ddaaf1d20e882d1f270c6f953ba3f82
|
checksums.yaml.gz.sig
CHANGED
|
Binary file
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
# Released under the MIT License.
|
|
4
|
+
# Copyright, 2026, by Samuel Williams.
|
|
5
|
+
|
|
6
|
+
require "async/http/protocol/http2/server"
|
|
7
|
+
|
|
8
|
+
module Async
|
|
9
|
+
module HTTY
|
|
10
|
+
module Protocol
|
|
11
|
+
module HTTY
|
|
12
|
+
class Server < ::Async::HTTP::Protocol::HTTP2::Server
|
|
13
|
+
def receive_goaway(frame)
|
|
14
|
+
super
|
|
15
|
+
|
|
16
|
+
unless self.framer.nil?
|
|
17
|
+
self.send_goaway
|
|
18
|
+
end
|
|
19
|
+
end
|
|
20
|
+
end
|
|
21
|
+
end
|
|
22
|
+
end
|
|
23
|
+
end
|
|
24
|
+
end
|
|
@@ -6,6 +6,7 @@
|
|
|
6
6
|
require "protocol/htty"
|
|
7
7
|
|
|
8
8
|
require "async/http/protocol/http2"
|
|
9
|
+
require_relative "htty/server"
|
|
9
10
|
|
|
10
11
|
module Async
|
|
11
12
|
module HTTY
|
|
@@ -24,7 +25,7 @@ module Async
|
|
|
24
25
|
def self.server(stream, settings: ::Async::HTTP::Protocol::HTTP2::SERVER_SETTINGS)
|
|
25
26
|
stream = ::Protocol::HTTY::Stream.open(stream, bootstrap: :write)
|
|
26
27
|
|
|
27
|
-
server =
|
|
28
|
+
server = Server.new(stream)
|
|
28
29
|
server.read_connection_preface(settings)
|
|
29
30
|
server.start_connection
|
|
30
31
|
|
data/lib/async/htty/server.rb
CHANGED
|
@@ -24,9 +24,8 @@ module Async
|
|
|
24
24
|
block.call
|
|
25
25
|
end
|
|
26
26
|
end
|
|
27
|
-
|
|
28
27
|
|
|
29
|
-
def self.open(app = nil, input: $stdin, output: $stdout, env: ENV, **options, &block)
|
|
28
|
+
def self.open(app = nil, input: $stdin, output: $stdout, error: $stderr, env: ENV, **options, &block)
|
|
30
29
|
app ||= block
|
|
31
30
|
server = self.new(app, **options)
|
|
32
31
|
|
|
@@ -37,13 +36,38 @@ module Async
|
|
|
37
36
|
$stderr.puts "HTTY is not supported by this environment, visit https://htty.dev for more information."
|
|
38
37
|
raise UnsupportedError, "HTTY is not supported by this environment"
|
|
39
38
|
end
|
|
39
|
+
|
|
40
|
+
unless input.respond_to?(:tty?) && input.tty?
|
|
41
|
+
raise UnsupportedError, "HTTY requires a TTY input stream"
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
original_input = input.dup
|
|
45
|
+
original_output = output.dup
|
|
46
|
+
original_error = error.dup
|
|
47
|
+
|
|
48
|
+
stream = ::IO::Stream::Duplex(original_input, original_output)
|
|
49
|
+
input.reopen(File::NULL)
|
|
50
|
+
output.reopen(File::NULL)
|
|
51
|
+
error.reopen(File::NULL)
|
|
40
52
|
|
|
41
53
|
Sync do |task|
|
|
42
|
-
with_raw_terminal(
|
|
43
|
-
stream = ::IO::Stream::Duplex(input, output)
|
|
54
|
+
with_raw_terminal(original_input) do
|
|
44
55
|
server.accept(stream, task: task)
|
|
45
56
|
end
|
|
46
57
|
end
|
|
58
|
+
|
|
59
|
+
ensure
|
|
60
|
+
if original_input
|
|
61
|
+
input.reopen(original_input)
|
|
62
|
+
end
|
|
63
|
+
|
|
64
|
+
if original_output
|
|
65
|
+
output.reopen(original_output)
|
|
66
|
+
end
|
|
67
|
+
|
|
68
|
+
if original_error
|
|
69
|
+
error.reopen(original_error)
|
|
70
|
+
end
|
|
47
71
|
end
|
|
48
72
|
|
|
49
73
|
def initialize(app, protocol: Protocol::HTTY)
|
data/lib/async/htty/version.rb
CHANGED
data/readme.md
CHANGED
|
@@ -14,8 +14,20 @@ Please see the [project documentation](https://socketry.github.io/async-htty/) f
|
|
|
14
14
|
|
|
15
15
|
Please see the [project releases](https://socketry.github.io/async-htty/releases/index) for all releases.
|
|
16
16
|
|
|
17
|
+
### v0.2.1
|
|
18
|
+
|
|
19
|
+
- Send a server-side GOAWAY when the HTTY client closes an HTTP/2 session, allowing terminal clients to detach cleanly.
|
|
20
|
+
- Add PTY coverage for binary request/response bodies across the full byte range.
|
|
21
|
+
|
|
22
|
+
### v0.2.0
|
|
23
|
+
|
|
24
|
+
- Reopen `stdin`, `stdout`, and `stderr` to null devices to prevent output from interfering with HTTY's byte stream.
|
|
25
|
+
- Guard against non-TTY input streams, which are not supported by HTTY.
|
|
26
|
+
|
|
17
27
|
### v0.1.0
|
|
18
28
|
|
|
29
|
+
- Initial implementation.
|
|
30
|
+
|
|
19
31
|
## Contributing
|
|
20
32
|
|
|
21
33
|
We welcome contributions to this project.
|
data/releases.md
CHANGED
|
@@ -1,3 +1,15 @@
|
|
|
1
1
|
# Releases
|
|
2
2
|
|
|
3
|
+
## v0.2.1
|
|
4
|
+
|
|
5
|
+
- Send a server-side GOAWAY when the HTTY client closes an HTTP/2 session, allowing terminal clients to detach cleanly.
|
|
6
|
+
- Add PTY coverage for binary request/response bodies across the full byte range.
|
|
7
|
+
|
|
8
|
+
## v0.2.0
|
|
9
|
+
|
|
10
|
+
- Reopen `stdin`, `stdout`, and `stderr` to null devices to prevent output from interfering with HTTY's byte stream.
|
|
11
|
+
- Guard against non-TTY input streams, which are not supported by HTTY.
|
|
12
|
+
|
|
3
13
|
## v0.1.0
|
|
14
|
+
|
|
15
|
+
- Initial implementation.
|
|
@@ -4,12 +4,48 @@
|
|
|
4
4
|
# Copyright, 2026, by Samuel Williams.
|
|
5
5
|
|
|
6
6
|
require "async"
|
|
7
|
+
require "protocol/http/body/writable"
|
|
7
8
|
require "protocol/http/request"
|
|
8
9
|
require "protocol/http/response"
|
|
9
10
|
require "protocol/http2"
|
|
11
|
+
require "protocol/http2/client"
|
|
12
|
+
require "protocol/http2/stream"
|
|
13
|
+
require "rbconfig"
|
|
10
14
|
require "async/htty"
|
|
11
15
|
|
|
16
|
+
require "async/htty/pty_stream"
|
|
17
|
+
|
|
18
|
+
class EchoResponseStream < Protocol::HTTP2::Stream
|
|
19
|
+
attr :response_headers
|
|
20
|
+
attr :body
|
|
21
|
+
|
|
22
|
+
def initialize(...)
|
|
23
|
+
super
|
|
24
|
+
@response_headers = []
|
|
25
|
+
@body = +"".b
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
def process_headers(frame)
|
|
29
|
+
@response_headers = super
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
def process_data(frame)
|
|
33
|
+
data = super
|
|
34
|
+
@body << data.b if data
|
|
35
|
+
return data
|
|
36
|
+
end
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
class EchoClient < Protocol::HTTP2::Client
|
|
40
|
+
def create_stream(id = next_stream_id)
|
|
41
|
+
EchoResponseStream.create(self, id)
|
|
42
|
+
end
|
|
43
|
+
end
|
|
44
|
+
|
|
12
45
|
describe Async::HTTY::Protocol::HTTY do
|
|
46
|
+
let(:root) {File.expand_path("../../../..", __dir__)}
|
|
47
|
+
let(:ruby_load_path) {File.join(root, "lib")}
|
|
48
|
+
|
|
13
49
|
def make_pipes
|
|
14
50
|
server_input, client_output = IO.pipe
|
|
15
51
|
client_input, server_output = IO.pipe
|
|
@@ -36,6 +72,29 @@ describe Async::HTTY::Protocol::HTTY do
|
|
|
36
72
|
IO::Stream::Duplex(pipes[:server_input], pipes[:server_output])
|
|
37
73
|
end
|
|
38
74
|
|
|
75
|
+
def spawn_fixture(name)
|
|
76
|
+
environment = {
|
|
77
|
+
"HTTY" => "1",
|
|
78
|
+
"RUBYLIB" => [ruby_load_path, ENV["RUBYLIB"]].compact.join(":"),
|
|
79
|
+
}
|
|
80
|
+
executable = File.join(root, "fixtures", "async", "htty", "executables", name)
|
|
81
|
+
|
|
82
|
+
PTY.spawn(environment, RbConfig.ruby, executable)
|
|
83
|
+
end
|
|
84
|
+
|
|
85
|
+
def with_fixture(name)
|
|
86
|
+
input, output, pid = spawn_fixture(name)
|
|
87
|
+
input.binmode
|
|
88
|
+
output.binmode
|
|
89
|
+
|
|
90
|
+
stream = Async::HTTY::PTYStream.new(input, output)
|
|
91
|
+
|
|
92
|
+
yield stream
|
|
93
|
+
ensure
|
|
94
|
+
stream&.close
|
|
95
|
+
Process.wait(pid) rescue nil
|
|
96
|
+
end
|
|
97
|
+
|
|
39
98
|
it "can carry an HTTP/2 request over HTTY bootstrap and raw transport" do
|
|
40
99
|
pipes = make_pipes
|
|
41
100
|
|
|
@@ -60,6 +119,95 @@ describe Async::HTTY::Protocol::HTTY do
|
|
|
60
119
|
end
|
|
61
120
|
end
|
|
62
121
|
|
|
122
|
+
it "round trips all byte values over a real PTY" do
|
|
123
|
+
payload = (0x00..0xff).to_a.pack("C*")
|
|
124
|
+
|
|
125
|
+
with_fixture("echo_body.rb") do |stream|
|
|
126
|
+
Protocol::HTTY::Stream.new(stream).read_bootstrap
|
|
127
|
+
|
|
128
|
+
framer = Protocol::HTTP2::Framer.new(stream)
|
|
129
|
+
client = EchoClient.new(framer)
|
|
130
|
+
client.send_connection_preface
|
|
131
|
+
|
|
132
|
+
request = client.create_stream
|
|
133
|
+
request.send_headers(
|
|
134
|
+
[
|
|
135
|
+
[":method", "POST"],
|
|
136
|
+
[":path", "/echo"],
|
|
137
|
+
[":scheme", "http"],
|
|
138
|
+
[":authority", "htty.local"],
|
|
139
|
+
["content-length", payload.bytesize.to_s],
|
|
140
|
+
["content-type", "application/octet-stream"],
|
|
141
|
+
]
|
|
142
|
+
)
|
|
143
|
+
request.send_data(payload)
|
|
144
|
+
request.send_data(nil)
|
|
145
|
+
|
|
146
|
+
until request.closed?
|
|
147
|
+
client.read_frame
|
|
148
|
+
end
|
|
149
|
+
|
|
150
|
+
expect(request.response_headers.to_h[":status"]).to be == "200"
|
|
151
|
+
expect(request.body).to be == payload
|
|
152
|
+
ensure
|
|
153
|
+
client&.send_goaway
|
|
154
|
+
client&.close
|
|
155
|
+
end
|
|
156
|
+
end
|
|
157
|
+
|
|
158
|
+
it "returns from accept when the client sends GOAWAY while a response body is active" do
|
|
159
|
+
pipes = make_pipes
|
|
160
|
+
body = Protocol::HTTP::Body::Writable.new
|
|
161
|
+
|
|
162
|
+
Sync do |task|
|
|
163
|
+
request_started = Async::Notification.new
|
|
164
|
+
server_finished = Async::Notification.new
|
|
165
|
+
|
|
166
|
+
server = Async::HTTY::Server.for do |request|
|
|
167
|
+
request_started.signal
|
|
168
|
+
Protocol::HTTP::Response[200, {}, body]
|
|
169
|
+
end
|
|
170
|
+
|
|
171
|
+
server_task = task.async do
|
|
172
|
+
server.accept(server_stream(pipes))
|
|
173
|
+
ensure
|
|
174
|
+
server_finished.signal
|
|
175
|
+
end
|
|
176
|
+
|
|
177
|
+
stream = Protocol::HTTY::Stream.open(client_stream(pipes), bootstrap: :read)
|
|
178
|
+
framer = Protocol::HTTP2::Framer.new(stream)
|
|
179
|
+
client = Protocol::HTTP2::Client.new(framer)
|
|
180
|
+
client.send_connection_preface
|
|
181
|
+
|
|
182
|
+
request = client.create_stream
|
|
183
|
+
request.send_headers(
|
|
184
|
+
[[":method", "GET"], [":path", "/"], [":scheme", "http"], [":authority", "htty.local"]],
|
|
185
|
+
Protocol::HTTP2::END_STREAM
|
|
186
|
+
)
|
|
187
|
+
|
|
188
|
+
request_started.wait
|
|
189
|
+
client.send_goaway
|
|
190
|
+
|
|
191
|
+
task.with_timeout(1) do
|
|
192
|
+
server_finished.wait
|
|
193
|
+
end
|
|
194
|
+
|
|
195
|
+
frame = task.with_timeout(1) do
|
|
196
|
+
loop do
|
|
197
|
+
frame = framer.read_frame(client.local_settings.maximum_frame_size)
|
|
198
|
+
break frame if frame.is_a?(Protocol::HTTP2::GoawayFrame)
|
|
199
|
+
end
|
|
200
|
+
end
|
|
201
|
+
|
|
202
|
+
expect(frame).to be(:is_a?, Protocol::HTTP2::GoawayFrame)
|
|
203
|
+
ensure
|
|
204
|
+
body.close
|
|
205
|
+
client&.close
|
|
206
|
+
server_task&.stop
|
|
207
|
+
close_pipes(pipes)
|
|
208
|
+
end
|
|
209
|
+
end
|
|
210
|
+
|
|
63
211
|
it "ignores terminal noise before the HTTY bootstrap" do
|
|
64
212
|
pipes = make_pipes
|
|
65
213
|
|
data/test/async/htty/server.rb
CHANGED
|
@@ -3,40 +3,24 @@
|
|
|
3
3
|
# Released under the MIT License.
|
|
4
4
|
# Copyright, 2026, by Samuel Williams.
|
|
5
5
|
|
|
6
|
-
require "stringio"
|
|
7
6
|
require "protocol/http/middleware"
|
|
8
7
|
require "async/htty"
|
|
8
|
+
require "async/htty/fake_file"
|
|
9
9
|
|
|
10
10
|
describe Async::HTTY::Server do
|
|
11
11
|
let(:server) {subject.new(Protocol::HTTP::Middleware::Okay)}
|
|
12
12
|
let(:env) {{"HTTY" => "1"}}
|
|
13
|
+
let(:error) {Async::HTTY::FakeFile.new}
|
|
13
14
|
|
|
14
15
|
it "exposes the HTTY protocol by default" do
|
|
15
16
|
expect(server.protocol).to be == Async::HTTY::Protocol::HTTY
|
|
16
17
|
end
|
|
17
18
|
|
|
18
19
|
it "switches tty input into raw mode while accepting a session" do
|
|
19
|
-
input =
|
|
20
|
-
output =
|
|
20
|
+
input = Async::HTTY::FakeFile.new(tty: true)
|
|
21
|
+
output = Async::HTTY::FakeFile.new
|
|
21
22
|
connection = Object.new
|
|
22
23
|
protocol = Object.new
|
|
23
|
-
input.instance_variable_set(:@raw_called, false)
|
|
24
|
-
|
|
25
|
-
def input.tty?
|
|
26
|
-
true
|
|
27
|
-
end
|
|
28
|
-
|
|
29
|
-
def input.raw
|
|
30
|
-
@raw_called = true
|
|
31
|
-
yield
|
|
32
|
-
end
|
|
33
|
-
|
|
34
|
-
def input.raw_called?
|
|
35
|
-
@raw_called
|
|
36
|
-
end
|
|
37
|
-
|
|
38
|
-
def input.timeout
|
|
39
|
-
end
|
|
40
24
|
|
|
41
25
|
def connection.each
|
|
42
26
|
end
|
|
@@ -55,49 +39,113 @@ describe Async::HTTY::Server do
|
|
|
55
39
|
connection
|
|
56
40
|
end
|
|
57
41
|
|
|
58
|
-
subject.open(Protocol::HTTP::Middleware::Okay, input: input, output: output, env: env, protocol: protocol)
|
|
42
|
+
subject.open(Protocol::HTTP::Middleware::Okay, input: input, output: output, error: error, env: env, protocol: protocol)
|
|
59
43
|
|
|
60
|
-
expect(input
|
|
44
|
+
expect(input).to be(:raw_called?)
|
|
45
|
+
expect(input).not.to be(:raw?)
|
|
61
46
|
expect(output.string).to be == ""
|
|
62
47
|
end
|
|
63
48
|
|
|
64
49
|
it "leaves raw mode if protocol setup fails" do
|
|
65
|
-
input =
|
|
66
|
-
output =
|
|
50
|
+
input = Async::HTTY::FakeFile.new(tty: true)
|
|
51
|
+
output = Async::HTTY::FakeFile.new
|
|
67
52
|
protocol = Object.new
|
|
68
|
-
input.instance_variable_set(:@raw_exited, false)
|
|
69
53
|
|
|
70
|
-
|
|
71
|
-
|
|
54
|
+
protocol.define_singleton_method(:server) do |_stream|
|
|
55
|
+
raise EOFError, "aborted"
|
|
72
56
|
end
|
|
73
57
|
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
58
|
+
expect do
|
|
59
|
+
subject.open(Protocol::HTTP::Middleware::Okay, input: input, output: output, error: error, env: env, protocol: protocol)
|
|
60
|
+
end.to raise_exception(EOFError, message: be =~ /aborted/)
|
|
61
|
+
|
|
62
|
+
expect(input).to be(:raw_exited?)
|
|
63
|
+
expect(input).not.to be(:raw?)
|
|
64
|
+
end
|
|
65
|
+
|
|
66
|
+
it "reopens stdio streams while accepting and restores them afterwards" do
|
|
67
|
+
input = Async::HTTY::FakeFile.new("request", tty: true)
|
|
68
|
+
output = Async::HTTY::FakeFile.new
|
|
69
|
+
error = Async::HTTY::FakeFile.new("diagnostics")
|
|
70
|
+
connection = Object.new
|
|
71
|
+
protocol = Object.new
|
|
72
|
+
reopened_to_null = nil
|
|
73
|
+
duplex_input = nil
|
|
74
|
+
duplex_output = nil
|
|
75
|
+
|
|
76
|
+
def connection.each
|
|
78
77
|
end
|
|
79
78
|
|
|
80
|
-
def
|
|
81
|
-
|
|
79
|
+
def connection.closed?
|
|
80
|
+
false
|
|
82
81
|
end
|
|
83
82
|
|
|
84
|
-
def
|
|
83
|
+
def connection.send_goaway
|
|
84
|
+
end
|
|
85
|
+
|
|
86
|
+
def connection.close
|
|
85
87
|
end
|
|
86
88
|
|
|
87
89
|
protocol.define_singleton_method(:server) do |stream|
|
|
90
|
+
reopened_to_null = [input.reopened_to_null?, output.reopened_to_null?, error.reopened_to_null?]
|
|
91
|
+
duplex_input = stream.io.input
|
|
92
|
+
duplex_output = stream.io.output
|
|
93
|
+
|
|
94
|
+
stream.write("response", flush: true)
|
|
95
|
+
|
|
96
|
+
connection
|
|
97
|
+
end
|
|
98
|
+
|
|
99
|
+
subject.open(Protocol::HTTP::Middleware::Okay, input: input, output: output, error: error, env: env, protocol: protocol)
|
|
100
|
+
|
|
101
|
+
expect(reopened_to_null).to be == [true, true, true]
|
|
102
|
+
expect(duplex_input).not.to be == input
|
|
103
|
+
expect(duplex_output).not.to be == output
|
|
104
|
+
expect(duplex_input.string).to be == "request"
|
|
105
|
+
|
|
106
|
+
expect(input).not.to be(:reopened_to_null?)
|
|
107
|
+
expect(output).not.to be(:reopened_to_null?)
|
|
108
|
+
expect(error).not.to be(:reopened_to_null?)
|
|
109
|
+
|
|
110
|
+
expect(input.reopen_events).to be == [:null, :file]
|
|
111
|
+
expect(output.reopen_events).to be == [:null, :file]
|
|
112
|
+
expect(error.reopen_events).to be == [:null, :file]
|
|
113
|
+
|
|
114
|
+
expect(input.string).to be == "request"
|
|
115
|
+
expect(output.string).to be == "response"
|
|
116
|
+
expect(error.string).to be == "diagnostics"
|
|
117
|
+
end
|
|
118
|
+
|
|
119
|
+
it "restores stdio streams if protocol setup fails" do
|
|
120
|
+
input = Async::HTTY::FakeFile.new("request", tty: true)
|
|
121
|
+
output = Async::HTTY::FakeFile.new("response")
|
|
122
|
+
error = Async::HTTY::FakeFile.new("diagnostics")
|
|
123
|
+
protocol = Object.new
|
|
124
|
+
|
|
125
|
+
protocol.define_singleton_method(:server) do |_stream|
|
|
88
126
|
raise EOFError, "aborted"
|
|
89
127
|
end
|
|
90
128
|
|
|
91
129
|
expect do
|
|
92
|
-
subject.open(Protocol::HTTP::Middleware::Okay, input: input, output: output, env: env, protocol: protocol)
|
|
130
|
+
subject.open(Protocol::HTTP::Middleware::Okay, input: input, output: output, error: error, env: env, protocol: protocol)
|
|
93
131
|
end.to raise_exception(EOFError, message: be =~ /aborted/)
|
|
94
132
|
|
|
95
|
-
expect(input
|
|
133
|
+
expect(input).not.to be(:reopened_to_null?)
|
|
134
|
+
expect(output).not.to be(:reopened_to_null?)
|
|
135
|
+
expect(error).not.to be(:reopened_to_null?)
|
|
136
|
+
|
|
137
|
+
expect(input.reopen_events).to be == [:null, :file]
|
|
138
|
+
expect(output.reopen_events).to be == [:null, :file]
|
|
139
|
+
expect(error.reopen_events).to be == [:null, :file]
|
|
140
|
+
|
|
141
|
+
expect(input.string).to be == "request"
|
|
142
|
+
expect(output.string).to be == "response"
|
|
143
|
+
expect(error.string).to be == "diagnostics"
|
|
96
144
|
end
|
|
97
145
|
|
|
98
146
|
it "sends command-side GOAWAY before closing the connection" do
|
|
99
|
-
input =
|
|
100
|
-
output =
|
|
147
|
+
input = Async::HTTY::FakeFile.new(tty: true)
|
|
148
|
+
output = Async::HTTY::FakeFile.new
|
|
101
149
|
connection = Object.new
|
|
102
150
|
protocol = Object.new
|
|
103
151
|
events = []
|
|
@@ -121,14 +169,14 @@ describe Async::HTTY::Server do
|
|
|
121
169
|
connection
|
|
122
170
|
end
|
|
123
171
|
|
|
124
|
-
subject.open(Protocol::HTTP::Middleware::Okay, input: input, output: output, env: env, protocol: protocol)
|
|
172
|
+
subject.open(Protocol::HTTP::Middleware::Okay, input: input, output: output, error: error, env: env, protocol: protocol)
|
|
125
173
|
|
|
126
174
|
expect(events).to be == [:goaway, :close]
|
|
127
175
|
end
|
|
128
176
|
|
|
129
177
|
it "opens a server with default stdio-style arguments" do
|
|
130
|
-
input =
|
|
131
|
-
output =
|
|
178
|
+
input = Async::HTTY::FakeFile.new(tty: true)
|
|
179
|
+
output = Async::HTTY::FakeFile.new
|
|
132
180
|
accepted = false
|
|
133
181
|
|
|
134
182
|
server = Object.new
|
|
@@ -148,7 +196,7 @@ describe Async::HTTY::Server do
|
|
|
148
196
|
server
|
|
149
197
|
end
|
|
150
198
|
|
|
151
|
-
subject.open(Protocol::HTTP::Middleware::Okay, input: input, output: output, env: env, protocol: protocol)
|
|
199
|
+
subject.open(Protocol::HTTP::Middleware::Okay, input: input, output: output, error: error, env: env, protocol: protocol)
|
|
152
200
|
|
|
153
201
|
expect(accepted).to be == true
|
|
154
202
|
expect(output.string).to be == ""
|
|
@@ -156,11 +204,23 @@ describe Async::HTTY::Server do
|
|
|
156
204
|
|
|
157
205
|
it "raises a typed error when HTTY is disabled" do
|
|
158
206
|
expect do
|
|
159
|
-
subject.open(Protocol::HTTP::Middleware::Okay, input:
|
|
207
|
+
subject.open(Protocol::HTTP::Middleware::Okay, input: Async::HTTY::FakeFile.new, output: Async::HTTY::FakeFile.new, error: error, env: {"HTTY" => "0"})
|
|
160
208
|
end.to raise_exception(Async::HTTY::DisabledError, message: be =~ /disabled/)
|
|
161
209
|
|
|
162
210
|
expect(Async::HTTY::DisabledError).to be < Async::HTTY::UnsupportedError
|
|
163
211
|
end
|
|
212
|
+
|
|
213
|
+
it "raises a typed error when stdin is not a tty" do
|
|
214
|
+
input = Async::HTTY::FakeFile.new
|
|
215
|
+
output = Async::HTTY::FakeFile.new
|
|
216
|
+
|
|
217
|
+
expect do
|
|
218
|
+
subject.open(Protocol::HTTP::Middleware::Okay, input: input, output: output, error: error, env: env)
|
|
219
|
+
end.to raise_exception(Async::HTTY::UnsupportedError, message: be =~ /TTY input/)
|
|
220
|
+
|
|
221
|
+
expect(input.reopen_events).to be == []
|
|
222
|
+
expect(output.reopen_events).to be == []
|
|
223
|
+
end
|
|
164
224
|
|
|
165
225
|
it "prints help and raises a typed error when HTTY is not advertised" do
|
|
166
226
|
error_output = StringIO.new
|
|
@@ -170,7 +230,7 @@ describe Async::HTTY::Server do
|
|
|
170
230
|
$stderr = error_output
|
|
171
231
|
|
|
172
232
|
expect do
|
|
173
|
-
subject.open(Protocol::HTTP::Middleware::Okay, input:
|
|
233
|
+
subject.open(Protocol::HTTP::Middleware::Okay, input: Async::HTTY::FakeFile.new, output: Async::HTTY::FakeFile.new, env: {})
|
|
174
234
|
end.to raise_exception(Async::HTTY::UnsupportedError, message: be =~ /not supported/)
|
|
175
235
|
ensure
|
|
176
236
|
$stderr = original_stderr
|
|
@@ -180,8 +240,8 @@ describe Async::HTTY::Server do
|
|
|
180
240
|
end
|
|
181
241
|
|
|
182
242
|
it "opens a server within its own async context when no task is provided" do
|
|
183
|
-
input =
|
|
184
|
-
output =
|
|
243
|
+
input = Async::HTTY::FakeFile.new(tty: true)
|
|
244
|
+
output = Async::HTTY::FakeFile.new
|
|
185
245
|
accepted = false
|
|
186
246
|
|
|
187
247
|
server = Object.new
|
|
@@ -201,7 +261,7 @@ describe Async::HTTY::Server do
|
|
|
201
261
|
server
|
|
202
262
|
end
|
|
203
263
|
|
|
204
|
-
subject.open(Protocol::HTTP::Middleware::Okay, input: input, output: output, env: env, protocol: protocol)
|
|
264
|
+
subject.open(Protocol::HTTP::Middleware::Okay, input: input, output: output, error: error, env: env, protocol: protocol)
|
|
205
265
|
|
|
206
266
|
expect(accepted).to be == true
|
|
207
267
|
end
|
data.tar.gz.sig
CHANGED
|
Binary file
|
metadata
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: async-htty
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.1
|
|
4
|
+
version: 0.2.1
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Samuel Williams
|
|
@@ -105,6 +105,7 @@ files:
|
|
|
105
105
|
- lib/async/htty/error.rb
|
|
106
106
|
- lib/async/htty/protocol.rb
|
|
107
107
|
- lib/async/htty/protocol/htty.rb
|
|
108
|
+
- lib/async/htty/protocol/htty/server.rb
|
|
108
109
|
- lib/async/htty/server.rb
|
|
109
110
|
- lib/async/htty/version.rb
|
|
110
111
|
- license.md
|
metadata.gz.sig
CHANGED
|
Binary file
|