tipi 0.42 → 0.43
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 +5 -0
- data/Gemfile +1 -1
- data/Gemfile.lock +14 -19
- data/benchmarks/bm_http1_parser.rb +44 -20
- data/df/server.rb +2 -0
- data/df/server_utils.rb +11 -14
- data/lib/tipi/digital_fabric/service.rb +4 -0
- data/lib/tipi/http1_adapter.rb +5 -4
- data/lib/tipi/version.rb +1 -1
- data/test/helper.rb +0 -1
- data/test/test_request.rb +1 -1
- data/tipi.gemspec +3 -5
- metadata +26 -47
- data/ext/tipi/extconf.rb +0 -13
- data/ext/tipi/http1_parser.c +0 -823
- data/ext/tipi/http1_parser.h +0 -18
- data/ext/tipi/tipi_ext.c +0 -5
- data/security/http1.rb +0 -12
- data/test/test_http1_parser.rb +0 -586
data/ext/tipi/http1_parser.h
DELETED
@@ -1,18 +0,0 @@
|
|
1
|
-
#ifndef HTTP1_PARSER_H
|
2
|
-
#define HTTP1_PARSER_H
|
3
|
-
|
4
|
-
#include "ruby.h"
|
5
|
-
|
6
|
-
// debugging
|
7
|
-
#define OBJ_ID(obj) (NUM2LONG(rb_funcall(obj, rb_intern("object_id"), 0)))
|
8
|
-
#define INSPECT(str, obj) { printf(str); VALUE s = rb_funcall(obj, rb_intern("inspect"), 0); printf(": %s\n", StringValueCStr(s)); }
|
9
|
-
#define TRACE_CALLER() { VALUE c = rb_funcall(rb_mKernel, rb_intern("caller"), 0); INSPECT("caller: ", c); }
|
10
|
-
#define TRACE_C_STACK() { \
|
11
|
-
void *entries[10]; \
|
12
|
-
size_t size = backtrace(entries, 10); \
|
13
|
-
char **strings = backtrace_symbols(entries, size); \
|
14
|
-
for (unsigned long i = 0; i < size; i++) printf("%s\n", strings[i]); \
|
15
|
-
free(strings); \
|
16
|
-
}
|
17
|
-
|
18
|
-
#endif /* HTTP1_PARSER_H */
|
data/ext/tipi/tipi_ext.c
DELETED
data/security/http1.rb
DELETED
@@ -1,12 +0,0 @@
|
|
1
|
-
# frozen_string_literal: true
|
2
|
-
|
3
|
-
module Tipi
|
4
|
-
HTTP1_LIMITS = {
|
5
|
-
max_method_length: 16,
|
6
|
-
max_path_length: 4096,
|
7
|
-
max_header_key_length: 128,
|
8
|
-
max_header_value_length: 2048,
|
9
|
-
max_header_count: 256,
|
10
|
-
max_chunked_encoding_chunk_size_length: 16,
|
11
|
-
}
|
12
|
-
end
|
data/test/test_http1_parser.rb
DELETED
@@ -1,586 +0,0 @@
|
|
1
|
-
# frozen_string_literal: true
|
2
|
-
|
3
|
-
require_relative 'helper'
|
4
|
-
require 'tipi_ext'
|
5
|
-
require_relative '../security/http1.rb'
|
6
|
-
|
7
|
-
class HTTP1ParserTest < MiniTest::Test
|
8
|
-
Error = Tipi::HTTP1Parser::Error
|
9
|
-
|
10
|
-
def setup
|
11
|
-
super
|
12
|
-
@i, @o = IO.pipe
|
13
|
-
@parser = Tipi::HTTP1Parser.new(@i)
|
14
|
-
end
|
15
|
-
alias_method :reset_parser, :setup
|
16
|
-
|
17
|
-
def test_request_line
|
18
|
-
msg = "GET / HTTP/1.1\r\n\r\n"
|
19
|
-
@o << msg
|
20
|
-
headers = @parser.parse_headers
|
21
|
-
|
22
|
-
assert_equal(
|
23
|
-
{
|
24
|
-
':method' => 'get',
|
25
|
-
':path' => '/',
|
26
|
-
':protocol' => 'http/1.1',
|
27
|
-
':rx' => msg.bytesize
|
28
|
-
},
|
29
|
-
headers
|
30
|
-
)
|
31
|
-
end
|
32
|
-
|
33
|
-
def test_request_line_whitespace
|
34
|
-
msg = "GET / HTTP/1.1\r\n\r\n"
|
35
|
-
@o << msg
|
36
|
-
headers = @parser.parse_headers
|
37
|
-
|
38
|
-
assert_equal(
|
39
|
-
{
|
40
|
-
':method' => 'get',
|
41
|
-
':path' => '/',
|
42
|
-
':protocol' => 'http/1.1',
|
43
|
-
':rx' => msg.bytesize
|
44
|
-
},
|
45
|
-
headers
|
46
|
-
)
|
47
|
-
end
|
48
|
-
|
49
|
-
def test_eof
|
50
|
-
@o << "GET / HTTP/1.1"
|
51
|
-
@o.close
|
52
|
-
|
53
|
-
assert_nil @parser.parse_headers
|
54
|
-
end
|
55
|
-
|
56
|
-
def test_method_case
|
57
|
-
@o << "GET / HTTP/1.1\r\n\r\n"
|
58
|
-
headers = @parser.parse_headers
|
59
|
-
assert_equal 'get', headers[':method']
|
60
|
-
|
61
|
-
reset_parser
|
62
|
-
@o << "post / HTTP/1.1\r\n\r\n"
|
63
|
-
headers = @parser.parse_headers
|
64
|
-
assert_equal 'post', headers[':method']
|
65
|
-
|
66
|
-
reset_parser
|
67
|
-
@o << "PoST / HTTP/1.1\r\n\r\n"
|
68
|
-
headers = @parser.parse_headers
|
69
|
-
assert_equal 'post', headers[':method']
|
70
|
-
end
|
71
|
-
|
72
|
-
def test_bad_method
|
73
|
-
@o << " / HTTP/1.1\r\n\r\n"
|
74
|
-
@o.close
|
75
|
-
|
76
|
-
assert_raises(Error) { @parser.parse_headers }
|
77
|
-
|
78
|
-
max_length = Tipi::HTTP1_LIMITS[:max_method_length]
|
79
|
-
|
80
|
-
reset_parser
|
81
|
-
@o << "#{'a' * max_length} / HTTP/1.1\r\n\r\n"
|
82
|
-
assert_equal 'a' * max_length, @parser.parse_headers[':method']
|
83
|
-
|
84
|
-
reset_parser
|
85
|
-
@o << "#{'a' * (max_length + 1)} / HTTP/1.1\r\n\r\n"
|
86
|
-
assert_raises(Error) { @parser.parse_headers }
|
87
|
-
end
|
88
|
-
|
89
|
-
def test_path_characters
|
90
|
-
@o << "GET /äBçDé¤23~{@€ HTTP/1.1\r\n\r\n"
|
91
|
-
headers = @parser.parse_headers
|
92
|
-
assert_equal '/äBçDé¤23~{@€', headers[':path']
|
93
|
-
end
|
94
|
-
|
95
|
-
def test_bad_path
|
96
|
-
@o << "GET HTTP/1.1\r\n\r\n"
|
97
|
-
assert_raises(Error) { @parser.parse_headers }
|
98
|
-
|
99
|
-
max_length = Tipi::HTTP1_LIMITS[:max_path_length]
|
100
|
-
|
101
|
-
reset_parser
|
102
|
-
@o << "get #{'a' * max_length} HTTP/1.1\r\n\r\n"
|
103
|
-
assert_equal 'a' * max_length, @parser.parse_headers[':path']
|
104
|
-
|
105
|
-
reset_parser
|
106
|
-
@o << "get #{'a' * (max_length + 1)} HTTP/1.1\r\n\r\n"
|
107
|
-
assert_raises(Error) { @parser.parse_headers }
|
108
|
-
end
|
109
|
-
|
110
|
-
def test_protocol
|
111
|
-
@o << "GET / http/1\r\n\r\n"
|
112
|
-
headers = @parser.parse_headers
|
113
|
-
assert_equal 'http/1', headers[':protocol']
|
114
|
-
|
115
|
-
reset_parser
|
116
|
-
@o << "GET / HTTP/1\r\n\r\n"
|
117
|
-
headers = @parser.parse_headers
|
118
|
-
assert_equal 'http/1', headers[':protocol']
|
119
|
-
|
120
|
-
reset_parser
|
121
|
-
@o << "GET / HTTP/1.0\r\n\r\n"
|
122
|
-
headers = @parser.parse_headers
|
123
|
-
assert_equal 'http/1.0', headers[':protocol']
|
124
|
-
|
125
|
-
@o << "GET / HttP/1.1\r\n\r\n"
|
126
|
-
headers = @parser.parse_headers
|
127
|
-
assert_equal 'http/1.1', headers[':protocol']
|
128
|
-
end
|
129
|
-
|
130
|
-
def test_bad_protocol
|
131
|
-
@o << "GET / blah\r\n\r\n"
|
132
|
-
assert_raises(Error) { @parser.parse_headers }
|
133
|
-
|
134
|
-
reset_parser
|
135
|
-
@o << "GET / http\r\n\r\n"
|
136
|
-
assert_raises(Error) { @parser.parse_headers }
|
137
|
-
|
138
|
-
reset_parser
|
139
|
-
@o << "GET / http/2\r\n\r\n"
|
140
|
-
assert_raises(Error) { @parser.parse_headers }
|
141
|
-
|
142
|
-
reset_parser
|
143
|
-
@o << "GET / http/1.\r\n\r\n"
|
144
|
-
assert_raises(Error) { @parser.parse_headers }
|
145
|
-
|
146
|
-
reset_parser
|
147
|
-
@o << "GET / http/a.1\r\n\r\n"
|
148
|
-
assert_raises(Error) { @parser.parse_headers }
|
149
|
-
|
150
|
-
reset_parser
|
151
|
-
@o << "GET / http/1.1.1\r\n\r\n"
|
152
|
-
assert_raises(Error) { @parser.parse_headers }
|
153
|
-
end
|
154
|
-
|
155
|
-
def test_headers_eof
|
156
|
-
@o << "GET / HTTP/1.1\r\na"
|
157
|
-
@o.close
|
158
|
-
|
159
|
-
assert_nil @parser.parse_headers
|
160
|
-
|
161
|
-
reset_parser
|
162
|
-
@o << "GET / HTTP/1.1\r\na:"
|
163
|
-
@o.close
|
164
|
-
|
165
|
-
assert_nil @parser.parse_headers
|
166
|
-
|
167
|
-
reset_parser
|
168
|
-
@o << "GET / HTTP/1.1\r\na: "
|
169
|
-
@o.close
|
170
|
-
|
171
|
-
assert_nil @parser.parse_headers
|
172
|
-
end
|
173
|
-
|
174
|
-
def test_headers
|
175
|
-
@o << "GET / HTTP/1.1\r\nFoo: Bar\r\n\r\n"
|
176
|
-
headers = @parser.parse_headers
|
177
|
-
assert_equal [':method', ':path', ':protocol', 'foo', ':rx'], headers.keys
|
178
|
-
assert_equal 'Bar', headers['foo']
|
179
|
-
|
180
|
-
reset_parser
|
181
|
-
@o << "GET / HTTP/1.1\r\nFOO: baR\r\n\r\n"
|
182
|
-
headers = @parser.parse_headers
|
183
|
-
assert_equal 'baR', headers['foo']
|
184
|
-
|
185
|
-
reset_parser
|
186
|
-
@o << "GET / HTTP/1.1\r\na: bbb\r\nc: ddd\r\n\r\n"
|
187
|
-
headers = @parser.parse_headers
|
188
|
-
assert_equal 'bbb', headers['a']
|
189
|
-
assert_equal 'ddd', headers['c']
|
190
|
-
end
|
191
|
-
|
192
|
-
def test_headers_multiple_values
|
193
|
-
@o << "GET / HTTP/1.1\r\nFoo: Bar\r\nfoo: baz\r\n\r\n"
|
194
|
-
headers = @parser.parse_headers
|
195
|
-
assert_equal ['Bar', 'baz'], headers['foo']
|
196
|
-
end
|
197
|
-
|
198
|
-
def test_bad_headers
|
199
|
-
@o << "GET / http/1.1\r\n a: b\r\n\r\n"
|
200
|
-
assert_raises(Error) { @parser.parse_headers }
|
201
|
-
|
202
|
-
reset_parser
|
203
|
-
@o << "GET / http/1.1\r\na b\r\n\r\n"
|
204
|
-
assert_raises(Error) { @parser.parse_headers }
|
205
|
-
|
206
|
-
max_key_length = Tipi::HTTP1_LIMITS[:max_header_key_length]
|
207
|
-
|
208
|
-
reset_parser
|
209
|
-
@o << "GET / http/1.1\r\n#{'a' * max_key_length}: b\r\n\r\n"
|
210
|
-
headers = @parser.parse_headers
|
211
|
-
assert_equal 'b', headers['a' * max_key_length]
|
212
|
-
|
213
|
-
reset_parser
|
214
|
-
@o << "GET / http/1.1\r\n#{'a' * (max_key_length + 1)}: b\r\n\r\n"
|
215
|
-
assert_raises(Error) { @parser.parse_headers }
|
216
|
-
|
217
|
-
max_value_length = Tipi::HTTP1_LIMITS[:max_header_value_length]
|
218
|
-
|
219
|
-
reset_parser
|
220
|
-
@o << "GET / http/1.1\r\nfoo: #{'a' * max_value_length}\r\n\r\n"
|
221
|
-
headers = @parser.parse_headers
|
222
|
-
assert_equal 'a' * max_value_length, headers['foo']
|
223
|
-
|
224
|
-
reset_parser
|
225
|
-
@o << "GET / http/1.1\r\nfoo: #{'a' * (max_value_length + 1)}\r\n\r\n"
|
226
|
-
assert_raises(Error) { @parser.parse_headers }
|
227
|
-
|
228
|
-
max_header_count = Tipi::HTTP1_LIMITS[:max_header_count]
|
229
|
-
|
230
|
-
reset_parser
|
231
|
-
hdrs = (1..max_header_count).map { |i| "foo#{i}: bar\r\n" }.join
|
232
|
-
@o << "GET / http/1.1\r\n#{hdrs}\r\n"
|
233
|
-
headers = @parser.parse_headers
|
234
|
-
assert_equal (max_header_count + 4), headers.size
|
235
|
-
|
236
|
-
reset_parser
|
237
|
-
hdrs = (1..(max_header_count + 1)).map { |i| "foo#{i}: bar\r\n" }.join
|
238
|
-
@o << "GET / http/1.1\r\n#{hdrs}\r\n"
|
239
|
-
assert_raises(Error) { @parser.parse_headers }
|
240
|
-
end
|
241
|
-
|
242
|
-
def test_request_without_cr
|
243
|
-
msg = "GET /foo HTTP/1.1\nBar: baz\n\n"
|
244
|
-
@o << msg
|
245
|
-
headers = @parser.parse_headers
|
246
|
-
assert_equal({
|
247
|
-
':method' => 'get',
|
248
|
-
':path' => '/foo',
|
249
|
-
':protocol' => 'http/1.1',
|
250
|
-
'bar' => 'baz',
|
251
|
-
':rx' => msg.bytesize
|
252
|
-
}, headers)
|
253
|
-
end
|
254
|
-
|
255
|
-
def test_read_body_with_content_length
|
256
|
-
10.times do
|
257
|
-
data = ' ' * rand(20..60000)
|
258
|
-
msg = "POST /foo HTTP/1.1\r\nContent-Length: #{data.bytesize}\r\n\r\n#{data}"
|
259
|
-
spin do
|
260
|
-
@o << msg
|
261
|
-
end
|
262
|
-
headers = @parser.parse_headers
|
263
|
-
assert_equal data.bytesize.to_s, headers['content-length']
|
264
|
-
|
265
|
-
body = @parser.read_body
|
266
|
-
assert_equal data, body
|
267
|
-
assert_equal msg.bytesize, headers[':rx']
|
268
|
-
end
|
269
|
-
end
|
270
|
-
|
271
|
-
def test_read_body_chunk_with_content_length
|
272
|
-
data = 'abc' * 20000
|
273
|
-
msg = "POST /foo HTTP/1.1\r\nContent-Length: #{data.bytesize}\r\n\r\n#{data}"
|
274
|
-
spin { @o << msg }
|
275
|
-
headers = @parser.parse_headers
|
276
|
-
assert_equal data.bytesize.to_s, headers['content-length']
|
277
|
-
|
278
|
-
buf = +''
|
279
|
-
count = 0
|
280
|
-
while (chunk = @parser.read_body_chunk(false))
|
281
|
-
count += 1
|
282
|
-
buf += chunk
|
283
|
-
end
|
284
|
-
assert_equal data.bytesize, data.bytesize
|
285
|
-
assert_equal data, buf
|
286
|
-
assert_in_range 1..3, count
|
287
|
-
assert_equal msg.bytesize, headers[':rx']
|
288
|
-
end
|
289
|
-
|
290
|
-
def test_read_body_with_content_length_incomplete
|
291
|
-
data = ' ' * rand(20..60000)
|
292
|
-
spin do
|
293
|
-
@o << "POST /foo HTTP/1.1\r\nContent-Length: #{data.bytesize + 1}\r\n\r\n#{data}"
|
294
|
-
@o.close # !!! otherwise the parser will keep waiting
|
295
|
-
end
|
296
|
-
headers = @parser.parse_headers
|
297
|
-
|
298
|
-
assert_raises(Tipi::HTTP1Parser::Error) { @parser.read_body }
|
299
|
-
end
|
300
|
-
|
301
|
-
def test_read_body_chunk_with_content_length_incomplete
|
302
|
-
data = 'abc' * 50
|
303
|
-
@o << "POST /foo HTTP/1.1\r\nContent-Length: #{data.bytesize + 1}\r\n\r\n#{data}"
|
304
|
-
@o.close
|
305
|
-
headers = @parser.parse_headers
|
306
|
-
|
307
|
-
assert_raises(Tipi::HTTP1Parser::Error) { @parser.read_body_chunk(false) }
|
308
|
-
end
|
309
|
-
|
310
|
-
def test_read_body_with_chunked_encoding
|
311
|
-
chunks = []
|
312
|
-
total_sent = 0
|
313
|
-
spin do
|
314
|
-
msg = "POST /foo HTTP/1.1\r\nTransfer-Encoding: chunked\r\n\r\n"
|
315
|
-
@o << msg
|
316
|
-
total_sent += msg.bytesize
|
317
|
-
rand(8..16).times do |i|
|
318
|
-
chunk = i.to_s * rand(200..360000)
|
319
|
-
msg = "#{chunk.bytesize.to_s(16)}\r\n#{chunk}\r\n"
|
320
|
-
@o << msg
|
321
|
-
chunks << chunk
|
322
|
-
total_sent += msg.bytesize
|
323
|
-
end
|
324
|
-
msg = "0\r\n\r\n"
|
325
|
-
@o << msg
|
326
|
-
total_sent += msg.bytesize
|
327
|
-
end
|
328
|
-
headers = @parser.parse_headers
|
329
|
-
assert_equal 'chunked', headers['transfer-encoding']
|
330
|
-
|
331
|
-
body = @parser.read_body
|
332
|
-
assert_equal chunks.join, body
|
333
|
-
assert_equal total_sent, headers[':rx']
|
334
|
-
end
|
335
|
-
|
336
|
-
def test_read_body_chunk_with_chunked_encoding
|
337
|
-
chunks = []
|
338
|
-
total_sent = 0
|
339
|
-
spin do
|
340
|
-
msg = "POST /foo HTTP/1.1\r\nTransfer-Encoding: chunked\r\n\r\n"
|
341
|
-
@o << msg
|
342
|
-
total_sent += msg.bytesize
|
343
|
-
rand(8..16).times do |i|
|
344
|
-
chunk = i.to_s * rand(40000..360000)
|
345
|
-
msg = "#{chunk.bytesize.to_s(16)}\r\n#{chunk}\r\n"
|
346
|
-
@o << msg
|
347
|
-
total_sent += msg.bytesize
|
348
|
-
chunks << chunk
|
349
|
-
end
|
350
|
-
msg = "0\r\n\r\n"
|
351
|
-
@o << msg
|
352
|
-
total_sent += msg.bytesize
|
353
|
-
end
|
354
|
-
headers = @parser.parse_headers
|
355
|
-
assert_equal 'chunked', headers['transfer-encoding']
|
356
|
-
|
357
|
-
received = []
|
358
|
-
while (chunk = @parser.read_body_chunk(false))
|
359
|
-
received << chunk
|
360
|
-
end
|
361
|
-
assert_equal chunks, received
|
362
|
-
assert_equal total_sent, headers[':rx']
|
363
|
-
end
|
364
|
-
|
365
|
-
def test_read_body_with_chunked_encoding_malformed
|
366
|
-
spin do
|
367
|
-
@o << "POST /foo HTTP/1.1\r\nTransfer-Encoding: chunked\r\n\r\n"
|
368
|
-
chunk = ' '.to_s * rand(40000..360000)
|
369
|
-
@o << "#{chunk.bytesize.to_s(16)}\r\n#{chunk}\r\n3"
|
370
|
-
@o << "0\r\n\r\n"
|
371
|
-
@o.close
|
372
|
-
end
|
373
|
-
headers = @parser.parse_headers
|
374
|
-
assert_raises(Tipi::HTTP1Parser::Error) { @parser.read_body }
|
375
|
-
|
376
|
-
reset_parser
|
377
|
-
# missing last empty chunk
|
378
|
-
spin do
|
379
|
-
@o << "POST /foo HTTP/1.1\r\nTransfer-Encoding: chunked\r\n\r\n"
|
380
|
-
chunk = ' '.to_s * rand(40000..360000)
|
381
|
-
@o << "#{chunk.bytesize.to_s(16)}\r\n#{chunk}\r\n"
|
382
|
-
@o.close
|
383
|
-
end
|
384
|
-
headers = @parser.parse_headers
|
385
|
-
assert_raises(Tipi::HTTP1Parser::Error) { @parser.read_body }
|
386
|
-
|
387
|
-
reset_parser
|
388
|
-
# bad chunk size
|
389
|
-
spin do
|
390
|
-
@o << "POST /foo HTTP/1.1\r\nTransfer-Encoding: chunked\r\n\r\n"
|
391
|
-
chunk = ' '.to_s * rand(40000..360000)
|
392
|
-
@o << "-#{chunk.bytesize.to_s(16)}\r\n#{chunk}\r\n"
|
393
|
-
@o.close
|
394
|
-
end
|
395
|
-
headers = @parser.parse_headers
|
396
|
-
assert_raises(Tipi::HTTP1Parser::Error) { @parser.read_body }
|
397
|
-
end
|
398
|
-
|
399
|
-
def test_read_body_chunk_with_chunked_encoding_malformed
|
400
|
-
chunk = nil
|
401
|
-
spin do
|
402
|
-
@o << "POST /foo HTTP/1.1\r\nTransfer-Encoding: chunked\r\n\r\n"
|
403
|
-
chunk = ' ' * rand(40000..360000)
|
404
|
-
@o << "#{chunk.bytesize.to_s(16)}\r\n#{chunk}\r\n3"
|
405
|
-
@o << "0\r\n\r\n"
|
406
|
-
@o.close
|
407
|
-
end
|
408
|
-
headers = @parser.parse_headers
|
409
|
-
read = @parser.read_body_chunk(false)
|
410
|
-
assert_equal chunk, read
|
411
|
-
assert_raises(Tipi::HTTP1Parser::Error) { @parser.read_body_chunk(false) }
|
412
|
-
|
413
|
-
reset_parser
|
414
|
-
Fiber.current.shutdown_all_children
|
415
|
-
# missing last empty chunk
|
416
|
-
chunk = nil
|
417
|
-
spin do
|
418
|
-
@o << "POST /foo HTTP/1.1\r\nTransfer-Encoding: chunked\r\n\r\n"
|
419
|
-
chunk = ' '.to_s * rand(20..1600)
|
420
|
-
@o << "#{chunk.bytesize.to_s(16)}\r\n#{chunk}\r\n"
|
421
|
-
@o.close
|
422
|
-
end
|
423
|
-
headers = @parser.parse_headers
|
424
|
-
read = @parser.read_body_chunk(false)
|
425
|
-
assert_equal chunk, read
|
426
|
-
assert_raises(Tipi::HTTP1Parser::Error) { @parser.read_body_chunk(false) }
|
427
|
-
|
428
|
-
reset_parser
|
429
|
-
Fiber.current.shutdown_all_children
|
430
|
-
# bad chunk size
|
431
|
-
spin do
|
432
|
-
@o << "POST /foo HTTP/1.1\r\nTransfer-Encoding: chunked\r\n\r\n"
|
433
|
-
chunk = ' '.to_s * rand(20..1600)
|
434
|
-
@o << "-#{chunk.bytesize.to_s(16)}\r\n#{chunk}\r\n"
|
435
|
-
@o.close
|
436
|
-
end
|
437
|
-
headers = @parser.parse_headers
|
438
|
-
assert_raises(Tipi::HTTP1Parser::Error) { @parser.read_body_chunk(false) }
|
439
|
-
|
440
|
-
reset_parser
|
441
|
-
Fiber.current.shutdown_all_children
|
442
|
-
# missing body
|
443
|
-
@o << "POST /foo HTTP/1.1\r\nTransfer-Encoding: chunked\r\n\r\n"
|
444
|
-
@o.close
|
445
|
-
headers = @parser.parse_headers
|
446
|
-
assert_raises(Tipi::HTTP1Parser::Error) { @parser.read_body_chunk(false) }
|
447
|
-
ensure
|
448
|
-
Fiber.current.shutdown_all_children
|
449
|
-
end
|
450
|
-
|
451
|
-
def test_complete?
|
452
|
-
@o << "GET / HTTP/1.1\r\n\r\n"
|
453
|
-
headers = @parser.parse_headers
|
454
|
-
assert_equal true, @parser.complete?
|
455
|
-
|
456
|
-
reset_parser
|
457
|
-
@o << "GET / HTTP/1.1\r\nContent-Length: 3\r\n\r\n"
|
458
|
-
headers = @parser.parse_headers
|
459
|
-
assert_equal false, @parser.complete?
|
460
|
-
@o << 'foo'
|
461
|
-
body = @parser.read_body
|
462
|
-
assert_equal 'foo', body
|
463
|
-
assert_equal true, @parser.complete?
|
464
|
-
|
465
|
-
reset_parser
|
466
|
-
@o << "POST / HTTP/1.1\r\nTransfer-Encoding: chunked\r\n\r\n"
|
467
|
-
headers = @parser.parse_headers
|
468
|
-
assert_equal false, @parser.complete?
|
469
|
-
@o << "3\r\nfoo\r\n"
|
470
|
-
chunk = @parser.read_body_chunk(false)
|
471
|
-
assert_equal 'foo', chunk
|
472
|
-
assert_equal false, @parser.complete?
|
473
|
-
@o << "0\r\n\r\n"
|
474
|
-
chunk = @parser.read_body_chunk(false)
|
475
|
-
assert_nil chunk
|
476
|
-
assert_equal true, @parser.complete?
|
477
|
-
end
|
478
|
-
|
479
|
-
def test_buffered_body_chunk
|
480
|
-
@o << "GET / HTTP/1.1\r\nContent-Length: 3\r\n\r\nfoo"
|
481
|
-
headers = @parser.parse_headers
|
482
|
-
assert_equal false, @parser.complete?
|
483
|
-
|
484
|
-
chunk = @parser.read_body_chunk(true)
|
485
|
-
assert_equal 'foo', chunk
|
486
|
-
assert_equal true, @parser.complete?
|
487
|
-
chunk = @parser.read_body_chunk(false)
|
488
|
-
assert_nil chunk
|
489
|
-
assert_equal true, @parser.complete?
|
490
|
-
|
491
|
-
reset_parser
|
492
|
-
@o << "GET / HTTP/1.1\r\nContent-Length: 6\r\n\r\nfoo"
|
493
|
-
headers = @parser.parse_headers
|
494
|
-
assert_equal false, @parser.complete?
|
495
|
-
|
496
|
-
chunk = @parser.read_body_chunk(true)
|
497
|
-
assert_equal 'foo', chunk
|
498
|
-
assert_equal false, @parser.complete?
|
499
|
-
@o << 'bar'
|
500
|
-
chunk = @parser.read_body_chunk(false)
|
501
|
-
assert_equal 'bar', chunk
|
502
|
-
assert_equal true, @parser.complete?
|
503
|
-
|
504
|
-
reset_parser
|
505
|
-
@o << "GET / HTTP/1.1\r\nTransfer-Encoding: chunked\r\n\r\n3\r\nfoo\r\n"
|
506
|
-
headers = @parser.parse_headers
|
507
|
-
assert_equal false, @parser.complete?
|
508
|
-
|
509
|
-
chunk = @parser.read_body_chunk(true)
|
510
|
-
assert_equal 'foo', chunk
|
511
|
-
assert_equal false, @parser.complete?
|
512
|
-
@o << "0\r\n\r\n"
|
513
|
-
chunk = @parser.read_body_chunk(true)
|
514
|
-
assert_nil chunk
|
515
|
-
assert_equal true, @parser.complete?
|
516
|
-
end
|
517
|
-
|
518
|
-
def test_parser_with_tcp_socket
|
519
|
-
port = rand(1234..5678)
|
520
|
-
server = TCPServer.new('127.0.0.1', port)
|
521
|
-
server_fiber = spin do
|
522
|
-
while (socket = server.accept)
|
523
|
-
spin do
|
524
|
-
parser = Tipi::HTTP1Parser.new(socket)
|
525
|
-
headers = parser.parse_headers
|
526
|
-
socket << headers.inspect
|
527
|
-
socket.shutdown
|
528
|
-
socket.close
|
529
|
-
end
|
530
|
-
end
|
531
|
-
end
|
532
|
-
|
533
|
-
snooze
|
534
|
-
client = TCPSocket.new('127.0.0.1', port)
|
535
|
-
msg = "get /foo HTTP/1.1\r\nCookie: abc=def\r\n\r\n"
|
536
|
-
client << msg
|
537
|
-
reply = client.read
|
538
|
-
assert_equal({
|
539
|
-
':method' => 'get',
|
540
|
-
':path' => '/foo',
|
541
|
-
':protocol' => 'http/1.1',
|
542
|
-
'cookie' => 'abc=def',
|
543
|
-
':rx' => msg.bytesize,
|
544
|
-
}, eval(reply))
|
545
|
-
ensure
|
546
|
-
client.shutdown rescue nil
|
547
|
-
client&.close
|
548
|
-
server_fiber&.stop
|
549
|
-
server_fiber&.await
|
550
|
-
server&.close
|
551
|
-
end
|
552
|
-
|
553
|
-
def test_parser_with_callable
|
554
|
-
buf = []
|
555
|
-
request = +"GET /foo HTTP/1.1\r\nHost: bar\r\n\r\n"
|
556
|
-
callable = proc do |len|
|
557
|
-
buf << {len: len}
|
558
|
-
request
|
559
|
-
end
|
560
|
-
|
561
|
-
parser = Tipi::HTTP1Parser.new(callable)
|
562
|
-
|
563
|
-
headers = parser.parse_headers
|
564
|
-
assert_equal({
|
565
|
-
':method' => 'get',
|
566
|
-
':path' => '/foo',
|
567
|
-
':protocol' => 'http/1.1',
|
568
|
-
'host' => 'bar',
|
569
|
-
':rx' => request.bytesize,
|
570
|
-
|
571
|
-
}, headers)
|
572
|
-
assert_equal [{len: 4096}], buf
|
573
|
-
|
574
|
-
request = +"GET /bar HTTP/1.1\r\nHost: baz\r\n\r\n"
|
575
|
-
headers = parser.parse_headers
|
576
|
-
assert_equal({
|
577
|
-
':method' => 'get',
|
578
|
-
':path' => '/bar',
|
579
|
-
':protocol' => 'http/1.1',
|
580
|
-
'host' => 'baz',
|
581
|
-
':rx' => request.bytesize,
|
582
|
-
|
583
|
-
}, headers)
|
584
|
-
assert_equal [{len: 4096}, {len: 4096}], buf
|
585
|
-
end
|
586
|
-
end
|