tp2 0.19.3 → 0.20.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
- data/.github/workflows/test.yml +1 -1
- data/CHANGELOG.md +10 -0
- data/Gemfile +6 -6
- data/examples/simple.rb +13 -3
- data/lib/tp2/connection.rb +42 -20
- data/lib/tp2/server.rb +30 -21
- data/lib/tp2/version.rb +1 -1
- data/test/test_connection.rb +4 -4
- data/test/test_server.rb +4 -6
- data/tp2.gemspec +2 -2
- metadata +6 -6
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: f50f04dad197bd317793e48f2f46e90f87c0ddf0f65c98d99c57cc8e29a24f66
|
|
4
|
+
data.tar.gz: 287838481d4b0e8150c021368cf709543331b5d4b0e87bb37b335c750c8ae267
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 1597b8856cdb6adf327652d5e161d7f8b9965358b9898ebbf2f8a5eeb5af994e3d9110b09093867ae651c9237dd4f3f31b10d7bb2a8d6e0854cb5379980d8e7d
|
|
7
|
+
data.tar.gz: 9f85ecb5c04107b8db550a7996429d39e39baab2efbcf3a355cfcb756ba6be45fb009a9da63ab2cf44fbda4bd2bec15b5187a65e867c97ac6d8b6aeeb7479832
|
data/.github/workflows/test.yml
CHANGED
data/CHANGELOG.md
CHANGED
data/Gemfile
CHANGED
|
@@ -1,12 +1,12 @@
|
|
|
1
|
-
source 'https://
|
|
1
|
+
source 'https://gem.coop'
|
|
2
2
|
|
|
3
3
|
gemspec name: 'tp2'
|
|
4
4
|
|
|
5
5
|
group :development do
|
|
6
|
-
gem 'listen', '3.
|
|
7
|
-
gem 'minitest', '
|
|
8
|
-
gem 'rake', '13.
|
|
9
|
-
gem 'rake-compiler', '1.
|
|
6
|
+
gem 'listen', '3.10.0'
|
|
7
|
+
gem 'minitest', '6.0.1'
|
|
8
|
+
gem 'rake', '13.3.1'
|
|
9
|
+
gem 'rake-compiler', '1.3.1'
|
|
10
10
|
gem 'simplecov', '0.22.0'
|
|
11
|
-
gem 'yard', '0.9.
|
|
11
|
+
gem 'yard', '0.9.38'
|
|
12
12
|
end
|
data/examples/simple.rb
CHANGED
|
@@ -8,9 +8,19 @@ end
|
|
|
8
8
|
|
|
9
9
|
require 'tp2'
|
|
10
10
|
|
|
11
|
+
BODY = 'foobar' * 1000
|
|
11
12
|
app = ->(req) {
|
|
12
|
-
req.respond(
|
|
13
|
+
req.respond(BODY)
|
|
13
14
|
}
|
|
14
15
|
|
|
15
|
-
|
|
16
|
-
|
|
16
|
+
PORT = 1234
|
|
17
|
+
|
|
18
|
+
machine = UM.new
|
|
19
|
+
opts = {
|
|
20
|
+
banner: TP2::BANNER,
|
|
21
|
+
}
|
|
22
|
+
# opts[:logger] = TP2::Logger.new(machine, **opts)
|
|
23
|
+
env = { bind: "127.0.0.1:#{PORT}" }.merge(opts)
|
|
24
|
+
puts "Listening on #{env[:bind]}"
|
|
25
|
+
server = TP2::Server.new(machine, env) { app&.call(it) }
|
|
26
|
+
server.run
|
data/lib/tp2/connection.rb
CHANGED
|
@@ -153,25 +153,50 @@ module TP2
|
|
|
153
153
|
|
|
154
154
|
SEND_FLAGS = UM::MSG_NOSIGNAL | UM::MSG_WAITALL
|
|
155
155
|
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
# @param request [Qeweney::Request] HTTP request
|
|
159
|
-
# @param body [String] response body
|
|
160
|
-
# @param headers
|
|
161
|
-
def respond(request, body, headers)
|
|
162
|
-
headers = @response_headers.merge(headers) if @response_headers
|
|
156
|
+
EMPTY_CHUNK = "0\r\n\r\n"
|
|
157
|
+
EMPTY_CHUNK_LEN = EMPTY_CHUNK.bytesize
|
|
163
158
|
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
159
|
+
CHUNKED_ENCODING_POSTLUDE = "\r\n#{EMPTY_CHUNK}"
|
|
160
|
+
|
|
161
|
+
if UM.instance_methods.include?(:sendv)
|
|
162
|
+
# Sends response including headers and body. Waits for the request to complete
|
|
163
|
+
# if not yet completed. The body is sent using chunked transfer encoding.
|
|
164
|
+
# @param request [Qeweney::Request] HTTP request
|
|
165
|
+
# @param body [String] response body
|
|
166
|
+
# @param headers
|
|
167
|
+
def respond(request, body, headers)
|
|
168
|
+
headers = @response_headers.merge(headers) if @response_headers
|
|
169
|
+
|
|
170
|
+
formatted_headers = format_headers(headers, body)
|
|
171
|
+
@response_headers = headers
|
|
172
|
+
request&.tx_incr(formatted_headers.bytesize + (body ? body.bytesize : 0))
|
|
173
|
+
if body
|
|
174
|
+
chunk_prelude = "#{body.bytesize.to_s(16)}\r\n"
|
|
175
|
+
@machine.sendv(@fd, formatted_headers, chunk_prelude, body, CHUNKED_ENCODING_POSTLUDE)
|
|
176
|
+
else
|
|
177
|
+
@machine.send(@fd, formatted_headers, formatted_headers.bytesize, SEND_FLAGS)
|
|
178
|
+
end
|
|
179
|
+
@logger&.info(request: request, response_headers: headers) if request
|
|
180
|
+
@done = true
|
|
181
|
+
end
|
|
182
|
+
else
|
|
183
|
+
def respond(request, body, headers)
|
|
184
|
+
headers = @response_headers.merge(headers) if @response_headers
|
|
185
|
+
|
|
186
|
+
formatted_headers = format_headers(headers, body)
|
|
187
|
+
@response_headers = headers
|
|
188
|
+
request&.tx_incr(formatted_headers.bytesize + (body ? body.bytesize : 0))
|
|
189
|
+
if body
|
|
190
|
+
chunk_prelude = "#{body.bytesize.to_s(16)}\r\n"
|
|
191
|
+
buf = +''
|
|
192
|
+
buf << formatted_headers << chunk_prelude << body << CHUNKED_ENCODING_POSTLUDE
|
|
193
|
+
@machine.send(@fd, buf, SEND_FLAGS)
|
|
194
|
+
else
|
|
195
|
+
@machine.send(@fd, formatted_headers, formatted_headers.bytesize, SEND_FLAGS)
|
|
196
|
+
end
|
|
197
|
+
@logger&.info(request: request, response_headers: headers) if request
|
|
198
|
+
@done = true
|
|
172
199
|
end
|
|
173
|
-
@logger&.info(request: request, response_headers: headers) if request
|
|
174
|
-
@done = true
|
|
175
200
|
end
|
|
176
201
|
|
|
177
202
|
# Sends response headers. If empty_response is truthy, the response status
|
|
@@ -187,9 +212,6 @@ module TP2
|
|
|
187
212
|
@response_headers = headers
|
|
188
213
|
end
|
|
189
214
|
|
|
190
|
-
EMPTY_CHUNK = "0\r\n\r\n"
|
|
191
|
-
EMPTY_CHUNK_LEN = EMPTY_CHUNK.bytesize
|
|
192
|
-
|
|
193
215
|
# Sends a response body chunk. If no headers were sent, default headers are
|
|
194
216
|
# sent using #send_headers. if the done option is true(thy), an empty chunk
|
|
195
217
|
# will be sent to signal response completion to the client.
|
data/lib/tp2/server.rb
CHANGED
|
@@ -50,11 +50,15 @@ module TP2
|
|
|
50
50
|
|
|
51
51
|
def run
|
|
52
52
|
setup
|
|
53
|
-
@machine.
|
|
53
|
+
@machine.await_fibers(@accept_fibers)
|
|
54
54
|
rescue UM::Terminate
|
|
55
55
|
graceful_shutdown
|
|
56
56
|
end
|
|
57
57
|
|
|
58
|
+
def stop!
|
|
59
|
+
graceful_shutdown
|
|
60
|
+
end
|
|
61
|
+
|
|
58
62
|
private
|
|
59
63
|
|
|
60
64
|
def setup
|
|
@@ -69,7 +73,7 @@ module TP2
|
|
|
69
73
|
setup_server_extensions
|
|
70
74
|
|
|
71
75
|
# map fibers
|
|
72
|
-
@
|
|
76
|
+
@connection_fibers = Set.new
|
|
73
77
|
end
|
|
74
78
|
|
|
75
79
|
def get_bind_entries
|
|
@@ -124,47 +128,52 @@ module TP2
|
|
|
124
128
|
end
|
|
125
129
|
|
|
126
130
|
def accept_incoming(listen_fd)
|
|
127
|
-
@machine.accept_each(listen_fd)
|
|
128
|
-
conn = Connection.new(self, @machine, fd, @env, &@app)
|
|
129
|
-
f = @machine.spin(conn) do
|
|
130
|
-
it.run
|
|
131
|
-
ensure
|
|
132
|
-
@connection_fiber_map.delete(f)
|
|
133
|
-
end
|
|
134
|
-
@connection_fiber_map[f] = true
|
|
135
|
-
end
|
|
131
|
+
@machine.accept_each(listen_fd) { start_client_connection(it) }
|
|
136
132
|
rescue UM::Terminate
|
|
137
133
|
# terminated
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
134
|
+
end
|
|
135
|
+
|
|
136
|
+
def start_client_connection(fd)
|
|
137
|
+
conn = Connection.new(self, @machine, fd, @env, &@app)
|
|
138
|
+
f = @machine.spin(conn) do
|
|
139
|
+
it.run
|
|
140
|
+
ensure
|
|
141
|
+
@connection_fibers.delete(f)
|
|
142
|
+
end
|
|
143
|
+
@connection_fibers << f
|
|
142
144
|
end
|
|
143
145
|
|
|
144
146
|
def close_all_server_fds
|
|
145
147
|
@server_fds.each { @machine.close_async(it) }
|
|
146
148
|
end
|
|
147
149
|
|
|
150
|
+
STOP = UM::Terminate.new
|
|
151
|
+
|
|
152
|
+
def stop_accept_fibers
|
|
153
|
+
@accept_fibers.each { @machine.schedule(it, STOP) if !it.done? }
|
|
154
|
+
@machine.await_fibers(@accept_fibers)
|
|
155
|
+
end
|
|
156
|
+
|
|
148
157
|
def graceful_shutdown
|
|
149
158
|
@env[:logger]&.info(message: 'Shutting down gracefully...')
|
|
150
159
|
|
|
151
160
|
# stop listening
|
|
152
161
|
close_all_server_fds
|
|
162
|
+
stop_accept_fibers
|
|
153
163
|
@machine.snooze
|
|
154
164
|
|
|
155
|
-
return if @
|
|
165
|
+
return if @connection_fibers.empty?
|
|
156
166
|
|
|
157
167
|
# sleep for a bit, let requests finish
|
|
158
168
|
@machine.sleep(PENDING_REQUESTS_GRACE_PERIOD)
|
|
159
|
-
return if @
|
|
169
|
+
return if @connection_fibers.empty?
|
|
160
170
|
|
|
161
171
|
# terminate pending fibers
|
|
162
|
-
pending = @
|
|
163
|
-
|
|
164
|
-
pending.each { @machine.schedule(it, signal) }
|
|
172
|
+
pending = @connection_fibers.to_a
|
|
173
|
+
pending.each { @machine.schedule(it, STOP) }
|
|
165
174
|
|
|
166
175
|
@machine.timeout(PENDING_REQUESTS_TIMEOUT_PERIOD, UM::Terminate) do
|
|
167
|
-
@machine.
|
|
176
|
+
@machine.await_fibers(@connection_fibers)
|
|
168
177
|
rescue UM::Terminate
|
|
169
178
|
# timeout on waiting for adapters to finish running, do nothing
|
|
170
179
|
end
|
data/lib/tp2/version.rb
CHANGED
data/test/test_connection.rb
CHANGED
|
@@ -491,9 +491,9 @@ class ConnectionTest < Minitest::Test
|
|
|
491
491
|
@machine.snooze
|
|
492
492
|
end
|
|
493
493
|
|
|
494
|
-
content = IO.read(
|
|
494
|
+
content = IO.read(fn)
|
|
495
495
|
file_size = content.bytesize
|
|
496
|
-
expected = "HTTP/1.1 200\r\nTransfer-Encoding: chunked\r\n\r\
|
|
496
|
+
expected = "HTTP/1.1 200\r\nTransfer-Encoding: chunked\r\n\r\n#{file_size.to_s(16)}\r\n#{content}\r\n0\r\n\r\n"
|
|
497
497
|
|
|
498
498
|
assert_equal expected, response
|
|
499
499
|
end
|
|
@@ -545,7 +545,7 @@ class ConnectionTest < Minitest::Test
|
|
|
545
545
|
assert_equal expected, response
|
|
546
546
|
end
|
|
547
547
|
|
|
548
|
-
def
|
|
548
|
+
def test_set_response_headers_1
|
|
549
549
|
@hook = ->(req) {
|
|
550
550
|
req.set_response_headers("Set-Cookie" => 'foo=bar')
|
|
551
551
|
req.respond('foo')
|
|
@@ -569,7 +569,7 @@ class ConnectionTest < Minitest::Test
|
|
|
569
569
|
assert_equal expected, response
|
|
570
570
|
end
|
|
571
571
|
|
|
572
|
-
def
|
|
572
|
+
def test_set_response_headers_2
|
|
573
573
|
@hook = ->(req) {
|
|
574
574
|
req.set_response_headers("Set-Cookie" => 'foo=bar')
|
|
575
575
|
req.set_response_headers("Foo" => 'bar')
|
data/test/test_server.rb
CHANGED
|
@@ -38,15 +38,13 @@ class ServerTest < Minitest::Test
|
|
|
38
38
|
|
|
39
39
|
def run_server
|
|
40
40
|
@server.run
|
|
41
|
-
rescue STOP
|
|
42
|
-
# ignore
|
|
43
41
|
ensure
|
|
44
42
|
@server_done = true
|
|
45
43
|
end
|
|
46
44
|
|
|
47
45
|
def teardown
|
|
48
46
|
@machine.close(@client_fd) rescue nil
|
|
49
|
-
@
|
|
47
|
+
@server.stop!
|
|
50
48
|
@machine.snooze until @server_done
|
|
51
49
|
end
|
|
52
50
|
|
|
@@ -113,7 +111,7 @@ class ServerTest < Minitest::Test
|
|
|
113
111
|
write_http_request "GET /foo HTTP/1.1\r\nServer: foo.com\r\n\r\nSCHMET /bar HTTP/1.1\r\n\r\n"
|
|
114
112
|
|
|
115
113
|
@machine.sleep(0.01)
|
|
116
|
-
@
|
|
114
|
+
@server.stop!
|
|
117
115
|
@machine.snooze
|
|
118
116
|
|
|
119
117
|
response = read_client_side
|
|
@@ -282,9 +280,9 @@ class ServerTest < Minitest::Test
|
|
|
282
280
|
@env[:logger] = TestLogger.new
|
|
283
281
|
@app = ->(req) { reqs << req; req.respond('Hello, world!', {}) }
|
|
284
282
|
|
|
285
|
-
write_http_request "GET / HTTP/1.
|
|
283
|
+
write_http_request "GET / HTTP/1.1\r\n\r\n"
|
|
286
284
|
response = read_client_side
|
|
287
|
-
expected = "HTTP/1.1 200\r\
|
|
285
|
+
expected = "HTTP/1.1 200\r\nTransfer-Encoding: chunked\r\n\r\nd\r\nHello, world!\r\n0\r\n\r\n"
|
|
288
286
|
assert_equal(expected, response)
|
|
289
287
|
|
|
290
288
|
entries = @env[:logger].entries
|
data/tp2.gemspec
CHANGED
|
@@ -20,7 +20,7 @@ Gem::Specification.new do |s|
|
|
|
20
20
|
s.required_ruby_version = '>= 3.4'
|
|
21
21
|
s.executables = ['tp2']
|
|
22
22
|
|
|
23
|
-
s.add_dependency 'uringmachine', '~> 0.
|
|
24
|
-
s.add_dependency 'qeweney', '~> 0.
|
|
23
|
+
s.add_dependency 'uringmachine', '~> 0.23.1'
|
|
24
|
+
s.add_dependency 'qeweney', '~> 0.25'
|
|
25
25
|
s.add_dependency 'rack', '~> 3.1.15'
|
|
26
26
|
end
|
metadata
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: tp2
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.
|
|
4
|
+
version: 0.20.0
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Sharon Rosner
|
|
@@ -15,28 +15,28 @@ dependencies:
|
|
|
15
15
|
requirements:
|
|
16
16
|
- - "~>"
|
|
17
17
|
- !ruby/object:Gem::Version
|
|
18
|
-
version: 0.
|
|
18
|
+
version: 0.23.1
|
|
19
19
|
type: :runtime
|
|
20
20
|
prerelease: false
|
|
21
21
|
version_requirements: !ruby/object:Gem::Requirement
|
|
22
22
|
requirements:
|
|
23
23
|
- - "~>"
|
|
24
24
|
- !ruby/object:Gem::Version
|
|
25
|
-
version: 0.
|
|
25
|
+
version: 0.23.1
|
|
26
26
|
- !ruby/object:Gem::Dependency
|
|
27
27
|
name: qeweney
|
|
28
28
|
requirement: !ruby/object:Gem::Requirement
|
|
29
29
|
requirements:
|
|
30
30
|
- - "~>"
|
|
31
31
|
- !ruby/object:Gem::Version
|
|
32
|
-
version: '0.
|
|
32
|
+
version: '0.25'
|
|
33
33
|
type: :runtime
|
|
34
34
|
prerelease: false
|
|
35
35
|
version_requirements: !ruby/object:Gem::Requirement
|
|
36
36
|
requirements:
|
|
37
37
|
- - "~>"
|
|
38
38
|
- !ruby/object:Gem::Version
|
|
39
|
-
version: '0.
|
|
39
|
+
version: '0.25'
|
|
40
40
|
- !ruby/object:Gem::Dependency
|
|
41
41
|
name: rack
|
|
42
42
|
requirement: !ruby/object:Gem::Requirement
|
|
@@ -111,7 +111,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
|
111
111
|
- !ruby/object:Gem::Version
|
|
112
112
|
version: '0'
|
|
113
113
|
requirements: []
|
|
114
|
-
rubygems_version: 4.0.
|
|
114
|
+
rubygems_version: 4.0.3
|
|
115
115
|
specification_version: 4
|
|
116
116
|
summary: Experimental HTTP/1 server for UringMachine
|
|
117
117
|
test_files: []
|