tp2 0.7.1 → 0.8.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
- data/CHANGELOG.md +6 -0
- data/bin/tp2 +9 -3
- data/examples/app.rb +3 -1
- data/lib/tp2/http1_adapter.rb +47 -14
- data/lib/tp2/server.rb +9 -3
- data/lib/tp2/version.rb +1 -1
- data/lib/tp2.rb +13 -8
- data/test/test_http1_adapter.rb +1 -1
- data/test/test_server.rb +4 -2
- data/tp2.gemspec +1 -1
- metadata +3 -3
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 75f66b5020339be69ee5ee0122eea1673a4f96330cedfec95cde805ef43b9221
|
4
|
+
data.tar.gz: efc072f110cacc703c6ae75f4bade2a0753c73ecffae0f3055f6346eb165c79d
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 5d522b6958b99135fe730b514d30ec8be9e18e01cabce1892e188ca2c255c5f963c4fc6ecf6c6b6f82b0c7ef9fc10c56dd2020a57bfbf9ea7abf27b3a3c88d2b
|
7
|
+
data.tar.gz: 64a166087f1bae5a36f648ae2068ffa731fc4ecba6e77c400a311fc8be82eea795cf25b640f94f91731d7d3d7484645837b462a0058159a3eb68f2787e54e8d5
|
data/CHANGELOG.md
CHANGED
data/bin/tp2
CHANGED
@@ -4,8 +4,10 @@
|
|
4
4
|
require "tp2"
|
5
5
|
require "optparse"
|
6
6
|
|
7
|
-
|
8
|
-
|
7
|
+
opts = {
|
8
|
+
banner: true,
|
9
|
+
log: true
|
10
|
+
}
|
9
11
|
|
10
12
|
parser = OptionParser.new do |o|
|
11
13
|
o.banner = "Usage: tp2 [options]"
|
@@ -28,6 +30,11 @@ parser = OptionParser.new do |o|
|
|
28
30
|
opts[:bind] << it
|
29
31
|
}
|
30
32
|
|
33
|
+
o.on("-s", "--silent", "Silent mode") {
|
34
|
+
opts[:banner] = false
|
35
|
+
opts[:log] = false
|
36
|
+
}
|
37
|
+
|
31
38
|
o.on("-h", "--help", "Show this help message") do
|
32
39
|
puts o
|
33
40
|
exit
|
@@ -83,5 +90,4 @@ end
|
|
83
90
|
|
84
91
|
detect_app(opts) if !opts[:app_type]
|
85
92
|
|
86
|
-
puts TP2::BANNER
|
87
93
|
TP2.run(opts)
|
data/examples/app.rb
CHANGED
data/lib/tp2/http1_adapter.rb
CHANGED
@@ -7,21 +7,27 @@ module TP2
|
|
7
7
|
class HTTP1Adapter
|
8
8
|
attr_reader :fd
|
9
9
|
|
10
|
-
def initialize(machine, fd, &app)
|
10
|
+
def initialize(machine, fd, opts, &app)
|
11
11
|
@machine = machine
|
12
12
|
@fd = fd
|
13
|
+
@opts = opts
|
13
14
|
@stream = UM::Stream.new(machine, fd)
|
14
15
|
@app = app
|
16
|
+
|
17
|
+
@done = nil
|
18
|
+
@response_headers = nil
|
15
19
|
end
|
16
20
|
|
17
21
|
def run
|
18
22
|
while true
|
23
|
+
@done = nil
|
24
|
+
@response_headers = nil
|
19
25
|
persist = serve_request
|
20
26
|
break if !persist
|
21
27
|
end
|
22
28
|
rescue UM::Terminate
|
23
|
-
#
|
24
|
-
rescue => e
|
29
|
+
# terminated
|
30
|
+
rescue Exception => e
|
25
31
|
puts '!' * 40
|
26
32
|
p e
|
27
33
|
puts e.backtrace.join("\n")
|
@@ -86,9 +92,9 @@ module TP2
|
|
86
92
|
# @param request [Qeweney::Request] HTTP request
|
87
93
|
# @param body [String] response body
|
88
94
|
# @param headers
|
89
|
-
def respond(
|
95
|
+
def respond(request, body, headers)
|
90
96
|
formatted_headers = format_headers(headers, body, false)
|
91
|
-
|
97
|
+
request.tx_incr(formatted_headers.bytesize + (body ? body.bytesize : 0))
|
92
98
|
if body
|
93
99
|
buf = formatted_headers + body
|
94
100
|
@machine.send(@fd, buf, buf.bytesize, SEND_FLAGS)
|
@@ -96,6 +102,9 @@ module TP2
|
|
96
102
|
else
|
97
103
|
@machine.send(@fd, formatted_headers, formatted_headers.bytesize, SEND_FLAGS)
|
98
104
|
end
|
105
|
+
log(request, headers) if @opts[:log]
|
106
|
+
@done = true
|
107
|
+
@response_headers = headers
|
99
108
|
end
|
100
109
|
|
101
110
|
# Sends response headers. If empty_response is truthy, the response status
|
@@ -107,9 +116,10 @@ module TP2
|
|
107
116
|
# @return [void]
|
108
117
|
def send_headers(request, headers, empty_response: false, chunked: true)
|
109
118
|
formatted_headers = format_headers(headers, !empty_response, http1_1?(request) && chunked)
|
110
|
-
|
119
|
+
request.tx_incr(formatted_headers.bytesize)
|
111
120
|
#
|
112
121
|
@machine.send(@fd, formatted_headers, formatted_headers.bytesize, SEND_FLAGS)
|
122
|
+
@response_headers = headers
|
113
123
|
end
|
114
124
|
|
115
125
|
# Sends a response body chunk. If no headers were sent, default headers are
|
@@ -119,26 +129,50 @@ module TP2
|
|
119
129
|
# @param chunk [String] response body chunk
|
120
130
|
# @param done [boolean] whether the response is completed
|
121
131
|
# @return [void]
|
122
|
-
def send_chunk(
|
132
|
+
def send_chunk(request, chunk, done: false)
|
123
133
|
data = +''
|
124
134
|
data << "#{chunk.bytesize.to_s(16)}\r\n#{chunk}\r\n" if chunk
|
125
135
|
data << "0\r\n\r\n" if done
|
126
136
|
return if data.empty?
|
127
137
|
|
128
|
-
|
138
|
+
request.tx_incr(data.bytesize)
|
129
139
|
@machine.send(@fd, data, data.bytesize, SEND_FLAGS)
|
140
|
+
if done && !@done
|
141
|
+
log(request, @response_headers) if @opts[:log]
|
142
|
+
@done = true
|
143
|
+
end
|
130
144
|
end
|
131
145
|
|
146
|
+
EMPTY_CHUNK = "0\r\n\r\n"
|
147
|
+
EMPTY_CHUNK_LEN = EMPTY_CHUNK.bytesize
|
148
|
+
|
132
149
|
# Finishes the response to the current request. If no headers were sent,
|
133
150
|
# default headers are sent using #send_headers.
|
134
151
|
# @return [void]
|
135
|
-
def finish(
|
136
|
-
|
137
|
-
@machine.send(@fd,
|
152
|
+
def finish(request)
|
153
|
+
request.tx_incr(EMPTY_CHUNK_LEN)
|
154
|
+
@machine.send(@fd, EMPTY_CHUNK, EMPTY_CHUNK_LEN, SEND_FLAGS)
|
155
|
+
if !@done
|
156
|
+
log(request, @response_headers) if @opts[:log]
|
157
|
+
@done = true
|
158
|
+
end
|
138
159
|
end
|
139
160
|
|
140
161
|
private
|
141
162
|
|
163
|
+
def log(request, response_headers)
|
164
|
+
request_headers = request.headers
|
165
|
+
str = format(
|
166
|
+
"%<stamp>s %<method>s %<path>s %<status>s %<tx>d\n",
|
167
|
+
stamp: Time.now.strftime('%Y-%m-%d %H:%M:%S.%3N'),
|
168
|
+
method: request_headers[':method'].upcase,
|
169
|
+
path: request_headers[':path'],
|
170
|
+
status: @status,
|
171
|
+
tx: request_headers[':tx']
|
172
|
+
)
|
173
|
+
@machine.write_async(1, str)
|
174
|
+
end
|
175
|
+
|
142
176
|
RE_REQUEST_LINE = /^([a-z]+)\s+([^\s]+)\s+(http\/[0-9\.]{1,3})/i.freeze
|
143
177
|
RE_HEADER_LINE = /^([a-z0-9\-]+)\:\s+(.+)/i.freeze
|
144
178
|
MAX_REQUEST_LINE_LEN = 1 << 14 # 16KB
|
@@ -237,9 +271,8 @@ module TP2
|
|
237
271
|
# @param chunked [boolean] whether to use chunked transfer encoding
|
238
272
|
# @return [String] formatted response headers
|
239
273
|
def format_headers(headers, body, chunked)
|
240
|
-
status = headers[':status']
|
241
|
-
|
242
|
-
lines = format_status_line(body, status, chunked)
|
274
|
+
@status = headers[':status'] || (body ? Qeweney::Status::OK : Qeweney::Status::NO_CONTENT)
|
275
|
+
lines = format_status_line(body, @status, chunked)
|
243
276
|
headers.each do |k, v|
|
244
277
|
next if k =~ INTERNAL_HEADER_REGEXP
|
245
278
|
|
data/lib/tp2/server.rb
CHANGED
@@ -17,7 +17,7 @@ module TP2
|
|
17
17
|
|
18
18
|
def self.tp2_app(opts)
|
19
19
|
if opts[:app_location]
|
20
|
-
puts "Loading app at #{opts[:app_location]}"
|
20
|
+
puts "Loading app at #{opts[:app_location]}" if opts[:log]
|
21
21
|
require opts[:app_location]
|
22
22
|
|
23
23
|
opts.merge!(TP2.config)
|
@@ -67,7 +67,7 @@ module TP2
|
|
67
67
|
@accept_fibers << @machine.spin { accept_incoming(fd) }
|
68
68
|
end
|
69
69
|
bind_string = bind_info.map { it.join(':') }.join(", ")
|
70
|
-
puts
|
70
|
+
@machine.puts("Listening on #{bind_string}") if @opts[:log]
|
71
71
|
|
72
72
|
# map fibers
|
73
73
|
@connection_fiber_map = {}
|
@@ -102,7 +102,7 @@ module TP2
|
|
102
102
|
|
103
103
|
def accept_incoming(listen_fd)
|
104
104
|
@machine.accept_each(listen_fd) do |fd|
|
105
|
-
conn = HTTP1Adapter.new(@machine, fd, &@app)
|
105
|
+
conn = HTTP1Adapter.new(@machine, fd, @opts, &@app)
|
106
106
|
f = @machine.spin(conn) do
|
107
107
|
it.run
|
108
108
|
ensure
|
@@ -110,6 +110,12 @@ module TP2
|
|
110
110
|
end
|
111
111
|
@connection_fiber_map[f] = true
|
112
112
|
end
|
113
|
+
rescue UM::Terminate
|
114
|
+
# terminated
|
115
|
+
rescue Exception => e
|
116
|
+
p e
|
117
|
+
p e.backtrace
|
118
|
+
exit!
|
113
119
|
end
|
114
120
|
|
115
121
|
def close_all_server_fds
|
data/lib/tp2/version.rb
CHANGED
data/lib/tp2.rb
CHANGED
@@ -4,6 +4,12 @@ require 'uringmachine'
|
|
4
4
|
require 'tp2/version'
|
5
5
|
require 'tp2/server'
|
6
6
|
|
7
|
+
class UringMachine
|
8
|
+
def puts(str)
|
9
|
+
write_async(1, "#{str}\n")
|
10
|
+
end
|
11
|
+
end
|
12
|
+
|
7
13
|
module TP2
|
8
14
|
BANNER = (
|
9
15
|
"\n" +
|
@@ -15,7 +21,6 @@ module TP2
|
|
15
21
|
" / \\ https://github.com/noteflakes/tp2\n" +
|
16
22
|
"⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺\n"
|
17
23
|
)
|
18
|
-
|
19
24
|
|
20
25
|
class << self
|
21
26
|
def run(opts = nil, &app)
|
@@ -27,8 +32,8 @@ module TP2
|
|
27
32
|
machine = UM.new
|
28
33
|
server = Server.new(machine, opts, &app)
|
29
34
|
|
30
|
-
setup_signal_handling(machine)
|
31
|
-
|
35
|
+
setup_signal_handling(machine, Fiber.current)
|
36
|
+
machine.puts(TP2::BANNER) if opts[:banner]
|
32
37
|
server.run
|
33
38
|
ensure
|
34
39
|
@in_run = false
|
@@ -44,18 +49,18 @@ module TP2
|
|
44
49
|
|
45
50
|
private
|
46
51
|
|
47
|
-
def setup_signal_handling(machine)
|
52
|
+
def setup_signal_handling(machine, fiber)
|
48
53
|
queue = UM::Queue.new
|
49
54
|
trap('SIGINT') { machine.push(queue, :SIGINT) }
|
50
|
-
machine.spin {
|
55
|
+
machine.spin { watch_for_int_signal(machine, queue, fiber) }
|
51
56
|
end
|
52
57
|
|
53
58
|
# waits for signal from queue, then terminates given fiber
|
54
59
|
# to be done
|
55
|
-
def
|
60
|
+
def watch_for_int_signal(machine, queue, fiber)
|
56
61
|
sig = machine.shift(queue)
|
57
|
-
puts
|
62
|
+
machine.puts("\nGot #{sig} signal, shutting down gracefully...")
|
58
63
|
machine.schedule(fiber, UM::Terminate.new)
|
59
64
|
end
|
60
65
|
end
|
61
|
-
end
|
66
|
+
end
|
data/test/test_http1_adapter.rb
CHANGED
@@ -31,7 +31,7 @@ class HTTP1AdapterTest < Minitest::Test
|
|
31
31
|
@reqs = []
|
32
32
|
@hook = nil
|
33
33
|
@app = ->(req) { @hook&.call(req); @reqs << req }
|
34
|
-
@adapter = TP2::HTTP1Adapter.new(@machine, @s_fd, &@app)
|
34
|
+
@adapter = TP2::HTTP1Adapter.new(@machine, @s_fd, {}, &@app)
|
35
35
|
end
|
36
36
|
|
37
37
|
def teardown
|
data/test/test_server.rb
CHANGED
@@ -136,7 +136,8 @@ class ServerTest < Minitest::Test
|
|
136
136
|
':path' => '/foo',
|
137
137
|
':protocol' => 'http/1.1',
|
138
138
|
'server' => 'foo.com',
|
139
|
-
'content-length' => '3'
|
139
|
+
'content-length' => '3',
|
140
|
+
':tx' => 48,
|
140
141
|
}, headers)
|
141
142
|
body = @bodies.shift
|
142
143
|
assert_equal 'abc', body
|
@@ -148,7 +149,8 @@ class ServerTest < Minitest::Test
|
|
148
149
|
':path' => '/bar',
|
149
150
|
':protocol' => 'http/1.1',
|
150
151
|
'server' => 'bar.com',
|
151
|
-
'content-length' => '6'
|
152
|
+
'content-length' => '6',
|
153
|
+
':tx' => 51,
|
152
154
|
}, headers)
|
153
155
|
body = @bodies.shift
|
154
156
|
assert_equal 'defghi', body
|
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.
|
23
|
+
s.add_dependency 'uringmachine', '>=0.13'
|
24
24
|
s.add_dependency 'qeweney', '0.21'
|
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.8.1
|
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.13'
|
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.13'
|
26
26
|
- !ruby/object:Gem::Dependency
|
27
27
|
name: qeweney
|
28
28
|
requirement: !ruby/object:Gem::Requirement
|