tp2 0.16 → 0.17
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/.gitignore +1 -0
- data/CHANGELOG.md +6 -0
- data/README.md +1 -1
- data/TODO.md +3 -3
- data/lib/tp2/connection.rb +29 -15
- data/lib/tp2/request_extensions.rb +4 -0
- data/lib/tp2/server.rb +48 -23
- data/lib/tp2/version.rb +1 -1
- data/lib/tp2.rb +12 -12
- data/test/bm_connection.rb +83 -0
- data/test/test_connection.rb +65 -3
- data/test/test_server.rb +65 -6
- metadata +2 -1
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 7f064819fba5d42c4376f3895ee0cce07745149e6b9ed06e864acb1ac3515d9e
|
4
|
+
data.tar.gz: 84767b1d2cd87c6006ba7202f48c2bf1cdc9345ddf258c8fcd716bcf2023c264
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 72b97e276e4abb593566d761f44ac7bfa0a9af5d9ff5dcbece407412dd07eac84a9dfbe6d983c832010fb9a365be27053df471d2b9e76a2955b7332a33b06e69
|
7
|
+
data.tar.gz: 6a13102f1f2008b32aa34fec7da9b2cda496e19f5445525f398b6c4f0a863b15bb68bf94b457e56f0b3c6ace41d3caca4722158ea69fc638d6a828f39e047bbd
|
data/.gitignore
CHANGED
data/CHANGELOG.md
CHANGED
@@ -1,3 +1,9 @@
|
|
1
|
+
# 0.17 2025-09-14
|
2
|
+
|
3
|
+
- Add support for server extensions: `Date`, `Server` headers
|
4
|
+
- Add `Request#set_response_headers` for injecting response headers
|
5
|
+
- Add support for env[:server_headers]
|
6
|
+
|
1
7
|
# 0.16 2025-09-11
|
2
8
|
|
3
9
|
- Remove support for HTTP/0.9, HTTP/1.0, use chunked transfer encoding
|
data/README.md
CHANGED
@@ -3,7 +3,7 @@
|
|
3
3
|
# TP2 - a io_uring-based app server for Ruby
|
4
4
|
|
5
5
|
[](http://rubygems.org/gems/tp2)
|
6
|
-
[](https://github.com/noteflakes/tp2/actions/workflows/test.yml)
|
7
7
|
[](https://github.com/digital-fabric/tp2/blob/master/LICENSE)
|
8
8
|
|
9
9
|
TP2 is an experimental HTTP server based on
|
data/TODO.md
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
## Immediate
|
2
2
|
|
3
|
-
- [
|
4
|
-
- [
|
3
|
+
- [v] Remove support for HTTP/0.9, HTTP/1.0
|
4
|
+
- [v] Reply with 505 HTTP version not supported:
|
5
5
|
|
6
6
|
```
|
7
7
|
< GET / HTTP/0.9
|
@@ -10,7 +10,7 @@
|
|
10
10
|
> Connection: close
|
11
11
|
```
|
12
12
|
|
13
|
-
- [
|
13
|
+
- [v] Use chunked transfer encoding exclusively
|
14
14
|
- [ ] Cache rendered headers
|
15
15
|
- [ ] Look at sketch in ~/Desktop/docs/
|
16
16
|
|
data/lib/tp2/connection.rb
CHANGED
@@ -13,11 +13,12 @@ module TP2
|
|
13
13
|
class Connection
|
14
14
|
attr_reader :fd, :response_headers, :logger
|
15
15
|
|
16
|
-
def initialize(machine, fd,
|
16
|
+
def initialize(server, machine, fd, env, &app)
|
17
|
+
@server = server
|
17
18
|
@machine = machine
|
18
19
|
@fd = fd
|
19
|
-
@
|
20
|
-
@logger =
|
20
|
+
@env = env
|
21
|
+
@logger = env[:logger]
|
21
22
|
@stream = UM::Stream.new(machine, fd)
|
22
23
|
@app = app
|
23
24
|
|
@@ -63,7 +64,7 @@ module TP2
|
|
63
64
|
# Handles an error encountered while serving a request by logging the error
|
64
65
|
# and optionally sending an error response with the relevant HTTP status
|
65
66
|
# code. For I/O errors, no response is sent.
|
66
|
-
#
|
67
|
+
#
|
67
68
|
# @param request [Qeweney::Request] HTTP request
|
68
69
|
# @param err [Exception] error
|
69
70
|
# @return [void]
|
@@ -83,7 +84,7 @@ module TP2
|
|
83
84
|
end
|
84
85
|
|
85
86
|
# Logs the given err and given message.
|
86
|
-
#
|
87
|
+
#
|
87
88
|
# @param err [Exception] error
|
88
89
|
# @param message [String] error message
|
89
90
|
# @return [void]
|
@@ -131,6 +132,16 @@ module TP2
|
|
131
132
|
|
132
133
|
# response API
|
133
134
|
|
135
|
+
# Sets response headers before sending any response. This method is used to
|
136
|
+
# add headers such as Set-Cookie or cache control headers to a response
|
137
|
+
# before actually responding, specifically in middleware hooks.
|
138
|
+
#
|
139
|
+
# @param headers [Hash] response headers
|
140
|
+
# @return [void]
|
141
|
+
def set_response_headers(headers)
|
142
|
+
@response_headers ? @response_headers.merge!(headers) : @response_headers = headers
|
143
|
+
end
|
144
|
+
|
134
145
|
SEND_FLAGS = UM::MSG_NOSIGNAL | UM::MSG_WAITALL
|
135
146
|
|
136
147
|
# Sends response including headers and body. Waits for the request to complete
|
@@ -139,7 +150,10 @@ module TP2
|
|
139
150
|
# @param body [String] response body
|
140
151
|
# @param headers
|
141
152
|
def respond(request, body, headers)
|
153
|
+
headers = @response_headers.merge(headers) if @response_headers
|
154
|
+
|
142
155
|
formatted_headers = format_headers(headers, body)
|
156
|
+
@response_headers = headers
|
143
157
|
request&.tx_incr(formatted_headers.bytesize + (body ? body.bytesize : 0))
|
144
158
|
if body
|
145
159
|
buf = "#{formatted_headers}#{body.bytesize.to_s(16)}\r\n#{body}\r\n#{EMPTY_CHUNK}"
|
@@ -149,7 +163,6 @@ module TP2
|
|
149
163
|
end
|
150
164
|
@logger&.info(request: request, response_headers: headers) if request
|
151
165
|
@done = true
|
152
|
-
@response_headers = headers
|
153
166
|
end
|
154
167
|
|
155
168
|
# Sends response headers. If empty_response is truthy, the response status
|
@@ -201,28 +214,28 @@ module TP2
|
|
201
214
|
@done = true
|
202
215
|
end
|
203
216
|
|
204
|
-
def respond_with_static_file(req, path,
|
217
|
+
def respond_with_static_file(req, path, env, cache_headers)
|
205
218
|
fd = @machine.open(path, UM::O_RDONLY)
|
206
|
-
|
207
|
-
if
|
208
|
-
|
219
|
+
env ||= {}
|
220
|
+
if env[:headers]
|
221
|
+
env[:headers].merge!(cache_headers)
|
209
222
|
else
|
210
|
-
|
223
|
+
env[:headers] = cache_headers
|
211
224
|
end
|
212
225
|
|
213
|
-
maxlen =
|
226
|
+
maxlen = env[:max_len] || 65_536
|
214
227
|
buf = String.new(capacity: maxlen)
|
215
228
|
headers_sent = nil
|
216
229
|
loop do
|
217
230
|
res = @machine.read(fd, buf, maxlen, 0)
|
218
231
|
if res < maxlen && !headers_sent
|
219
|
-
return respond(req, buf,
|
232
|
+
return respond(req, buf, env[:headers])
|
220
233
|
elsif res == 0
|
221
234
|
return finish(req)
|
222
235
|
end
|
223
236
|
|
224
237
|
if !headers_sent
|
225
|
-
send_headers(req,
|
238
|
+
send_headers(req, env[:headers])
|
226
239
|
headers_sent = true
|
227
240
|
end
|
228
241
|
done = res < maxlen
|
@@ -230,7 +243,7 @@ module TP2
|
|
230
243
|
return if done
|
231
244
|
end
|
232
245
|
ensure
|
233
|
-
@machine.
|
246
|
+
@machine.close_async(fd) if fd
|
234
247
|
end
|
235
248
|
|
236
249
|
def close
|
@@ -332,6 +345,7 @@ module TP2
|
|
332
345
|
def format_headers(headers, body)
|
333
346
|
status = headers[':status'] || (body ? Qeweney::Status::OK : Qeweney::Status::NO_CONTENT)
|
334
347
|
lines = format_status_line(body, status)
|
348
|
+
lines << @env[:server_headers] if @env[:server_headers]
|
335
349
|
headers.each do |k, v|
|
336
350
|
next if k =~ INTERNAL_HEADER_REGEXP
|
337
351
|
|
data/lib/tp2/server.rb
CHANGED
@@ -9,42 +9,42 @@ module TP2
|
|
9
9
|
PENDING_REQUESTS_GRACE_PERIOD = 0.1
|
10
10
|
PENDING_REQUESTS_TIMEOUT_PERIOD = 5
|
11
11
|
|
12
|
-
def self.rack_app(
|
13
|
-
raise 'Missing app location' if !
|
12
|
+
def self.rack_app(env)
|
13
|
+
raise 'Missing app location' if !env[:app_location]
|
14
14
|
|
15
|
-
TP2::RackAdapter.load(
|
15
|
+
TP2::RackAdapter.load(env[:app_location])
|
16
16
|
end
|
17
17
|
|
18
|
-
def self.tp2_app(_machine,
|
19
|
-
if
|
20
|
-
|
21
|
-
require
|
18
|
+
def self.tp2_app(_machine, env)
|
19
|
+
if env[:app_location]
|
20
|
+
env[:logger]&.info(message: 'Loading web app', location: env[:app_location])
|
21
|
+
require env[:app_location]
|
22
22
|
|
23
|
-
|
23
|
+
env.merge!(TP2.config)
|
24
24
|
end
|
25
|
-
|
25
|
+
env[:app]
|
26
26
|
end
|
27
27
|
|
28
|
-
def self.static_app(
|
28
|
+
def self.static_app(env); end
|
29
29
|
|
30
|
-
def initialize(machine,
|
30
|
+
def initialize(machine, env, &app)
|
31
31
|
@machine = machine
|
32
|
-
@
|
33
|
-
@app = app ||
|
32
|
+
@env = env
|
33
|
+
@app = app || app_from_env
|
34
34
|
@server_fds = []
|
35
35
|
@accept_fibers = []
|
36
36
|
end
|
37
37
|
|
38
|
-
def
|
39
|
-
case @
|
38
|
+
def app_from_env
|
39
|
+
case @env[:app_type]
|
40
40
|
when nil, :tp2
|
41
|
-
Server.tp2_app(@machine, @
|
41
|
+
Server.tp2_app(@machine, @env)
|
42
42
|
when :rack
|
43
|
-
Server.rack_app(@
|
43
|
+
Server.rack_app(@env)
|
44
44
|
when :static
|
45
|
-
Server.static_app(@
|
45
|
+
Server.static_app(@env)
|
46
46
|
else
|
47
|
-
raise "Invalid app type #{@
|
47
|
+
raise "Invalid app type #{@env[:app_type].inspect}"
|
48
48
|
end
|
49
49
|
end
|
50
50
|
|
@@ -65,14 +65,15 @@ module TP2
|
|
65
65
|
@accept_fibers << @machine.spin { accept_incoming(fd) }
|
66
66
|
end
|
67
67
|
bind_string = bind_info.map { it.join(':') }.join(', ')
|
68
|
-
@
|
68
|
+
@env[:logger]&.info(message: "Listening on #{bind_string}")
|
69
|
+
setup_server_extensions
|
69
70
|
|
70
71
|
# map fibers
|
71
72
|
@connection_fiber_map = {}
|
72
73
|
end
|
73
74
|
|
74
75
|
def get_bind_entries
|
75
|
-
bind = @
|
76
|
+
bind = @env[:bind]
|
76
77
|
case bind
|
77
78
|
when Array
|
78
79
|
bind.map { bind_info(it) }
|
@@ -98,9 +99,33 @@ module TP2
|
|
98
99
|
fd
|
99
100
|
end
|
100
101
|
|
102
|
+
def setup_server_extensions
|
103
|
+
extensions = @env[:server_extensions]
|
104
|
+
return if !extensions
|
105
|
+
|
106
|
+
server_name = extensions[:name]
|
107
|
+
if extensions[:date]
|
108
|
+
@date_header_fiber = @machine.spin {
|
109
|
+
@machine.periodically(1) { update_server_headers(server_name) }
|
110
|
+
}
|
111
|
+
update_server_headers(server_name)
|
112
|
+
elsif server_name
|
113
|
+
@env[:server_headers] = "Server: #{server_name}\r\n"
|
114
|
+
end
|
115
|
+
end
|
116
|
+
|
117
|
+
def update_server_headers(server_name)
|
118
|
+
@env[:server_date] = Time.now
|
119
|
+
if server_name
|
120
|
+
@env[:server_headers] = "Server: #{server_name}\r\nDate: #{@env[:server_date].httpdate}\r\n"
|
121
|
+
else
|
122
|
+
@env[:server_headers] = "Date: #{Time.now.httpdate}\r\n"
|
123
|
+
end
|
124
|
+
end
|
125
|
+
|
101
126
|
def accept_incoming(listen_fd)
|
102
127
|
@machine.accept_each(listen_fd) do |fd|
|
103
|
-
conn = Connection.new(@machine, fd, @
|
128
|
+
conn = Connection.new(self, @machine, fd, @env, &@app)
|
104
129
|
f = @machine.spin(conn) do
|
105
130
|
it.run
|
106
131
|
ensure
|
@@ -121,7 +146,7 @@ module TP2
|
|
121
146
|
end
|
122
147
|
|
123
148
|
def graceful_shutdown
|
124
|
-
@
|
149
|
+
@env[:logger]&.info(message: 'Shutting down gracefully...')
|
125
150
|
|
126
151
|
# stop listening
|
127
152
|
close_all_server_fds
|
data/lib/tp2/version.rb
CHANGED
data/lib/tp2.rb
CHANGED
@@ -24,22 +24,22 @@ module TP2
|
|
24
24
|
)
|
25
25
|
|
26
26
|
class << self
|
27
|
-
def run(
|
27
|
+
def run(env = {}, &app)
|
28
28
|
if @in_run
|
29
|
-
@
|
30
|
-
@
|
29
|
+
@env = env
|
30
|
+
@env[:app] = app if app
|
31
31
|
return
|
32
32
|
end
|
33
33
|
|
34
|
-
|
34
|
+
env ||= @env || {}
|
35
35
|
begin
|
36
36
|
@in_run = true
|
37
|
-
machine =
|
38
|
-
machine.puts(
|
37
|
+
machine = env[:machine] || UM.new
|
38
|
+
machine.puts(env[:banner]) if env[:banner]
|
39
39
|
|
40
|
-
|
40
|
+
env[:logger]&.info(message: "Running TP2 #{TP2::VERSION}, UringMachine #{UM::VERSION}, Ruby #{RUBY_VERSION}")
|
41
41
|
|
42
|
-
server = Server.new(machine,
|
42
|
+
server = Server.new(machine, env, &app)
|
43
43
|
|
44
44
|
setup_signal_handling(machine, Fiber.current)
|
45
45
|
server.run
|
@@ -48,11 +48,11 @@ module TP2
|
|
48
48
|
end
|
49
49
|
end
|
50
50
|
|
51
|
-
def
|
52
|
-
return @
|
51
|
+
def env(env = nil, &app)
|
52
|
+
return @env if !env && !app
|
53
53
|
|
54
|
-
@
|
55
|
-
@
|
54
|
+
@env = env || {}
|
55
|
+
@env[:app] = app if app
|
56
56
|
end
|
57
57
|
|
58
58
|
private
|
@@ -0,0 +1,83 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'bundler/inline'
|
4
|
+
|
5
|
+
gemfile {
|
6
|
+
gem 'tp2', path: '..'
|
7
|
+
gem 'benchmark'
|
8
|
+
gem 'benchmark-ips'
|
9
|
+
gem 'vernier'
|
10
|
+
}
|
11
|
+
|
12
|
+
require 'vernier'
|
13
|
+
require 'securerandom'
|
14
|
+
require 'benchmark/ips'
|
15
|
+
|
16
|
+
class ConnectionTester
|
17
|
+
def make_socket_pair
|
18
|
+
port = SecureRandom.random_number(10000..40000)
|
19
|
+
server_fd = @machine.socket(UM::AF_INET, UM::SOCK_STREAM, 0, 0)
|
20
|
+
@machine.setsockopt(server_fd, UM::SOL_SOCKET, UM::SO_REUSEADDR, true)
|
21
|
+
@machine.bind(server_fd, '127.0.0.1', port)
|
22
|
+
@machine.listen(server_fd, UM::SOMAXCONN)
|
23
|
+
|
24
|
+
client_conn_fd = @machine.socket(UM::AF_INET, UM::SOCK_STREAM, 0, 0)
|
25
|
+
@machine.connect(client_conn_fd, '127.0.0.1', port)
|
26
|
+
|
27
|
+
server_conn_fd = @machine.accept(server_fd)
|
28
|
+
|
29
|
+
@machine.close(server_fd)
|
30
|
+
[client_conn_fd, server_conn_fd]
|
31
|
+
end
|
32
|
+
|
33
|
+
def setup
|
34
|
+
@machine = UM.new
|
35
|
+
@c_fd, @s_fd = make_socket_pair
|
36
|
+
@reqs = []
|
37
|
+
@app = ->(req) { req.respond('foobar', 'Content-Type' => 'text/plain') }
|
38
|
+
@adapter = TP2::Connection.new(nil, @machine, @s_fd, {}, &@app)
|
39
|
+
end
|
40
|
+
|
41
|
+
def teardown
|
42
|
+
@machine.close(@c_fd) rescue nil
|
43
|
+
@machine.close(@s_fd) rescue nil
|
44
|
+
end
|
45
|
+
|
46
|
+
def write_http_request(msg, shutdown_wr = true)
|
47
|
+
@machine.send(@c_fd, msg, msg.bytesize, UM::MSG_WAITALL)
|
48
|
+
@machine.shutdown(@c_fd, UM::SHUT_WR) if shutdown_wr
|
49
|
+
end
|
50
|
+
|
51
|
+
def write_client_side(msg)
|
52
|
+
@machine.send(@c_fd, msg, msg.bytesize, UM::MSG_WAITALL)
|
53
|
+
end
|
54
|
+
|
55
|
+
def read_client_side(len = 65536)
|
56
|
+
buf = +''
|
57
|
+
res = @machine.recv(@c_fd, buf, len, 0)
|
58
|
+
res == 0 ? nil : buf
|
59
|
+
end
|
60
|
+
|
61
|
+
def run_request
|
62
|
+
write_http_request "GET / HTTP/1.1\r\n\r\n", false
|
63
|
+
@adapter.serve_request
|
64
|
+
read_client_side(len = 65536)
|
65
|
+
end
|
66
|
+
end
|
67
|
+
|
68
|
+
tester = ConnectionTester.new
|
69
|
+
tester.setup
|
70
|
+
|
71
|
+
# puts '*' * 40
|
72
|
+
# p tester.run_request
|
73
|
+
# p tester.run_request
|
74
|
+
# puts '*' * 40
|
75
|
+
|
76
|
+
# Vernier.profile(out: "profile.vernier.json") {
|
77
|
+
# 100000.times { tester.run_request }
|
78
|
+
# }
|
79
|
+
|
80
|
+
Benchmark.ips do |x|
|
81
|
+
x.report('request') { tester.run_request }
|
82
|
+
x.compare!(order: :baseline)
|
83
|
+
end
|
data/test/test_connection.rb
CHANGED
@@ -1,10 +1,11 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
3
|
require_relative './helper'
|
4
|
+
require 'securerandom'
|
4
5
|
|
5
6
|
class ConnectionTest < Minitest::Test
|
6
7
|
def make_socket_pair
|
7
|
-
port = 10000
|
8
|
+
port = SecureRandom.random_number(10000..40000)
|
8
9
|
server_fd = @machine.socket(UM::AF_INET, UM::SOCK_STREAM, 0, 0)
|
9
10
|
@machine.setsockopt(server_fd, UM::SOL_SOCKET, UM::SO_REUSEADDR, true)
|
10
11
|
@machine.bind(server_fd, '127.0.0.1', port)
|
@@ -25,7 +26,8 @@ class ConnectionTest < Minitest::Test
|
|
25
26
|
@reqs = []
|
26
27
|
@hook = nil
|
27
28
|
@app = ->(req) { @hook&.call(req); @reqs << req }
|
28
|
-
@
|
29
|
+
@env = {}
|
30
|
+
@adapter = TP2::Connection.new(nil, @machine, @s_fd, @env, &@app)
|
29
31
|
end
|
30
32
|
|
31
33
|
def teardown
|
@@ -341,7 +343,7 @@ class ConnectionTest < Minitest::Test
|
|
341
343
|
@adapter.run
|
342
344
|
response = read_client_side
|
343
345
|
|
344
|
-
expected = "HTTP/1.1 200\r\nTransfer-Encoding: chunked\r\n\r\nd\r\nHello, world!\r\n0\r\n\r\n" +
|
346
|
+
expected = "HTTP/1.1 200\r\nTransfer-Encoding: chunked\r\n\r\nd\r\nHello, world!\r\n0\r\n\r\n" +
|
345
347
|
"HTTP/1.1 200\r\nTransfer-Encoding: chunked\r\n\r\ne\r\nHello, foobar!\r\n0\r\n\r\n"
|
346
348
|
assert_equal(expected, response)
|
347
349
|
end
|
@@ -520,4 +522,64 @@ class ConnectionTest < Minitest::Test
|
|
520
522
|
expected = "HTTP/1.1 200\r\nTransfer-Encoding: chunked\r\n\r\n3\r\nfoo\r\n3\r\nbar\r\n0\r\n\r\n"
|
521
523
|
assert_equal expected, response
|
522
524
|
end
|
525
|
+
|
526
|
+
def test_connection_server_headers
|
527
|
+
@env[:server_headers] = "Server: TP2\r\n"
|
528
|
+
|
529
|
+
@hook = ->(req) do
|
530
|
+
req.respond('foo')
|
531
|
+
end
|
532
|
+
|
533
|
+
write_client_side("GET / HTTP/1.1\r\n\r\n")
|
534
|
+
@adapter.serve_request
|
535
|
+
response = read_client_side(65536)
|
536
|
+
expected = "HTTP/1.1 200\r\nTransfer-Encoding: chunked\r\nServer: TP2\r\n\r\n3\r\nfoo\r\n0\r\n\r\n"
|
537
|
+
assert_equal expected, response
|
538
|
+
|
539
|
+
@env[:server_headers] = "Server: TP3\r\n"
|
540
|
+
|
541
|
+
write_client_side("GET / HTTP/1.1\r\n\r\n")
|
542
|
+
@adapter.serve_request
|
543
|
+
response = read_client_side(65536)
|
544
|
+
expected = "HTTP/1.1 200\r\nTransfer-Encoding: chunked\r\nServer: TP3\r\n\r\n3\r\nfoo\r\n0\r\n\r\n"
|
545
|
+
assert_equal expected, response
|
546
|
+
end
|
547
|
+
|
548
|
+
def test_set_response_headers
|
549
|
+
@hook = ->(req) {
|
550
|
+
req.set_response_headers("Set-Cookie" => 'foo=bar')
|
551
|
+
req.respond('foo')
|
552
|
+
}
|
553
|
+
|
554
|
+
write_client_side("GET / HTTP/1.1\r\n\r\n")
|
555
|
+
@adapter.serve_request
|
556
|
+
response = read_client_side(65536)
|
557
|
+
expected = "HTTP/1.1 200\r\nTransfer-Encoding: chunked\r\nSet-Cookie: foo=bar\r\n\r\n3\r\nfoo\r\n0\r\n\r\n"
|
558
|
+
assert_equal expected, response
|
559
|
+
|
560
|
+
@hook = ->(req) {
|
561
|
+
req.set_response_headers("Set-Cookie" => 'foo=bar')
|
562
|
+
req.respond('foo', 'Content-Type' => 'text/plain')
|
563
|
+
}
|
564
|
+
|
565
|
+
write_client_side("GET / HTTP/1.1\r\n\r\n")
|
566
|
+
@adapter.serve_request
|
567
|
+
response = read_client_side(65536)
|
568
|
+
expected = "HTTP/1.1 200\r\nTransfer-Encoding: chunked\r\nSet-Cookie: foo=bar\r\nContent-Type: text/plain\r\n\r\n3\r\nfoo\r\n0\r\n\r\n"
|
569
|
+
assert_equal expected, response
|
570
|
+
end
|
571
|
+
|
572
|
+
def test_set_response_headers
|
573
|
+
@hook = ->(req) {
|
574
|
+
req.set_response_headers("Set-Cookie" => 'foo=bar')
|
575
|
+
req.set_response_headers("Foo" => 'bar')
|
576
|
+
req.respond('foo', 'Content-Type' => 'text/plain')
|
577
|
+
}
|
578
|
+
|
579
|
+
write_client_side("GET / HTTP/1.1\r\n\r\n")
|
580
|
+
@adapter.serve_request
|
581
|
+
response = read_client_side(65536)
|
582
|
+
expected = "HTTP/1.1 200\r\nTransfer-Encoding: chunked\r\nSet-Cookie: foo=bar\r\nFoo: bar\r\nContent-Type: text/plain\r\n\r\n3\r\nfoo\r\n0\r\n\r\n"
|
583
|
+
assert_equal expected, response
|
584
|
+
end
|
523
585
|
end
|
data/test/test_server.rb
CHANGED
@@ -22,11 +22,11 @@ class ServerTest < Minitest::Test
|
|
22
22
|
class STOP < StandardError
|
23
23
|
end
|
24
24
|
|
25
|
-
def setup
|
25
|
+
def setup(opts = {})
|
26
26
|
@machine = UM.new
|
27
27
|
@port = 10000 + rand(30000)
|
28
|
-
@
|
29
|
-
@server = TP2::Server.new(@machine, @
|
28
|
+
@env = { bind: "127.0.0.1:#{@port}" }.merge(opts)
|
29
|
+
@server = TP2::Server.new(@machine, @env) { @app&.call(it) }
|
30
30
|
@f_server = @machine.spin { run_server }
|
31
31
|
|
32
32
|
# let server spin and listen to incoming connections
|
@@ -94,8 +94,9 @@ class ServerTest < Minitest::Test
|
|
94
94
|
|
95
95
|
write_http_request "GET /foo HTTP/1.1\r\nServer: foo.com\r\n\r\nSCHmet /bar HTTP/1.1\r\n\r\n"
|
96
96
|
|
97
|
+
@machine.sleep(0.1)
|
97
98
|
response = read_client_side
|
98
|
-
expected = "HTTP/1.1 200\r\nTransfer-Encoding: chunked\r\n\r\nb\r\nmethod: get\r\n0\r\n\r\n" +
|
99
|
+
expected = "HTTP/1.1 200\r\nTransfer-Encoding: chunked\r\n\r\nb\r\nmethod: get\r\n0\r\n\r\n" +
|
99
100
|
"HTTP/1.1 200\r\nTransfer-Encoding: chunked\r\n\r\ne\r\nmethod: schmet\r\n0\r\n\r\n"
|
100
101
|
assert_equal(expected, response)
|
101
102
|
end
|
@@ -278,7 +279,7 @@ class ServerTest < Minitest::Test
|
|
278
279
|
def test_logging
|
279
280
|
skip
|
280
281
|
reqs = []
|
281
|
-
@
|
282
|
+
@env[:logger] = TestLogger.new
|
282
283
|
@app = ->(req) { reqs << req; req.respond('Hello, world!', {}) }
|
283
284
|
|
284
285
|
write_http_request "GET / HTTP/1.0\r\n\r\n"
|
@@ -286,10 +287,68 @@ class ServerTest < Minitest::Test
|
|
286
287
|
expected = "HTTP/1.1 200\r\nContent-Length: 13\r\n\r\nHello, world!"
|
287
288
|
assert_equal(expected, response)
|
288
289
|
|
289
|
-
entries = @
|
290
|
+
entries = @env[:logger].entries
|
290
291
|
assert_equal 1, entries.size
|
291
292
|
assert_equal 1, reqs.size
|
292
293
|
|
293
294
|
assert_equal reqs.first, entries.first[:request]
|
294
295
|
end
|
296
|
+
|
297
|
+
def test_server_headers
|
298
|
+
@env[:server_headers] = "Server: Tipi\r\n"
|
299
|
+
|
300
|
+
@app = ->(req) {
|
301
|
+
req.respond('Hello, world!', {})
|
302
|
+
}
|
303
|
+
|
304
|
+
write_http_request "GET / HTTP/1.1\r\n\r\n"
|
305
|
+
response = read_client_side
|
306
|
+
expected = "HTTP/1.1 200\r\nTransfer-Encoding: chunked\r\nServer: Tipi\r\n\r\nd\r\nHello, world!\r\n0\r\n\r\n"
|
307
|
+
assert_equal(expected, response)
|
308
|
+
end
|
309
|
+
|
310
|
+
def test_server_headers_date
|
311
|
+
setup({ server_extensions: { date: true } })
|
312
|
+
@machine.sleep(0.1)
|
313
|
+
assert_kind_of Time, @env[:server_date]
|
314
|
+
|
315
|
+
@app = ->(req) {
|
316
|
+
req.respond('foo', {})
|
317
|
+
}
|
318
|
+
|
319
|
+
write_http_request "GET / HTTP/1.1\r\n\r\n"
|
320
|
+
response = read_client_side
|
321
|
+
expected = "HTTP/1.1 200\r\nTransfer-Encoding: chunked\r\nDate: #{@env[:server_date].httpdate}\r\n\r\n3\r\nfoo\r\n0\r\n\r\n"
|
322
|
+
assert_equal(expected, response)
|
323
|
+
end
|
324
|
+
|
325
|
+
def test_server_headers_date_and_server_name
|
326
|
+
setup({ server_extensions: { date: true, name: 'Foo' } })
|
327
|
+
@machine.sleep(0.1)
|
328
|
+
assert_kind_of Time, @env[:server_date]
|
329
|
+
|
330
|
+
@app = ->(req) {
|
331
|
+
req.respond('foo', {})
|
332
|
+
}
|
333
|
+
|
334
|
+
write_http_request "GET / HTTP/1.1\r\n\r\n"
|
335
|
+
response = read_client_side
|
336
|
+
expected = "HTTP/1.1 200\r\nTransfer-Encoding: chunked\r\nServer: Foo\r\nDate: #{@env[:server_date].httpdate}\r\n\r\n3\r\nfoo\r\n0\r\n\r\n"
|
337
|
+
assert_equal(expected, response)
|
338
|
+
end
|
339
|
+
|
340
|
+
def test_server_headers_server_name
|
341
|
+
setup({ server_extensions: { name: 'Bar' } })
|
342
|
+
@machine.sleep(0.1)
|
343
|
+
assert_nil @env[:server_date]
|
344
|
+
|
345
|
+
@app = ->(req) {
|
346
|
+
req.respond('foo', {})
|
347
|
+
}
|
348
|
+
|
349
|
+
write_http_request "GET / HTTP/1.1\r\n\r\n"
|
350
|
+
response = read_client_side
|
351
|
+
expected = "HTTP/1.1 200\r\nTransfer-Encoding: chunked\r\nServer: Bar\r\n\r\n3\r\nfoo\r\n0\r\n\r\n"
|
352
|
+
assert_equal(expected, response)
|
353
|
+
end
|
295
354
|
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.17'
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Sharon Rosner
|
@@ -78,6 +78,7 @@ files:
|
|
78
78
|
- lib/tp2/request_extensions.rb
|
79
79
|
- lib/tp2/server.rb
|
80
80
|
- lib/tp2/version.rb
|
81
|
+
- test/bm_connection.rb
|
81
82
|
- test/helper.rb
|
82
83
|
- test/run.rb
|
83
84
|
- test/test_connection.rb
|