tp2 0.14.1 → 0.16
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 +11 -0
- data/README.md +7 -1
- data/TODO.md +22 -0
- data/lib/tp2/{http1_connection.rb → connection.rb} +61 -53
- data/lib/tp2/errors.rb +15 -0
- data/lib/tp2/server.rb +2 -2
- data/lib/tp2/version.rb +1 -1
- data/test/helper.rb +23 -0
- data/test/{test_http1_connection.rb → test_connection.rb} +37 -84
- data/test/test_server.rb +18 -6
- data/tp2-logo.png +0 -0
- data/tp2.gemspec +2 -2
- metadata +10 -8
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 9b39e332d3140ec95dee3edc265fec814873208d8b17797fccd5513b843b614b
|
4
|
+
data.tar.gz: f39b677a8e703b9d9f8a48aa223365b6355eb5810735812cf600ae1645351d89
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 0cab6849c05ca00890073e2ef7fe2db7410fbda9ac10f4d908fba7c04e02452dc31ec4f9c24c56dd5719ca11c6bcd57de325b1e422fab4114515674e3273c854
|
7
|
+
data.tar.gz: 7cb455c7da2207024e6ce8d41435dee1c6ed2d4357b259d9b34a750f829504576304aac7cc8e998a5bd06eeca61db8e643311bcf67c0726528bf3a0f7d04699a
|
data/CHANGELOG.md
CHANGED
@@ -1,3 +1,14 @@
|
|
1
|
+
# 0.16 2025-09-11
|
2
|
+
|
3
|
+
- Remove support for HTTP/0.9, HTTP/1.0, use chunked transfer encoding
|
4
|
+
exclusively for HTTP responses
|
5
|
+
- Remove call to app.ready
|
6
|
+
|
7
|
+
# 0.15 2025-08-30
|
8
|
+
|
9
|
+
- Call app.ready after setting up, if method available
|
10
|
+
- Update uringmachine, qeweney deps
|
11
|
+
|
1
12
|
# 0.14.1 2025-07-08
|
2
13
|
|
3
14
|
- Add request elapsed time to log
|
data/README.md
CHANGED
@@ -1,4 +1,10 @@
|
|
1
|
-
|
1
|
+
<p align="center"><img src="tp2-logo.png" /></p>
|
2
|
+
|
3
|
+
# TP2 - a io_uring-based app server for Ruby
|
4
|
+
|
5
|
+
[](http://rubygems.org/gems/tp2)
|
6
|
+
[](https://github.com/digital-fabric/tp2/actions?query=workflow%3ATests)
|
7
|
+
[](https://github.com/digital-fabric/tp2/blob/master/LICENSE)
|
2
8
|
|
3
9
|
TP2 is an experimental HTTP server based on
|
4
10
|
[UringMachine](https://github.com/digital-fabric/uringmachine) and [Qeweney](https://github.com/digital-fabric/qeweney).
|
data/TODO.md
CHANGED
@@ -1,3 +1,25 @@
|
|
1
|
+
## Immediate
|
2
|
+
|
3
|
+
- [ ] Remove support for HTTP/0.9, HTTP/1.0
|
4
|
+
- [ ] Reply with 505 HTTP version not supported:
|
5
|
+
|
6
|
+
```
|
7
|
+
< GET / HTTP/0.9
|
8
|
+
|
9
|
+
> HTTP/1.1 505
|
10
|
+
> Connection: close
|
11
|
+
```
|
12
|
+
|
13
|
+
- [ ] Use chunked transfer encoding exclusively
|
14
|
+
- [ ] Cache rendered headers
|
15
|
+
- [ ] Look at sketch in ~/Desktop/docs/
|
16
|
+
|
17
|
+
- [ ] Add options for:
|
18
|
+
- [ ] Date header
|
19
|
+
- [ ] Server header
|
20
|
+
|
21
|
+
##
|
22
|
+
|
1
23
|
- Add failing tests for request bombs (requests with lots of bytes)
|
2
24
|
- Add test for `ProtocolError` handling
|
3
25
|
- Add limits with tests for:
|
@@ -2,10 +2,16 @@
|
|
2
2
|
|
3
3
|
require 'qeweney'
|
4
4
|
require 'stringio'
|
5
|
+
require 'tp2/errors'
|
5
6
|
|
6
7
|
module TP2
|
7
|
-
|
8
|
-
|
8
|
+
# Implements an HTTP/1.1 connection received by the TP2 server. This
|
9
|
+
# implementation rejects incoming HTTP/0.9 or HTTP/1.0 requests. The response
|
10
|
+
# body is sent exclusively using chunked transfer encoding. Request bodies are
|
11
|
+
# accepted using either fixed length (Content-Length header) or chunked
|
12
|
+
# transfer encoding.
|
13
|
+
class Connection
|
14
|
+
attr_reader :fd, :response_headers, :logger
|
9
15
|
|
10
16
|
def initialize(machine, fd, opts, &app)
|
11
17
|
@machine = machine
|
@@ -37,37 +43,52 @@ module TP2
|
|
37
43
|
@machine.close_async(@fd)
|
38
44
|
end
|
39
45
|
|
40
|
-
#
|
46
|
+
# Processes an incoming request by parsing the headers, creating a request
|
47
|
+
# object and handing it off to the app handler. Returns true if the
|
48
|
+
# connection should be persisted.
|
41
49
|
def serve_request
|
42
50
|
headers = parse_headers
|
43
51
|
return false if !headers
|
44
52
|
|
45
53
|
request = Qeweney::Request.new(headers, self)
|
54
|
+
|
46
55
|
request.start_stamp = monotonic_clock
|
47
56
|
@app.call(request)
|
48
57
|
persist_connection?(headers)
|
49
|
-
rescue ProtocolError => e
|
50
|
-
@logger&.error(
|
51
|
-
message: 'Protocol error, closing connection',
|
52
|
-
error: e
|
53
|
-
)
|
54
|
-
false
|
55
|
-
rescue SystemCallError => e
|
56
|
-
@logger&.error(
|
57
|
-
message: 'I/O error, closing connection',
|
58
|
-
error: e
|
59
|
-
)
|
60
|
-
false
|
61
58
|
rescue StandardError => e
|
62
|
-
|
63
|
-
|
64
|
-
|
65
|
-
|
59
|
+
handle_error(request, e)
|
60
|
+
false
|
61
|
+
end
|
62
|
+
|
63
|
+
# Handles an error encountered while serving a request by logging the error
|
64
|
+
# and optionally sending an error response with the relevant HTTP status
|
65
|
+
# code. For I/O errors, no response is sent.
|
66
|
+
#
|
67
|
+
# @param request [Qeweney::Request] HTTP request
|
68
|
+
# @param err [Exception] error
|
69
|
+
# @return [void]
|
70
|
+
def handle_error(request, err)
|
71
|
+
case err
|
72
|
+
when SystemCallError
|
73
|
+
log_error(err, 'I/O error')
|
74
|
+
when ProtocolError
|
75
|
+
log_error(err, err.message)
|
76
|
+
respond(request, err.message, ':status' => err.http_status)
|
77
|
+
else
|
78
|
+
log_error(err, 'Internal error')
|
79
|
+
return if !request || @done
|
66
80
|
|
67
|
-
if request && !@done
|
68
81
|
respond(request, 'Internal server error', ':status' => Qeweney::Status::INTERNAL_SERVER_ERROR)
|
69
82
|
end
|
70
|
-
|
83
|
+
end
|
84
|
+
|
85
|
+
# Logs the given err and given message.
|
86
|
+
#
|
87
|
+
# @param err [Exception] error
|
88
|
+
# @param message [String] error message
|
89
|
+
# @return [void]
|
90
|
+
def log_error(err, message)
|
91
|
+
@logger&.error(message: "#{message}, closing connection", error: err)
|
71
92
|
end
|
72
93
|
|
73
94
|
def get_body(req)
|
@@ -118,16 +139,15 @@ module TP2
|
|
118
139
|
# @param body [String] response body
|
119
140
|
# @param headers
|
120
141
|
def respond(request, body, headers)
|
121
|
-
formatted_headers = format_headers(headers, body
|
122
|
-
request
|
142
|
+
formatted_headers = format_headers(headers, body)
|
143
|
+
request&.tx_incr(formatted_headers.bytesize + (body ? body.bytesize : 0))
|
123
144
|
if body
|
124
|
-
buf = formatted_headers
|
145
|
+
buf = "#{formatted_headers}#{body.bytesize.to_s(16)}\r\n#{body}\r\n#{EMPTY_CHUNK}"
|
125
146
|
@machine.send(@fd, buf, buf.bytesize, SEND_FLAGS)
|
126
|
-
# handle_write(formatted_headers + body)
|
127
147
|
else
|
128
148
|
@machine.send(@fd, formatted_headers, formatted_headers.bytesize, SEND_FLAGS)
|
129
149
|
end
|
130
|
-
@logger&.info(request: request, response_headers: headers)
|
150
|
+
@logger&.info(request: request, response_headers: headers) if request
|
131
151
|
@done = true
|
132
152
|
@response_headers = headers
|
133
153
|
end
|
@@ -137,10 +157,9 @@ module TP2
|
|
137
157
|
# @param request [Qeweney::Request] HTTP request
|
138
158
|
# @param headers [Hash] response headers
|
139
159
|
# @param empty_response [boolean] whether a response body will be sent
|
140
|
-
# @param chunked [boolean] whether to use chunked transfer encoding
|
141
160
|
# @return [void]
|
142
|
-
def send_headers(request, headers, empty_response: false
|
143
|
-
formatted_headers = format_headers(headers, !empty_response
|
161
|
+
def send_headers(request, headers, empty_response: false)
|
162
|
+
formatted_headers = format_headers(headers, !empty_response)
|
144
163
|
request.tx_incr(formatted_headers.bytesize)
|
145
164
|
@machine.send(@fd, formatted_headers, formatted_headers.bytesize, SEND_FLAGS)
|
146
165
|
@response_headers = headers
|
@@ -224,20 +243,15 @@ module TP2
|
|
224
243
|
|
225
244
|
private
|
226
245
|
|
227
|
-
RE_REQUEST_LINE =
|
246
|
+
RE_REQUEST_LINE = /^([a-z]+)\s+([^\s]+)\s+http\/([019\.]{1,3})/i
|
228
247
|
RE_HEADER_LINE = /^([a-z0-9-]+):\s+(.+)/i
|
229
248
|
MAX_REQUEST_LINE_LEN = 1 << 14 # 16KB
|
230
249
|
MAX_HEADER_LINE_LEN = 1 << 10 # 1KB
|
231
250
|
MAX_CHUNK_SIZE_LEN = 16
|
232
251
|
|
233
|
-
class ProtocolError < StandardError
|
234
|
-
end
|
235
|
-
|
236
252
|
def persist_connection?(headers)
|
237
253
|
connection = headers['connection']&.downcase
|
238
|
-
return connection != 'close'
|
239
|
-
|
240
|
-
connection && connection != 'close'
|
254
|
+
return connection != 'close'
|
241
255
|
end
|
242
256
|
|
243
257
|
def parse_headers
|
@@ -263,12 +277,15 @@ module TP2
|
|
263
277
|
return nil if !line
|
264
278
|
|
265
279
|
m = line.match(RE_REQUEST_LINE)
|
266
|
-
raise ProtocolError,
|
280
|
+
raise ProtocolError, 'Invalid request line' if !m
|
281
|
+
|
282
|
+
http_version = m[3]
|
283
|
+
raise UnsupportedHTTPVersionError, 'HTTP version not supported' if http_version != '1.1'
|
267
284
|
|
268
285
|
{
|
269
286
|
':method' => m[1].downcase,
|
270
287
|
':path' => m[2],
|
271
|
-
':protocol' =>
|
288
|
+
':protocol' => 'http/1.1'
|
272
289
|
}
|
273
290
|
end
|
274
291
|
|
@@ -305,21 +322,16 @@ module TP2
|
|
305
322
|
buffer ? (buffer << chunk) : chunk
|
306
323
|
end
|
307
324
|
|
308
|
-
def http1_1?(request)
|
309
|
-
request.headers[':protocol'] == 'http/1.1'
|
310
|
-
end
|
311
|
-
|
312
325
|
INTERNAL_HEADER_REGEXP = /^:/
|
313
326
|
|
314
327
|
# Formats response headers into an array. If empty_response is true(thy),
|
315
328
|
# the response status code will default to 204, otherwise to 200.
|
316
329
|
# @param headers [Hash] response headers
|
317
330
|
# @param body [boolean] whether a response body will be sent
|
318
|
-
# @param chunked [boolean] whether to use chunked transfer encoding
|
319
331
|
# @return [String] formatted response headers
|
320
|
-
def format_headers(headers, body
|
332
|
+
def format_headers(headers, body)
|
321
333
|
status = headers[':status'] || (body ? Qeweney::Status::OK : Qeweney::Status::NO_CONTENT)
|
322
|
-
lines = format_status_line(body, status
|
334
|
+
lines = format_status_line(body, status)
|
323
335
|
headers.each do |k, v|
|
324
336
|
next if k =~ INTERNAL_HEADER_REGEXP
|
325
337
|
|
@@ -329,11 +341,11 @@ module TP2
|
|
329
341
|
lines
|
330
342
|
end
|
331
343
|
|
332
|
-
def format_status_line(body, status
|
344
|
+
def format_status_line(body, status)
|
333
345
|
if !body
|
334
346
|
empty_status_line(status)
|
335
347
|
else
|
336
|
-
with_body_status_line(status, body
|
348
|
+
with_body_status_line(status, body)
|
337
349
|
end
|
338
350
|
end
|
339
351
|
|
@@ -345,12 +357,8 @@ module TP2
|
|
345
357
|
end
|
346
358
|
end
|
347
359
|
|
348
|
-
def with_body_status_line(status, body
|
349
|
-
|
350
|
-
+"HTTP/1.1 #{status}\r\nTransfer-Encoding: chunked\r\n"
|
351
|
-
else
|
352
|
-
+"HTTP/1.1 #{status}\r\nContent-Length: #{body.bytesize}\r\n"
|
353
|
-
end
|
360
|
+
def with_body_status_line(status, body)
|
361
|
+
+"HTTP/1.1 #{status}\r\nTransfer-Encoding: chunked\r\n"
|
354
362
|
end
|
355
363
|
|
356
364
|
def collect_header_lines(lines, key, value)
|
data/lib/tp2/errors.rb
ADDED
@@ -0,0 +1,15 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module TP2
|
4
|
+
class ProtocolError < StandardError
|
5
|
+
def http_status
|
6
|
+
Qeweney::Status::BAD_REQUEST
|
7
|
+
end
|
8
|
+
end
|
9
|
+
|
10
|
+
class UnsupportedHTTPVersionError < ProtocolError
|
11
|
+
def http_status
|
12
|
+
Qeweney::Status::HTTP_VERSION_NOT_SUPPORTED
|
13
|
+
end
|
14
|
+
end
|
15
|
+
end
|
data/lib/tp2/server.rb
CHANGED
@@ -1,6 +1,6 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
-
require 'tp2/
|
3
|
+
require 'tp2/connection'
|
4
4
|
require 'tp2/request_extensions'
|
5
5
|
require 'tp2/rack_adapter'
|
6
6
|
|
@@ -100,7 +100,7 @@ module TP2
|
|
100
100
|
|
101
101
|
def accept_incoming(listen_fd)
|
102
102
|
@machine.accept_each(listen_fd) do |fd|
|
103
|
-
conn =
|
103
|
+
conn = Connection.new(@machine, fd, @opts, &@app)
|
104
104
|
f = @machine.spin(conn) do
|
105
105
|
it.run
|
106
106
|
ensure
|
data/lib/tp2/version.rb
CHANGED
data/test/helper.rb
CHANGED
@@ -36,6 +36,29 @@ module Kernel
|
|
36
36
|
end
|
37
37
|
end
|
38
38
|
|
39
|
+
class Qeweney::Request
|
40
|
+
def response_headers
|
41
|
+
adapter.headers
|
42
|
+
end
|
43
|
+
|
44
|
+
def response_status
|
45
|
+
adapter.status
|
46
|
+
end
|
47
|
+
|
48
|
+
def response_body
|
49
|
+
adapter.body
|
50
|
+
end
|
51
|
+
|
52
|
+
def response_json
|
53
|
+
raise if response_content_type != 'application/json'
|
54
|
+
JSON.parse(response_body, symbolize_names: true)
|
55
|
+
end
|
56
|
+
|
57
|
+
def response_content_type
|
58
|
+
response_headers['Content-Type']
|
59
|
+
end
|
60
|
+
end
|
61
|
+
|
39
62
|
module Minitest::Assertions
|
40
63
|
def assert_in_range exp_range, act
|
41
64
|
msg = message(msg) { "Expected #{mu_pp(act)} to be in range #{mu_pp(exp_range)}" }
|
@@ -2,7 +2,7 @@
|
|
2
2
|
|
3
3
|
require_relative './helper'
|
4
4
|
|
5
|
-
class
|
5
|
+
class ConnectionTest < Minitest::Test
|
6
6
|
def make_socket_pair
|
7
7
|
port = 10000 + rand(30000)
|
8
8
|
server_fd = @machine.socket(UM::AF_INET, UM::SOCK_STREAM, 0, 0)
|
@@ -25,7 +25,7 @@ class HTTP1ConnectionTest < Minitest::Test
|
|
25
25
|
@reqs = []
|
26
26
|
@hook = nil
|
27
27
|
@app = ->(req) { @hook&.call(req); @reqs << req }
|
28
|
-
@adapter = TP2::
|
28
|
+
@adapter = TP2::Connection.new(@machine, @s_fd, {}, &@app)
|
29
29
|
end
|
30
30
|
|
31
31
|
def teardown
|
@@ -48,8 +48,31 @@ class HTTP1ConnectionTest < Minitest::Test
|
|
48
48
|
res == 0 ? nil : buf
|
49
49
|
end
|
50
50
|
|
51
|
-
def
|
51
|
+
def test_http_unsupported_versions
|
52
|
+
write_http_request "GET / HTTP/0.9\r\n\r\n"
|
53
|
+
@adapter.serve_request
|
54
|
+
response = read_client_side
|
55
|
+
assert_equal "HTTP/1.1 505\r\nTransfer-Encoding: chunked\r\n\r\n1a\r\nHTTP version not supported\r\n0\r\n\r\n", response
|
56
|
+
|
57
|
+
setup
|
58
|
+
|
52
59
|
write_http_request "GET / HTTP/1.0\r\n\r\n"
|
60
|
+
@adapter.serve_request
|
61
|
+
response = read_client_side
|
62
|
+
assert_equal "HTTP/1.1 505\r\nTransfer-Encoding: chunked\r\n\r\n1a\r\nHTTP version not supported\r\n0\r\n\r\n", response
|
63
|
+
|
64
|
+
setup
|
65
|
+
|
66
|
+
@hook = ->(req) { req.respond('hi') }
|
67
|
+
write_http_request "GET / HTTP/1.1\r\n\r\n"
|
68
|
+
@adapter.serve_request
|
69
|
+
@machine.close(@s_fd)
|
70
|
+
response = read_client_side
|
71
|
+
assert_equal "HTTP/1.1 200\r\nTransfer-Encoding: chunked\r\n\r\n2\r\nhi\r\n0\r\n\r\n", response
|
72
|
+
end
|
73
|
+
|
74
|
+
def test_basic_request_parsing
|
75
|
+
write_http_request "GET / HTTP/1.1\r\n\r\n"
|
53
76
|
|
54
77
|
@adapter.serve_request
|
55
78
|
assert_equal 1, @reqs.size
|
@@ -58,7 +81,7 @@ class HTTP1ConnectionTest < Minitest::Test
|
|
58
81
|
assert_equal({
|
59
82
|
':method' => 'get',
|
60
83
|
':path' => '/',
|
61
|
-
':protocol' => 'http/1.
|
84
|
+
':protocol' => 'http/1.1'
|
62
85
|
}, headers)
|
63
86
|
end
|
64
87
|
|
@@ -196,34 +219,6 @@ class HTTP1ConnectionTest < Minitest::Test
|
|
196
219
|
assert_equal '123456789abcdefghijklmnopqrstuv', body
|
197
220
|
end
|
198
221
|
|
199
|
-
def test_body_to_eof
|
200
|
-
skip
|
201
|
-
|
202
|
-
write_http_request <<~HTTP.crlf_lines
|
203
|
-
POST /foo HTTP/1.1
|
204
|
-
Server: foo.com
|
205
|
-
|
206
|
-
barbaz
|
207
|
-
HTTP
|
208
|
-
|
209
|
-
@bodies = []
|
210
|
-
@hook = ->(req) { @bodies << req.read }
|
211
|
-
|
212
|
-
@adapter.run
|
213
|
-
assert_equal 1, @reqs.size
|
214
|
-
|
215
|
-
req0 = @reqs.shift
|
216
|
-
headers = req0.headers
|
217
|
-
assert_equal({
|
218
|
-
':method' => 'post',
|
219
|
-
':path' => '/foo',
|
220
|
-
':protocol' => 'http/1.1',
|
221
|
-
'server' => 'foo.com'
|
222
|
-
}, headers)
|
223
|
-
body = @bodies.shift
|
224
|
-
assert_equal 'barbaz', body
|
225
|
-
end
|
226
|
-
|
227
222
|
def test_each_chunk
|
228
223
|
write_http_request <<~HTTP.crlf_lines
|
229
224
|
POST /foo HTTP/1.1
|
@@ -278,30 +273,12 @@ class HTTP1ConnectionTest < Minitest::Test
|
|
278
273
|
assert_equal ['123456789abcdefghijklmnopqrstuv'], chunks
|
279
274
|
end
|
280
275
|
|
281
|
-
def test_that_server_uses_content_length_in_http_1_0
|
282
|
-
@hook = ->(req) {
|
283
|
-
req.respond('Hello, world!', {})
|
284
|
-
}
|
285
|
-
|
286
|
-
write_http_request "GET / HTTP/1.0\r\n\r\n"
|
287
|
-
@adapter.run
|
288
|
-
response = read_client_side
|
289
|
-
|
290
|
-
expected = <<~HTTP.crlf_lines
|
291
|
-
HTTP/1.1 200
|
292
|
-
Content-Length: 13
|
293
|
-
|
294
|
-
Hello, world!
|
295
|
-
HTTP
|
296
|
-
assert_equal(expected, response)
|
297
|
-
end
|
298
|
-
|
299
276
|
def test_204_status_on_empty_response
|
300
277
|
@hook = ->(req) {
|
301
278
|
req.respond(nil, {})
|
302
279
|
}
|
303
280
|
|
304
|
-
write_http_request "GET / HTTP/1.
|
281
|
+
write_http_request "GET / HTTP/1.1\r\n\r\n"
|
305
282
|
@adapter.run
|
306
283
|
response = read_client_side
|
307
284
|
|
@@ -325,46 +302,29 @@ class HTTP1ConnectionTest < Minitest::Test
|
|
325
302
|
@adapter.run
|
326
303
|
|
327
304
|
response = read_client_side
|
328
|
-
expected =
|
329
|
-
HTTP/1.1 200
|
330
|
-
Content-Length: 13
|
331
|
-
|
332
|
-
Hello, world!
|
333
|
-
HTTP
|
305
|
+
expected = "HTTP/1.1 200\r\nTransfer-Encoding: chunked\r\n\r\nd\r\nHello, world!\r\n0\r\n\r\n"
|
334
306
|
assert_equal(expected, response)
|
335
307
|
end
|
336
308
|
|
337
|
-
def
|
309
|
+
def test_that_server_maintains_connection_if_no_connection_close_header
|
338
310
|
@hook = ->(req) {
|
339
311
|
req.respond('Hi', {})
|
340
312
|
}
|
341
313
|
|
342
|
-
write_http_request "GET / HTTP/1.
|
314
|
+
write_http_request "GET / HTTP/1.1\r\nConnection: close\r\n\r\n", false
|
343
315
|
res = @adapter.serve_request
|
344
|
-
assert_equal
|
316
|
+
assert_equal false, res
|
345
317
|
|
346
318
|
response = read_client_side
|
347
|
-
assert_equal("HTTP/1.1 200\r\
|
319
|
+
assert_equal("HTTP/1.1 200\r\nTransfer-Encoding: chunked\r\n\r\n2\r\nHi\r\n0\r\n\r\n", response)
|
348
320
|
|
349
321
|
write_http_request "GET / HTTP/1.1\r\n\r\n", false
|
350
322
|
res = @adapter.serve_request
|
351
323
|
assert_equal true, res
|
352
324
|
|
353
325
|
response = read_client_side
|
354
|
-
expected =
|
355
|
-
HTTP/1.1 200
|
356
|
-
Content-Length: 2
|
357
|
-
|
358
|
-
Hi
|
359
|
-
HTTP
|
326
|
+
expected = "HTTP/1.1 200\r\nTransfer-Encoding: chunked\r\n\r\n2\r\nHi\r\n0\r\n\r\n"
|
360
327
|
assert_equal(expected, response)
|
361
|
-
|
362
|
-
write_http_request "GET / HTTP/1.0\r\n\r\n"
|
363
|
-
res = @adapter.serve_request
|
364
|
-
assert_equal false, !!res
|
365
|
-
|
366
|
-
response = read_client_side
|
367
|
-
assert_equal("HTTP/1.1 200\r\nContent-Length: 2\r\n\r\nHi", response)
|
368
328
|
end
|
369
329
|
|
370
330
|
def test_pipelining_client
|
@@ -381,15 +341,8 @@ class HTTP1ConnectionTest < Minitest::Test
|
|
381
341
|
@adapter.run
|
382
342
|
response = read_client_side
|
383
343
|
|
384
|
-
expected =
|
385
|
-
|
386
|
-
Content-Length: 13
|
387
|
-
|
388
|
-
Hello, world!HTTP/1.1 200
|
389
|
-
Content-Length: 14
|
390
|
-
|
391
|
-
Hello, foobar!
|
392
|
-
HTTP
|
344
|
+
expected = "HTTP/1.1 200\r\nTransfer-Encoding: chunked\r\n\r\nd\r\nHello, world!\r\n0\r\n\r\n" +
|
345
|
+
"HTTP/1.1 200\r\nTransfer-Encoding: chunked\r\n\r\ne\r\nHello, foobar!\r\n0\r\n\r\n"
|
393
346
|
assert_equal(expected, response)
|
394
347
|
end
|
395
348
|
|
@@ -538,7 +491,7 @@ class HTTP1ConnectionTest < Minitest::Test
|
|
538
491
|
|
539
492
|
content = IO.read(__FILE__)
|
540
493
|
file_size = content.bytesize
|
541
|
-
expected = "HTTP/1.1 200\r\
|
494
|
+
expected = "HTTP/1.1 200\r\nTransfer-Encoding: chunked\r\n\r\n6\r\nfoobar\r\n0\r\n\r\n"
|
542
495
|
|
543
496
|
assert_equal expected, response
|
544
497
|
end
|
data/test/test_server.rb
CHANGED
@@ -65,14 +65,25 @@ class ServerTest < Minitest::Test
|
|
65
65
|
res == 0 ? nil : buf
|
66
66
|
end
|
67
67
|
|
68
|
-
def
|
68
|
+
def test_http_1_0_response
|
69
69
|
@app = ->(req) {
|
70
70
|
req.respond('Hello, world!', {})
|
71
71
|
}
|
72
72
|
|
73
73
|
write_http_request "GET / HTTP/1.0\r\n\r\n"
|
74
74
|
response = read_client_side
|
75
|
-
expected = "HTTP/1.1
|
75
|
+
expected = "HTTP/1.1 505\r\nTransfer-Encoding: chunked\r\n\r\n1a\r\nHTTP version not supported\r\n0\r\n\r\n"
|
76
|
+
assert_equal(expected, response)
|
77
|
+
end
|
78
|
+
|
79
|
+
def test_basic_app_response
|
80
|
+
@app = ->(req) {
|
81
|
+
req.respond('Hello, world!', {})
|
82
|
+
}
|
83
|
+
|
84
|
+
write_http_request "GET / HTTP/1.1\r\n\r\n"
|
85
|
+
response = read_client_side
|
86
|
+
expected = "HTTP/1.1 200\r\nTransfer-Encoding: chunked\r\n\r\nd\r\nHello, world!\r\n0\r\n\r\n"
|
76
87
|
assert_equal(expected, response)
|
77
88
|
end
|
78
89
|
|
@@ -84,7 +95,8 @@ class ServerTest < Minitest::Test
|
|
84
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"
|
85
96
|
|
86
97
|
response = read_client_side
|
87
|
-
expected = "HTTP/1.1 200\r\
|
98
|
+
expected = "HTTP/1.1 200\r\nTransfer-Encoding: chunked\r\n\r\nb\r\nmethod: get\r\n0\r\n\r\n" +
|
99
|
+
"HTTP/1.1 200\r\nTransfer-Encoding: chunked\r\n\r\ne\r\nmethod: schmet\r\n0\r\n\r\n"
|
88
100
|
assert_equal(expected, response)
|
89
101
|
end
|
90
102
|
|
@@ -104,7 +116,7 @@ class ServerTest < Minitest::Test
|
|
104
116
|
@machine.snooze
|
105
117
|
|
106
118
|
response = read_client_side
|
107
|
-
expected = "HTTP/1.1 200\r\
|
119
|
+
expected = "HTTP/1.1 200\r\nTransfer-Encoding: chunked\r\n\r\nb\r\nTerminated!\r\n0\r\n\r\n"
|
108
120
|
assert_equal(expected, response)
|
109
121
|
end
|
110
122
|
|
@@ -143,7 +155,7 @@ class ServerTest < Minitest::Test
|
|
143
155
|
':protocol' => 'http/1.1',
|
144
156
|
'server' => 'foo.com',
|
145
157
|
'content-length' => '3',
|
146
|
-
':tx' =>
|
158
|
+
':tx' => 56,
|
147
159
|
}, headers)
|
148
160
|
body = @bodies.shift
|
149
161
|
assert_equal 'abc', body
|
@@ -156,7 +168,7 @@ class ServerTest < Minitest::Test
|
|
156
168
|
':protocol' => 'http/1.1',
|
157
169
|
'server' => 'bar.com',
|
158
170
|
'content-length' => '6',
|
159
|
-
':tx' =>
|
171
|
+
':tx' => 59,
|
160
172
|
}, headers)
|
161
173
|
body = @bodies.shift
|
162
174
|
assert_equal 'defghi', body
|
data/tp2-logo.png
ADDED
Binary file
|
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.18'
|
24
|
+
s.add_dependency 'qeweney', '~> 0.22'
|
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.16'
|
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.18'
|
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.18'
|
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.22'
|
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.22'
|
40
40
|
- !ruby/object:Gem::Dependency
|
41
41
|
name: rack
|
42
42
|
requirement: !ruby/object:Gem::Requirement
|
@@ -71,7 +71,8 @@ files:
|
|
71
71
|
- examples/rack.ru
|
72
72
|
- examples/simple.rb
|
73
73
|
- lib/tp2.rb
|
74
|
-
- lib/tp2/
|
74
|
+
- lib/tp2/connection.rb
|
75
|
+
- lib/tp2/errors.rb
|
75
76
|
- lib/tp2/logger.rb
|
76
77
|
- lib/tp2/rack_adapter.rb
|
77
78
|
- lib/tp2/request_extensions.rb
|
@@ -79,8 +80,9 @@ files:
|
|
79
80
|
- lib/tp2/version.rb
|
80
81
|
- test/helper.rb
|
81
82
|
- test/run.rb
|
82
|
-
- test/
|
83
|
+
- test/test_connection.rb
|
83
84
|
- test/test_server.rb
|
85
|
+
- tp2-logo.png
|
84
86
|
- tp2.gemspec
|
85
87
|
homepage: https://github.com/noteflakes/tp2
|
86
88
|
licenses:
|
@@ -107,7 +109,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
107
109
|
- !ruby/object:Gem::Version
|
108
110
|
version: '0'
|
109
111
|
requirements: []
|
110
|
-
rubygems_version: 3.
|
112
|
+
rubygems_version: 3.7.0.dev
|
111
113
|
specification_version: 4
|
112
114
|
summary: Experimental HTTP/1 server for UringMachine
|
113
115
|
test_files: []
|