tp2 0.6 → 0.7
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/CHANGELOG.md +8 -0
- data/bin/tp2 +87 -0
- data/examples/app.rb +7 -0
- data/examples/rack.ru +3 -0
- data/examples/simple.rb +2 -20
- data/lib/tp2/http1_adapter.rb +27 -56
- data/lib/tp2/rack_adapter.rb +35 -0
- data/lib/tp2/server.rb +92 -23
- data/lib/tp2/version.rb +1 -1
- data/lib/tp2.rb +57 -0
- data/test/test_http1_adapter.rb +10 -2
- data/test/test_server.rb +14 -11
- data/tp2.gemspec +3 -1
- metadata +23 -4
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: ed2dc84d547495b29f7f569f5898e14a29ea7114683374d10150618d6fed6ba7
|
4
|
+
data.tar.gz: 6cfb58517566700905799005ff089e951f3c5f728310c2c3e5f4fdb6307c3033
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: d2785997104626457256a3dc48326b3e37783a21e2f5e8c06d20e7a2afb4443227258f50c0a33fda0e0703f280f44a95ea4b9fbc327c8d971750baef4f9af87b
|
7
|
+
data.tar.gz: 5540476e00ce0787c3f967c9a9621bcfd09e629ba0617c9853be04d523e9d530f113cab95afc60b67378017ecd9d9e62731762576eb13e7035c035d4fbb3e6ea
|
data/CHANGELOG.md
CHANGED
@@ -1,6 +1,14 @@
|
|
1
|
+
# Version 0.7 2025-06-03
|
2
|
+
|
3
|
+
- Introduce tp2 binary
|
4
|
+
- Add support for loading tp2 apps and Rack apps
|
5
|
+
- Add support for binding to multiple addresses
|
6
|
+
- Show banner when running binary
|
7
|
+
|
1
8
|
# Version 0.6 2025-05-05
|
2
9
|
|
3
10
|
- Update UringMachine to 0.10
|
11
|
+
- UseUM::Stream for HTTP parsing
|
4
12
|
|
5
13
|
# Version 0.5.2 2025-05-05
|
6
14
|
|
data/bin/tp2
ADDED
@@ -0,0 +1,87 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
# frozen_string_literal: true
|
3
|
+
|
4
|
+
require "tp2"
|
5
|
+
require "optparse"
|
6
|
+
|
7
|
+
|
8
|
+
opts = {}
|
9
|
+
|
10
|
+
parser = OptionParser.new do |o|
|
11
|
+
o.banner = "Usage: tp2 [options]"
|
12
|
+
|
13
|
+
o.on("-c", "--config CONFIG_FILE", String, "TP2 config file to use (default: app.rb)") {
|
14
|
+
opts[:app_type] = :tp2
|
15
|
+
opts[:app_location] = it
|
16
|
+
}
|
17
|
+
o.on("-r", "--rack RACK_FILE", String, "Rack config file to use (default: config.ru)") {
|
18
|
+
opts[:app_type] = :rack
|
19
|
+
opts[:app_location] = it
|
20
|
+
}
|
21
|
+
o.on("-d", "--dir PATH", String, "Static files at path") {
|
22
|
+
opts[:app_type] = :static
|
23
|
+
opts[:app_location] = it
|
24
|
+
}
|
25
|
+
|
26
|
+
o.on("-b", "--bind BIND", String, "Bind address (default: http://0.0.0.0:1234). You can specify this flag multiple times to bind to multiple addresses.") {
|
27
|
+
opts[:bind] ||= []
|
28
|
+
opts[:bind] << it
|
29
|
+
}
|
30
|
+
|
31
|
+
o.on("-h", "--help", "Show this help message") do
|
32
|
+
puts o
|
33
|
+
exit
|
34
|
+
end
|
35
|
+
|
36
|
+
o.on("-v", "--version", "Show version") do
|
37
|
+
puts "TP2 version #{TP2::VERSION}"
|
38
|
+
exit
|
39
|
+
end
|
40
|
+
end
|
41
|
+
|
42
|
+
begin
|
43
|
+
parser.parse!
|
44
|
+
rescue StandardError => e
|
45
|
+
puts e.message
|
46
|
+
puts e.backtrace.join("\n")
|
47
|
+
exit
|
48
|
+
end
|
49
|
+
|
50
|
+
opts[:location] = ARGV.shift || '.'
|
51
|
+
|
52
|
+
def check_app_file(opts, fn, type)
|
53
|
+
fn = File.join(opts[:location], fn)
|
54
|
+
return false if !File.file?(fn)
|
55
|
+
|
56
|
+
opts[:app_type] = type
|
57
|
+
opts[:app_location] = File.expand_path(fn)
|
58
|
+
end
|
59
|
+
|
60
|
+
def detect_app(opts)
|
61
|
+
if File.file?(opts[:location])
|
62
|
+
case File.extname(opts[:location])
|
63
|
+
when '.rb'
|
64
|
+
opts[:app_type] = :tp2
|
65
|
+
opts[:app_location] = File.expand_path(opts[:location])
|
66
|
+
when '.ru'
|
67
|
+
opts[:app_type] = :rack
|
68
|
+
opts[:app_location] = File.expand_path(opts[:location])
|
69
|
+
else
|
70
|
+
raise "Don't know how to serve #{opts[:location]}"
|
71
|
+
end
|
72
|
+
return
|
73
|
+
end
|
74
|
+
|
75
|
+
if File.directory?(opts[:location])
|
76
|
+
return if check_app_file(opts, 'app.rb', :tp2)
|
77
|
+
return if check_app_file(opts, 'config.ru', :rack)
|
78
|
+
end
|
79
|
+
|
80
|
+
opts[:app_type] = :static
|
81
|
+
opts[:app_location] = Dir.pwd
|
82
|
+
end
|
83
|
+
|
84
|
+
detect_app(opts) if !opts[:app_type]
|
85
|
+
|
86
|
+
puts TP2::BANNER
|
87
|
+
TP2.run(opts)
|
data/examples/app.rb
ADDED
data/examples/rack.ru
ADDED
data/examples/simple.rb
CHANGED
@@ -12,23 +12,5 @@ app = ->(req) {
|
|
12
12
|
req.respond('foobar', ':status' => Qeweney::Status::TEAPOT)
|
13
13
|
}
|
14
14
|
|
15
|
-
|
16
|
-
|
17
|
-
puts "Listening on port 1234..."
|
18
|
-
|
19
|
-
server_fiber = machine.spin { server.run }
|
20
|
-
|
21
|
-
sig_queue = UM::Queue.new
|
22
|
-
trap('SIGINT') { machine.push(sig_queue, :SIGINT) }
|
23
|
-
|
24
|
-
puts "Running... (pid: #{Process.pid})"
|
25
|
-
STDOUT.flush
|
26
|
-
|
27
|
-
# wait for signal
|
28
|
-
sig = machine.shift(sig_queue)
|
29
|
-
|
30
|
-
puts "Got signal (#{sig}), shutting down gracefully..."
|
31
|
-
machine.schedule(server_fiber, UM::Terminate.new)
|
32
|
-
machine.join(server_fiber)
|
33
|
-
|
34
|
-
puts
|
15
|
+
TP2.config(&app)
|
16
|
+
TP2.run
|
data/lib/tp2/http1_adapter.rb
CHANGED
@@ -10,8 +10,7 @@ module TP2
|
|
10
10
|
def initialize(machine, fd, &app)
|
11
11
|
@machine = machine
|
12
12
|
@fd = fd
|
13
|
-
@
|
14
|
-
@sio = StringIO.new(@buffer)
|
13
|
+
@stream = UM::Stream.new(machine, fd)
|
15
14
|
@app = app
|
16
15
|
end
|
17
16
|
|
@@ -80,10 +79,6 @@ module TP2
|
|
80
79
|
|
81
80
|
# response API
|
82
81
|
|
83
|
-
CRLF = "\r\n"
|
84
|
-
ZERO_CRLF_CRLF = "0\r\n\r\n"
|
85
|
-
CRLF_ZERO_CRLF_CRLF = "\r\n0\r\n\r\n"
|
86
|
-
|
87
82
|
SEND_FLAGS = UM::MSG_NOSIGNAL | UM::MSG_WAITALL
|
88
83
|
|
89
84
|
# Sends response including headers and body. Waits for the request to complete
|
@@ -139,13 +134,16 @@ module TP2
|
|
139
134
|
# @return [void]
|
140
135
|
def finish(_request)
|
141
136
|
# request.tx_incr(5)
|
142
|
-
@machine.send(@fd,
|
137
|
+
@machine.send(@fd, "0\r\n\r\n", "0\r\n\r\n".bytesize, SEND_FLAGS)
|
143
138
|
end
|
144
139
|
|
145
140
|
private
|
146
141
|
|
147
|
-
RE_REQUEST_LINE = /^([a-z]+)\s+([^\s]+)\s+(http\/[0-9\.]{1,3})/i
|
148
|
-
RE_HEADER_LINE = /^([a-z0-9\-]+)\:\s+(.+)/i
|
142
|
+
RE_REQUEST_LINE = /^([a-z]+)\s+([^\s]+)\s+(http\/[0-9\.]{1,3})/i.freeze
|
143
|
+
RE_HEADER_LINE = /^([a-z0-9\-]+)\:\s+(.+)/i.freeze
|
144
|
+
MAX_REQUEST_LINE_LEN = 1 << 14 # 16KB
|
145
|
+
MAX_HEADER_LINE_LEN = 1 << 10 # 1KB
|
146
|
+
MAX_CHUNK_SIZE_LEN = 16
|
149
147
|
|
150
148
|
class ProtocolError < StandardError
|
151
149
|
end
|
@@ -160,11 +158,12 @@ module TP2
|
|
160
158
|
end
|
161
159
|
|
162
160
|
def parse_headers
|
163
|
-
|
161
|
+
buf = String.new(capacity: 4096)
|
162
|
+
headers = get_request_line(buf)
|
164
163
|
return nil if !headers
|
165
164
|
|
166
165
|
while true
|
167
|
-
line = get_line
|
166
|
+
line = @stream.get_line(buf, MAX_HEADER_LINE_LEN)
|
168
167
|
break if line.nil? || line.empty?
|
169
168
|
|
170
169
|
m = line.match(RE_HEADER_LINE)
|
@@ -176,18 +175,8 @@ module TP2
|
|
176
175
|
headers
|
177
176
|
end
|
178
177
|
|
179
|
-
def
|
180
|
-
|
181
|
-
line = @sio.gets(chomp: true)
|
182
|
-
return line if line
|
183
|
-
|
184
|
-
res = @machine.read(@fd, @buffer, 65536, -1)
|
185
|
-
return nil if res == 0
|
186
|
-
end
|
187
|
-
end
|
188
|
-
|
189
|
-
def get_request_line
|
190
|
-
line = get_line
|
178
|
+
def get_request_line(buf)
|
179
|
+
line = @stream.get_line(buf, MAX_REQUEST_LINE_LEN)
|
191
180
|
return nil if !line
|
192
181
|
|
193
182
|
m = line.match(RE_REQUEST_LINE)
|
@@ -209,48 +198,30 @@ module TP2
|
|
209
198
|
end
|
210
199
|
|
211
200
|
def read(len, buf = nil, raise_on_eof = true)
|
212
|
-
|
213
|
-
if
|
214
|
-
|
215
|
-
if buf
|
216
|
-
buf << from_sio
|
217
|
-
else
|
218
|
-
buf = +from_sio
|
219
|
-
end
|
220
|
-
else
|
221
|
-
left = len
|
222
|
-
buf ||= +''
|
201
|
+
str = @stream.get_string(buf, len)
|
202
|
+
if !str && raise_on_eof
|
203
|
+
raise ProtocolError, "Missing data"
|
223
204
|
end
|
224
205
|
|
225
|
-
|
226
|
-
res = @machine.read(@fd, buf, left, -1)
|
227
|
-
if res == 0
|
228
|
-
raise ProtocolError, "Incomplete body" if raise_on_eof
|
229
|
-
|
230
|
-
return buf
|
231
|
-
end
|
232
|
-
|
233
|
-
|
234
|
-
left -= res
|
235
|
-
end
|
236
|
-
buf
|
206
|
+
str
|
237
207
|
end
|
238
208
|
|
239
|
-
def read_chunk(headers,
|
240
|
-
|
241
|
-
|
209
|
+
def read_chunk(headers, buffer)
|
210
|
+
tmp = String.new(capacity: 256)
|
211
|
+
chunk_size_str = @stream.get_line(tmp, MAX_CHUNK_SIZE_LEN)
|
212
|
+
return nil if !chunk_size_str
|
242
213
|
|
243
|
-
chunk_size =
|
214
|
+
chunk_size = chunk_size_str.to_i(16)
|
244
215
|
if chunk_size == 0
|
245
216
|
headers[':body-done-reading'] = true
|
246
|
-
get_line
|
217
|
+
@stream.get_line(tmp, 0)
|
247
218
|
return nil
|
248
219
|
end
|
249
220
|
|
250
|
-
chunk =
|
251
|
-
get_line
|
221
|
+
chunk = @stream.get_string(nil, chunk_size)
|
222
|
+
@stream.get_line(tmp, 0)
|
252
223
|
|
253
|
-
chunk
|
224
|
+
buffer ? (buffer << chunk) : chunk
|
254
225
|
end
|
255
226
|
|
256
227
|
def http1_1?(request)
|
@@ -274,7 +245,7 @@ module TP2
|
|
274
245
|
|
275
246
|
collect_header_lines(lines, k, v)
|
276
247
|
end
|
277
|
-
lines <<
|
248
|
+
lines << "\r\n"
|
278
249
|
lines
|
279
250
|
end
|
280
251
|
|
@@ -298,7 +269,7 @@ module TP2
|
|
298
269
|
if chunked
|
299
270
|
+"HTTP/1.1 #{status}\r\nTransfer-Encoding: chunked\r\n"
|
300
271
|
else
|
301
|
-
+"HTTP/1.1 #{status}\r\nContent-Length: #{body.
|
272
|
+
+"HTTP/1.1 #{status}\r\nContent-Length: #{body.bytesize}\r\n"
|
302
273
|
end
|
303
274
|
end
|
304
275
|
|
@@ -0,0 +1,35 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'rack'
|
4
|
+
|
5
|
+
module TP2
|
6
|
+
module RackAdapter
|
7
|
+
class << self
|
8
|
+
def run(&app)
|
9
|
+
->(req) { respond(req, app.(env(req))) }
|
10
|
+
end
|
11
|
+
|
12
|
+
def load(path)
|
13
|
+
src = IO.read(path)
|
14
|
+
instance_eval(src, path, 1)
|
15
|
+
end
|
16
|
+
|
17
|
+
def env(request)
|
18
|
+
Qeweney.rack_env_from_request(request)
|
19
|
+
end
|
20
|
+
|
21
|
+
def respond(request, (status_code, headers, body))
|
22
|
+
headers[':status'] = status_code.to_s
|
23
|
+
|
24
|
+
content =
|
25
|
+
if body.respond_to?(:to_path)
|
26
|
+
File.open(body.to_path, 'rb') { |f| f.read }
|
27
|
+
else
|
28
|
+
body.first
|
29
|
+
end
|
30
|
+
|
31
|
+
request.respond(content, headers)
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|
data/lib/tp2/server.rb
CHANGED
@@ -2,22 +2,57 @@
|
|
2
2
|
|
3
3
|
require 'tp2/http1_adapter'
|
4
4
|
require 'tp2/request_extensions'
|
5
|
+
require 'tp2/rack_adapter'
|
5
6
|
|
6
7
|
module TP2
|
7
8
|
class Server
|
8
9
|
PENDING_REQUESTS_GRACE_PERIOD = 0.1
|
9
10
|
PENDING_REQUESTS_TIMEOUT_PERIOD = 5
|
10
11
|
|
11
|
-
def
|
12
|
+
def self.rack_app(opts)
|
13
|
+
raise "Missing app location" if !opts[:app_location]
|
14
|
+
|
15
|
+
TP2::RackAdapter.load(opts[:app_location])
|
16
|
+
end
|
17
|
+
|
18
|
+
def self.tp2_app(opts)
|
19
|
+
if opts[:app_location]
|
20
|
+
puts "Loading app at #{opts[:app_location]}"
|
21
|
+
require opts[:app_location]
|
22
|
+
|
23
|
+
opts.merge!(TP2.config)
|
24
|
+
end
|
25
|
+
opts[:app]
|
26
|
+
end
|
27
|
+
|
28
|
+
def self.static_app(opts)
|
29
|
+
end
|
30
|
+
|
31
|
+
|
32
|
+
def initialize(machine, opts, &app)
|
12
33
|
@machine = machine
|
13
|
-
@
|
14
|
-
@
|
15
|
-
@
|
34
|
+
@opts = opts
|
35
|
+
@app = app || app_from_opts
|
36
|
+
@server_fds = []
|
37
|
+
@accept_fibers = []
|
16
38
|
end
|
17
39
|
|
40
|
+
def app_from_opts
|
41
|
+
case @opts[:app_type]
|
42
|
+
when nil, :tp2
|
43
|
+
Server.tp2_app(@opts)
|
44
|
+
when :rack
|
45
|
+
Server.rack_app(@opts)
|
46
|
+
when :static
|
47
|
+
Server.static_app(@opts)
|
48
|
+
else
|
49
|
+
raise "Invalid app type #{@opts[:app_type].inspect}"
|
50
|
+
end
|
51
|
+
end
|
52
|
+
|
18
53
|
def run
|
19
54
|
setup
|
20
|
-
|
55
|
+
@machine.join(*@accept_fibers)
|
21
56
|
rescue UM::Terminate
|
22
57
|
graceful_shutdown
|
23
58
|
end
|
@@ -25,51 +60,85 @@ module TP2
|
|
25
60
|
private
|
26
61
|
|
27
62
|
def setup
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
63
|
+
bind_info = get_bind_entries
|
64
|
+
bind_info.each do |(host, port)|
|
65
|
+
fd = setup_server_socket(host, port)
|
66
|
+
@server_fds << fd
|
67
|
+
@accept_fibers << @machine.spin { accept_incoming(fd) }
|
68
|
+
end
|
69
|
+
bind_string = bind_info.map { it.join(':') }.join(", ")
|
70
|
+
puts "Listening on #{bind_string}"
|
71
|
+
|
33
72
|
# map fibers
|
34
|
-
@
|
73
|
+
@connection_fiber_map = {}
|
74
|
+
end
|
75
|
+
|
76
|
+
def get_bind_entries
|
77
|
+
bind = @opts[:bind]
|
78
|
+
case bind
|
79
|
+
when Array
|
80
|
+
bind.map { bind_info(it) }
|
81
|
+
when String
|
82
|
+
[bind_info(bind)]
|
83
|
+
else
|
84
|
+
# default
|
85
|
+
[['0.0.0.0', 1234]]
|
86
|
+
end
|
87
|
+
end
|
35
88
|
|
36
|
-
|
89
|
+
def bind_info(bind_string)
|
90
|
+
parts = bind_string.split(':')
|
91
|
+
[parts[0], parts[1].to_i]
|
37
92
|
end
|
38
93
|
|
39
|
-
def
|
40
|
-
@machine.
|
94
|
+
def setup_server_socket(host, port)
|
95
|
+
fd = @machine.socket(UM::AF_INET, UM::SOCK_STREAM, 0, 0)
|
96
|
+
@machine.setsockopt(fd, UM::SOL_SOCKET, UM::SO_REUSEADDR, true)
|
97
|
+
@machine.setsockopt(fd, UM::SOL_SOCKET, UM::SO_REUSEPORT, true)
|
98
|
+
@machine.bind(fd, host, port)
|
99
|
+
@machine.listen(fd, UM::SOMAXCONN)
|
100
|
+
fd
|
101
|
+
end
|
102
|
+
|
103
|
+
def accept_incoming(listen_fd)
|
104
|
+
@machine.accept_each(listen_fd) do |fd|
|
41
105
|
conn = HTTP1Adapter.new(@machine, fd, &@app)
|
42
106
|
f = @machine.spin(conn) do
|
43
107
|
it.run
|
44
108
|
ensure
|
45
|
-
@
|
109
|
+
@connection_fiber_map.delete(f)
|
46
110
|
end
|
47
|
-
@
|
111
|
+
@connection_fiber_map[f] = true
|
48
112
|
end
|
49
113
|
end
|
50
114
|
|
115
|
+
def close_all_server_fds
|
116
|
+
@server_fds.each { @machine.close(it) }
|
117
|
+
end
|
118
|
+
|
51
119
|
def graceful_shutdown
|
52
120
|
# stop listening
|
53
|
-
|
121
|
+
close_all_server_fds
|
122
|
+
@machine.snooze
|
54
123
|
|
55
|
-
return if @
|
124
|
+
return if @connection_fiber_map.empty?
|
56
125
|
|
57
126
|
# sleep for a bit, let requests finish
|
58
127
|
@machine.sleep(PENDING_REQUESTS_GRACE_PERIOD)
|
59
|
-
return if @
|
128
|
+
return if @connection_fiber_map.empty?
|
60
129
|
|
61
130
|
# terminate pending fibers
|
62
|
-
pending = @
|
131
|
+
pending = @connection_fiber_map.keys
|
63
132
|
signal = UM::Terminate.new
|
64
133
|
pending.each { @machine.schedule(it, signal) }
|
65
134
|
|
66
135
|
@machine.timeout(PENDING_REQUESTS_TIMEOUT_PERIOD, UM::Terminate) do
|
67
|
-
@machine.join(*@
|
68
|
-
|
136
|
+
@machine.join(*@connection_fiber_map.keys)
|
137
|
+
rescue UM::Terminate
|
69
138
|
# timeout on waiting for adapters to finish running, do nothing
|
70
139
|
end
|
71
140
|
ensure
|
72
|
-
@machine.close(@server_fd)
|
141
|
+
@machine.close(@server_fd) rescue nil
|
73
142
|
end
|
74
143
|
end
|
75
144
|
end
|
data/lib/tp2/version.rb
CHANGED
data/lib/tp2.rb
CHANGED
@@ -1,4 +1,61 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
3
|
require 'uringmachine'
|
4
|
+
require 'tp2/version'
|
4
5
|
require 'tp2/server'
|
6
|
+
|
7
|
+
module TP2
|
8
|
+
BANNER = (
|
9
|
+
"\n" +
|
10
|
+
" ooo\n" +
|
11
|
+
" oo\n" +
|
12
|
+
" o\n" +
|
13
|
+
" \\|/ TP2 - a modern web server for Ruby apps\n" +
|
14
|
+
" / \\ \n" +
|
15
|
+
" / \\ https://github.com/noteflakes/tp2\n" +
|
16
|
+
"⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺\n"
|
17
|
+
)
|
18
|
+
|
19
|
+
|
20
|
+
class << self
|
21
|
+
def run(opts = nil, &app)
|
22
|
+
return if @in_run
|
23
|
+
|
24
|
+
opts ||= @config || {}
|
25
|
+
begin
|
26
|
+
@in_run = true
|
27
|
+
machine = UM.new
|
28
|
+
server = Server.new(machine, opts, &app)
|
29
|
+
|
30
|
+
setup_signal_handling(machine)
|
31
|
+
|
32
|
+
server.run
|
33
|
+
ensure
|
34
|
+
@in_run = false
|
35
|
+
end
|
36
|
+
end
|
37
|
+
|
38
|
+
def config(opts = nil, &app)
|
39
|
+
return @config if !opts && !app
|
40
|
+
|
41
|
+
@config = opts || {}
|
42
|
+
@config[:app] = app if app
|
43
|
+
end
|
44
|
+
|
45
|
+
private
|
46
|
+
|
47
|
+
def setup_signal_handling(machine)
|
48
|
+
queue = UM::Queue.new
|
49
|
+
trap('SIGINT') { machine.push(queue, :SIGINT) }
|
50
|
+
machine.spin { terminate_on_signal(machine, queue, Fiber.current) }
|
51
|
+
end
|
52
|
+
|
53
|
+
# waits for signal from queue, then terminates given fiber
|
54
|
+
# to be done
|
55
|
+
def terminate_on_signal(machine, queue, fiber)
|
56
|
+
sig = machine.shift(queue)
|
57
|
+
puts "\nGot signal (#{sig}), shutting down gracefully..."
|
58
|
+
machine.schedule(fiber, UM::Terminate.new)
|
59
|
+
end
|
60
|
+
end
|
61
|
+
end
|
data/test/test_http1_adapter.rb
CHANGED
@@ -69,13 +69,16 @@ class HTTP1AdapterTest < Minitest::Test
|
|
69
69
|
end
|
70
70
|
|
71
71
|
def test_pipelined_requests
|
72
|
-
|
72
|
+
msg = <<~HTTP.crlf_lines
|
73
73
|
GET /foo HTTP/1.1
|
74
74
|
Server: foo.com
|
75
75
|
|
76
76
|
SCHMET /bar HTTP/1.1
|
77
77
|
|
78
|
+
|
79
|
+
|
78
80
|
HTTP
|
81
|
+
write_http_request msg
|
79
82
|
|
80
83
|
@adapter.run
|
81
84
|
assert_equal 2, @reqs.size
|
@@ -142,7 +145,7 @@ class HTTP1AdapterTest < Minitest::Test
|
|
142
145
|
end
|
143
146
|
|
144
147
|
def test_pipelined_requests_with_body_chunked
|
145
|
-
|
148
|
+
msg = <<~HTTP.crlf_lines
|
146
149
|
POST /foo HTTP/1.1
|
147
150
|
Server: foo.com
|
148
151
|
Transfer-Encoding: chunked
|
@@ -161,7 +164,10 @@ class HTTP1AdapterTest < Minitest::Test
|
|
161
164
|
123456789abcdefghijklmnopqrstuv
|
162
165
|
0
|
163
166
|
|
167
|
+
|
168
|
+
|
164
169
|
HTTP
|
170
|
+
write_http_request(msg)
|
165
171
|
|
166
172
|
@bodies = []
|
167
173
|
@hook = ->(req) { @bodies << req.read }
|
@@ -197,6 +203,8 @@ class HTTP1AdapterTest < Minitest::Test
|
|
197
203
|
end
|
198
204
|
|
199
205
|
def test_body_to_eof
|
206
|
+
skip
|
207
|
+
|
200
208
|
write_http_request <<~HTTP.crlf_lines
|
201
209
|
POST /foo HTTP/1.1
|
202
210
|
Server: foo.com
|
data/test/test_server.rb
CHANGED
@@ -2,9 +2,6 @@
|
|
2
2
|
|
3
3
|
require_relative './helper'
|
4
4
|
|
5
|
-
class String
|
6
|
-
end
|
7
|
-
|
8
5
|
class ServerTest < Minitest::Test
|
9
6
|
def make_socket_pair
|
10
7
|
port = 10000 + rand(30000)
|
@@ -28,7 +25,7 @@ class ServerTest < Minitest::Test
|
|
28
25
|
def setup
|
29
26
|
@machine = UM.new
|
30
27
|
@port = 10000 + rand(30000)
|
31
|
-
@server = TP2::Server.new(@machine,
|
28
|
+
@server = TP2::Server.new(@machine, { bind: "127.0.0.1:#{@port}" }) { @app&.call(it) }
|
32
29
|
@f_server = @machine.spin do
|
33
30
|
@server.run
|
34
31
|
rescue STOP
|
@@ -106,9 +103,17 @@ class ServerTest < Minitest::Test
|
|
106
103
|
end
|
107
104
|
|
108
105
|
def test_pipelined_requests_with_body
|
109
|
-
skip
|
106
|
+
# skip
|
110
107
|
|
111
|
-
|
108
|
+
@bodies = []
|
109
|
+
@reqs = []
|
110
|
+
@app = ->(req) {
|
111
|
+
@reqs << req
|
112
|
+
@bodies << req.read
|
113
|
+
req.respond("method: #{req.method}")
|
114
|
+
}
|
115
|
+
|
116
|
+
msg = <<~HTTP.crlf_lines
|
112
117
|
POST /foo HTTP/1.1
|
113
118
|
Server: foo.com
|
114
119
|
Content-Length: 3
|
@@ -119,12 +124,10 @@ class ServerTest < Minitest::Test
|
|
119
124
|
|
120
125
|
defghi
|
121
126
|
HTTP
|
127
|
+
write_http_request msg
|
122
128
|
|
123
|
-
|
124
|
-
|
125
|
-
|
126
|
-
@adapter.run
|
127
|
-
assert_equal 2, @reqs.size
|
129
|
+
read_client_side
|
130
|
+
assert_equal 2, @bodies.size
|
128
131
|
|
129
132
|
req0 = @reqs.shift
|
130
133
|
headers = req0.headers
|
data/tp2.gemspec
CHANGED
@@ -18,7 +18,9 @@ Gem::Specification.new do |s|
|
|
18
18
|
s.extra_rdoc_files = ['README.md']
|
19
19
|
s.require_paths = ['lib']
|
20
20
|
s.required_ruby_version = '>= 3.4'
|
21
|
+
s.executables = ['tp2']
|
21
22
|
|
22
|
-
s.add_dependency 'uringmachine', '0.
|
23
|
+
s.add_dependency 'uringmachine', '0.12'
|
23
24
|
s.add_dependency 'qeweney', '0.21'
|
25
|
+
s.add_dependency 'rack', '3.1.15'
|
24
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.7'
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Sharon Rosner
|
@@ -15,14 +15,14 @@ dependencies:
|
|
15
15
|
requirements:
|
16
16
|
- - '='
|
17
17
|
- !ruby/object:Gem::Version
|
18
|
-
version: '0.
|
18
|
+
version: '0.12'
|
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.12'
|
26
26
|
- !ruby/object:Gem::Dependency
|
27
27
|
name: qeweney
|
28
28
|
requirement: !ruby/object:Gem::Requirement
|
@@ -37,8 +37,23 @@ dependencies:
|
|
37
37
|
- - '='
|
38
38
|
- !ruby/object:Gem::Version
|
39
39
|
version: '0.21'
|
40
|
+
- !ruby/object:Gem::Dependency
|
41
|
+
name: rack
|
42
|
+
requirement: !ruby/object:Gem::Requirement
|
43
|
+
requirements:
|
44
|
+
- - '='
|
45
|
+
- !ruby/object:Gem::Version
|
46
|
+
version: 3.1.15
|
47
|
+
type: :runtime
|
48
|
+
prerelease: false
|
49
|
+
version_requirements: !ruby/object:Gem::Requirement
|
50
|
+
requirements:
|
51
|
+
- - '='
|
52
|
+
- !ruby/object:Gem::Version
|
53
|
+
version: 3.1.15
|
40
54
|
email: sharon@noteflakes.com
|
41
|
-
executables:
|
55
|
+
executables:
|
56
|
+
- tp2
|
42
57
|
extensions: []
|
43
58
|
extra_rdoc_files:
|
44
59
|
- README.md
|
@@ -50,9 +65,13 @@ files:
|
|
50
65
|
- README.md
|
51
66
|
- Rakefile
|
52
67
|
- TODO.md
|
68
|
+
- bin/tp2
|
69
|
+
- examples/app.rb
|
70
|
+
- examples/rack.ru
|
53
71
|
- examples/simple.rb
|
54
72
|
- lib/tp2.rb
|
55
73
|
- lib/tp2/http1_adapter.rb
|
74
|
+
- lib/tp2/rack_adapter.rb
|
56
75
|
- lib/tp2/request_extensions.rb
|
57
76
|
- lib/tp2/server.rb
|
58
77
|
- lib/tp2/version.rb
|