http_parser.rb 0.7.0 → 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/.github/workflows/linux.yml +2 -2
- data/.github/workflows/windows.yml +2 -2
- data/Rakefile +3 -2
- data/ext/ruby_http_parser/ext_help.h +6 -1
- data/ext/ruby_http_parser/org/ruby_http_parser/RubyHttpParser.java +9 -23
- data/ext/ruby_http_parser/ruby_http_parser.c +31 -3
- data/http_parser.rb.gemspec +2 -2
- data/tasks/compile.rake +1 -1
- metadata +3 -10
- data/spec/parser_spec.rb +0 -401
- data/spec/spec_helper.rb +0 -1
- data/spec/support/requests.json +0 -612
- data/spec/support/responses.json +0 -395
data/spec/parser_spec.rb
DELETED
|
@@ -1,401 +0,0 @@
|
|
|
1
|
-
if defined?(Encoding)
|
|
2
|
-
Encoding.default_external = "UTF-8"
|
|
3
|
-
end
|
|
4
|
-
require "spec_helper"
|
|
5
|
-
require "json"
|
|
6
|
-
|
|
7
|
-
describe HTTP::Parser do
|
|
8
|
-
before do
|
|
9
|
-
@parser = HTTP::Parser.new
|
|
10
|
-
|
|
11
|
-
@headers = nil
|
|
12
|
-
@body = ""
|
|
13
|
-
@started = false
|
|
14
|
-
@done = false
|
|
15
|
-
|
|
16
|
-
@parser.on_message_begin = proc{ @started = true }
|
|
17
|
-
@parser.on_headers_complete = proc { |e| @headers = e }
|
|
18
|
-
@parser.on_body = proc { |chunk| @body << chunk }
|
|
19
|
-
@parser.on_message_complete = proc{ @done = true }
|
|
20
|
-
end
|
|
21
|
-
|
|
22
|
-
it "should have initial state" do
|
|
23
|
-
expect(@parser.headers).to be_nil
|
|
24
|
-
|
|
25
|
-
expect(@parser.http_version).to be_nil
|
|
26
|
-
expect(@parser.http_method).to be_nil
|
|
27
|
-
expect(@parser.status_code).to be_nil
|
|
28
|
-
|
|
29
|
-
expect(@parser.request_url).to be_nil
|
|
30
|
-
|
|
31
|
-
expect(@parser.header_value_type).to eq(:mixed)
|
|
32
|
-
end
|
|
33
|
-
|
|
34
|
-
it "should allow us to set the header value type" do
|
|
35
|
-
[:mixed, :arrays, :strings].each do |type|
|
|
36
|
-
@parser.header_value_type = type
|
|
37
|
-
expect(@parser.header_value_type).to eq(type)
|
|
38
|
-
|
|
39
|
-
parser_tmp = HTTP::Parser.new(nil, type)
|
|
40
|
-
expect(parser_tmp.header_value_type).to eq(type)
|
|
41
|
-
end
|
|
42
|
-
end
|
|
43
|
-
|
|
44
|
-
it "should allow us to set the default header value type" do
|
|
45
|
-
[:mixed, :arrays, :strings].each do |type|
|
|
46
|
-
HTTP::Parser.default_header_value_type = type
|
|
47
|
-
|
|
48
|
-
parser = HTTP::Parser.new
|
|
49
|
-
expect(parser.header_value_type).to eq(type)
|
|
50
|
-
end
|
|
51
|
-
end
|
|
52
|
-
|
|
53
|
-
it "should throw an Argument Error if header value type is invalid" do
|
|
54
|
-
expect{ @parser.header_value_type = 'bob' }.to raise_error(ArgumentError)
|
|
55
|
-
end
|
|
56
|
-
|
|
57
|
-
it "should throw an Argument Error if default header value type is invalid" do
|
|
58
|
-
expect{ HTTP::Parser.default_header_value_type = 'bob' }.to raise_error(ArgumentError)
|
|
59
|
-
end
|
|
60
|
-
|
|
61
|
-
it "should implement basic api" do
|
|
62
|
-
@parser <<
|
|
63
|
-
"GET /test?ok=1 HTTP/1.1\r\n" +
|
|
64
|
-
"User-Agent: curl/7.18.0\r\n" +
|
|
65
|
-
"Host: 0.0.0.0:5000\r\n" +
|
|
66
|
-
"Accept: */*\r\n" +
|
|
67
|
-
"Content-Length: 5\r\n" +
|
|
68
|
-
"\r\n" +
|
|
69
|
-
"World"
|
|
70
|
-
|
|
71
|
-
expect(@started).to be true
|
|
72
|
-
expect(@done).to be true
|
|
73
|
-
|
|
74
|
-
expect(@parser.http_major).to eq(1)
|
|
75
|
-
expect(@parser.http_minor).to eq(1)
|
|
76
|
-
expect(@parser.http_version).to eq([1,1])
|
|
77
|
-
expect(@parser.http_method).to eq('GET')
|
|
78
|
-
expect(@parser.status_code).to be_nil
|
|
79
|
-
|
|
80
|
-
expect(@parser.request_url).to eq('/test?ok=1')
|
|
81
|
-
|
|
82
|
-
expect(@parser.headers).to eq(@headers)
|
|
83
|
-
expect(@parser.headers['User-Agent']).to eq('curl/7.18.0')
|
|
84
|
-
expect(@parser.headers['Host']).to eq('0.0.0.0:5000')
|
|
85
|
-
|
|
86
|
-
expect(@body).to eq("World")
|
|
87
|
-
end
|
|
88
|
-
|
|
89
|
-
it "should raise errors on invalid data" do
|
|
90
|
-
expect{ @parser << "BLAH" }.to raise_error(HTTP::Parser::Error)
|
|
91
|
-
end
|
|
92
|
-
|
|
93
|
-
it "should abort parser via header complete callback with a body" do
|
|
94
|
-
@parser.on_headers_complete = proc { |e| @headers = e; :stop }
|
|
95
|
-
|
|
96
|
-
data =
|
|
97
|
-
"GET / HTTP/1.0\r\n" +
|
|
98
|
-
"Content-Length: 5\r\n" +
|
|
99
|
-
"\r\n" +
|
|
100
|
-
"World"
|
|
101
|
-
|
|
102
|
-
bytes = @parser << data
|
|
103
|
-
|
|
104
|
-
expect(bytes).to eq(37)
|
|
105
|
-
expect(data[bytes..-1]).to eq('World')
|
|
106
|
-
|
|
107
|
-
expect(@headers).to eq({'Content-Length' => '5'})
|
|
108
|
-
expect(@body).to be_empty
|
|
109
|
-
expect(@done).to be false
|
|
110
|
-
end
|
|
111
|
-
|
|
112
|
-
it "should abort parser via header complete callback without a body" do
|
|
113
|
-
@parser.on_headers_complete = proc { |e| @headers = e; :stop }
|
|
114
|
-
|
|
115
|
-
data =
|
|
116
|
-
"GET / HTTP/1.0\r\n" +
|
|
117
|
-
"Content-Length: 0\r\n" +
|
|
118
|
-
"\r\n"
|
|
119
|
-
|
|
120
|
-
bytes = @parser << data
|
|
121
|
-
|
|
122
|
-
expect(bytes).to eq(37)
|
|
123
|
-
expect(data[bytes..-1]).to eq('')
|
|
124
|
-
|
|
125
|
-
expect(@headers).to eq({'Content-Length' => '0'})
|
|
126
|
-
expect(@body).to be_empty
|
|
127
|
-
expect(@done).to be false
|
|
128
|
-
end
|
|
129
|
-
|
|
130
|
-
it "should abort parser via message complete callback with a body" do
|
|
131
|
-
@parser.on_message_complete = proc { :stop }
|
|
132
|
-
|
|
133
|
-
data =
|
|
134
|
-
"CONNECT www.example.com:443 HTTP/1.0\r\n" +
|
|
135
|
-
"Connection: keep-alive\r\n" +
|
|
136
|
-
"\r\n" +
|
|
137
|
-
"World"
|
|
138
|
-
|
|
139
|
-
bytes = @parser << data
|
|
140
|
-
|
|
141
|
-
expect(bytes).to eq(64)
|
|
142
|
-
expect(data[bytes..-1]).to eq('World')
|
|
143
|
-
|
|
144
|
-
expect(@headers).to eq({'Connection' => 'keep-alive'})
|
|
145
|
-
expect(@parser.upgrade_data).to eq('World')
|
|
146
|
-
expect(@body).to be_empty
|
|
147
|
-
expect(@done).to be false
|
|
148
|
-
end
|
|
149
|
-
|
|
150
|
-
it "should abort parser via message complete callback without a body" do
|
|
151
|
-
@parser.on_message_complete = proc { :stop }
|
|
152
|
-
|
|
153
|
-
data =
|
|
154
|
-
"CONNECT www.example.com:443 HTTP/1.0\r\n" +
|
|
155
|
-
"Connection: keep-alive\r\n" +
|
|
156
|
-
"\r\n"
|
|
157
|
-
|
|
158
|
-
bytes = @parser << data
|
|
159
|
-
|
|
160
|
-
expect(bytes).to eq(64)
|
|
161
|
-
expect(data[bytes..-1]).to eq('')
|
|
162
|
-
|
|
163
|
-
expect(@headers).to eq({'Connection' => 'keep-alive'})
|
|
164
|
-
expect(@parser.upgrade_data).to eq('')
|
|
165
|
-
expect(@body).to be_empty
|
|
166
|
-
expect(@done).to be false
|
|
167
|
-
end
|
|
168
|
-
|
|
169
|
-
it "should reset to initial state" do
|
|
170
|
-
@parser << "GET / HTTP/1.0\r\n\r\n"
|
|
171
|
-
|
|
172
|
-
expect(@parser.http_method).to eq('GET')
|
|
173
|
-
expect(@parser.http_version).to eq([1,0])
|
|
174
|
-
|
|
175
|
-
expect(@parser.request_url).to eq('/')
|
|
176
|
-
|
|
177
|
-
expect(@parser.reset!).to be true
|
|
178
|
-
|
|
179
|
-
expect(@parser.http_version).to be_nil
|
|
180
|
-
expect(@parser.http_method).to be_nil
|
|
181
|
-
expect(@parser.status_code).to be_nil
|
|
182
|
-
|
|
183
|
-
expect(@parser.request_url).to be_nil
|
|
184
|
-
end
|
|
185
|
-
|
|
186
|
-
it "should optionally reset parser state on no-body responses" do
|
|
187
|
-
expect(@parser.reset!).to be true
|
|
188
|
-
|
|
189
|
-
@head, @complete = 0, 0
|
|
190
|
-
@parser.on_headers_complete = proc {|h| @head += 1; :reset }
|
|
191
|
-
@parser.on_message_complete = proc { @complete += 1 }
|
|
192
|
-
@parser.on_body = proc {|b| fail }
|
|
193
|
-
|
|
194
|
-
head_response = "HTTP/1.1 200 OK\r\nContent-Length:10\r\n\r\n"
|
|
195
|
-
|
|
196
|
-
@parser << head_response
|
|
197
|
-
expect(@head).to eq(1)
|
|
198
|
-
expect(@complete).to eq(1)
|
|
199
|
-
|
|
200
|
-
@parser << head_response
|
|
201
|
-
expect(@head).to eq(2)
|
|
202
|
-
expect(@complete).to eq(2)
|
|
203
|
-
end
|
|
204
|
-
|
|
205
|
-
it "should retain callbacks after reset" do
|
|
206
|
-
expect(@parser.reset!).to be true
|
|
207
|
-
|
|
208
|
-
@parser << "GET / HTTP/1.0\r\n\r\n"
|
|
209
|
-
expect(@started).to be true
|
|
210
|
-
expect(@headers).to eq({})
|
|
211
|
-
expect(@done).to be true
|
|
212
|
-
end
|
|
213
|
-
|
|
214
|
-
it "should parse headers incrementally" do
|
|
215
|
-
request =
|
|
216
|
-
"GET / HTTP/1.0\r\n" +
|
|
217
|
-
"Header1: value 1\r\n" +
|
|
218
|
-
"Header2: value 2\r\n" +
|
|
219
|
-
"\r\n"
|
|
220
|
-
|
|
221
|
-
while chunk = request.slice!(0,2) and !chunk.empty?
|
|
222
|
-
@parser << chunk
|
|
223
|
-
end
|
|
224
|
-
|
|
225
|
-
expect(@parser.headers).to eq({
|
|
226
|
-
'Header1' => 'value 1',
|
|
227
|
-
'Header2' => 'value 2'
|
|
228
|
-
})
|
|
229
|
-
end
|
|
230
|
-
|
|
231
|
-
it "should handle multiple headers using strings" do
|
|
232
|
-
@parser.header_value_type = :strings
|
|
233
|
-
|
|
234
|
-
@parser <<
|
|
235
|
-
"GET / HTTP/1.0\r\n" +
|
|
236
|
-
"Set-Cookie: PREF=ID=a7d2c98; expires=Fri, 05-Apr-2013 05:00:45 GMT; path=/; domain=.bob.com\r\n" +
|
|
237
|
-
"Set-Cookie: NID=46jSHxPM; path=/; domain=.bob.com; HttpOnly\r\n" +
|
|
238
|
-
"\r\n"
|
|
239
|
-
|
|
240
|
-
expect(@parser.headers["Set-Cookie"]).to eq("PREF=ID=a7d2c98; expires=Fri, 05-Apr-2013 05:00:45 GMT; path=/; domain=.bob.com, NID=46jSHxPM; path=/; domain=.bob.com; HttpOnly")
|
|
241
|
-
end
|
|
242
|
-
|
|
243
|
-
it "should handle multiple headers using strings" do
|
|
244
|
-
@parser.header_value_type = :arrays
|
|
245
|
-
|
|
246
|
-
@parser <<
|
|
247
|
-
"GET / HTTP/1.0\r\n" +
|
|
248
|
-
"Set-Cookie: PREF=ID=a7d2c98; expires=Fri, 05-Apr-2013 05:00:45 GMT; path=/; domain=.bob.com\r\n" +
|
|
249
|
-
"Set-Cookie: NID=46jSHxPM; path=/; domain=.bob.com; HttpOnly\r\n" +
|
|
250
|
-
"\r\n"
|
|
251
|
-
|
|
252
|
-
expect(@parser.headers["Set-Cookie"]).to eq([
|
|
253
|
-
"PREF=ID=a7d2c98; expires=Fri, 05-Apr-2013 05:00:45 GMT; path=/; domain=.bob.com",
|
|
254
|
-
"NID=46jSHxPM; path=/; domain=.bob.com; HttpOnly"
|
|
255
|
-
])
|
|
256
|
-
end
|
|
257
|
-
|
|
258
|
-
it "should handle multiple headers using mixed" do
|
|
259
|
-
@parser.header_value_type = :mixed
|
|
260
|
-
|
|
261
|
-
@parser <<
|
|
262
|
-
"GET / HTTP/1.0\r\n" +
|
|
263
|
-
"Set-Cookie: PREF=ID=a7d2c98; expires=Fri, 05-Apr-2013 05:00:45 GMT; path=/; domain=.bob.com\r\n" +
|
|
264
|
-
"Set-Cookie: NID=46jSHxPM; path=/; domain=.bob.com; HttpOnly\r\n" +
|
|
265
|
-
"\r\n"
|
|
266
|
-
|
|
267
|
-
expect(@parser.headers["Set-Cookie"]).to eq([
|
|
268
|
-
"PREF=ID=a7d2c98; expires=Fri, 05-Apr-2013 05:00:45 GMT; path=/; domain=.bob.com",
|
|
269
|
-
"NID=46jSHxPM; path=/; domain=.bob.com; HttpOnly"
|
|
270
|
-
])
|
|
271
|
-
end
|
|
272
|
-
|
|
273
|
-
it "should handle a single cookie using mixed" do
|
|
274
|
-
@parser.header_value_type = :mixed
|
|
275
|
-
|
|
276
|
-
@parser <<
|
|
277
|
-
"GET / HTTP/1.0\r\n" +
|
|
278
|
-
"Set-Cookie: PREF=ID=a7d2c98; expires=Fri, 05-Apr-2013 05:00:45 GMT; path=/; domain=.bob.com\r\n" +
|
|
279
|
-
"\r\n"
|
|
280
|
-
|
|
281
|
-
expect(@parser.headers["Set-Cookie"]).to eq("PREF=ID=a7d2c98; expires=Fri, 05-Apr-2013 05:00:45 GMT; path=/; domain=.bob.com")
|
|
282
|
-
end
|
|
283
|
-
|
|
284
|
-
it "should support alternative api" do
|
|
285
|
-
callbacks = double('callbacks')
|
|
286
|
-
allow(callbacks).to receive(:on_message_begin){ @started = true }
|
|
287
|
-
allow(callbacks).to receive(:on_headers_complete){ |e| @headers = e }
|
|
288
|
-
allow(callbacks).to receive(:on_body){ |chunk| @body << chunk }
|
|
289
|
-
allow(callbacks).to receive(:on_message_complete){ @done = true }
|
|
290
|
-
|
|
291
|
-
@parser = HTTP::Parser.new(callbacks)
|
|
292
|
-
@parser << "GET / HTTP/1.0\r\n\r\n"
|
|
293
|
-
|
|
294
|
-
expect(@started).to be true
|
|
295
|
-
expect(@headers).to eq({})
|
|
296
|
-
expect(@body).to eq('')
|
|
297
|
-
expect(@done).to be true
|
|
298
|
-
end
|
|
299
|
-
|
|
300
|
-
it "should ignore extra content beyond specified length" do
|
|
301
|
-
@parser <<
|
|
302
|
-
"GET / HTTP/1.0\r\n" +
|
|
303
|
-
"Content-Length: 5\r\n" +
|
|
304
|
-
"\r\n" +
|
|
305
|
-
"hello" +
|
|
306
|
-
" \n"
|
|
307
|
-
|
|
308
|
-
expect(@body).to eq('hello')
|
|
309
|
-
expect(@done).to be true
|
|
310
|
-
end
|
|
311
|
-
|
|
312
|
-
it 'sets upgrade_data if available' do
|
|
313
|
-
@parser <<
|
|
314
|
-
"GET /demo HTTP/1.1\r\n" +
|
|
315
|
-
"Connection: Upgrade\r\n" +
|
|
316
|
-
"Upgrade: WebSocket\r\n\r\n" +
|
|
317
|
-
"third key data"
|
|
318
|
-
|
|
319
|
-
expect(@parser.upgrade?).to be true
|
|
320
|
-
expect(@parser.upgrade_data).to eq('third key data')
|
|
321
|
-
end
|
|
322
|
-
|
|
323
|
-
it 'sets upgrade_data to blank if un-available' do
|
|
324
|
-
@parser <<
|
|
325
|
-
"GET /demo HTTP/1.1\r\n" +
|
|
326
|
-
"Connection: Upgrade\r\n" +
|
|
327
|
-
"Upgrade: WebSocket\r\n\r\n"
|
|
328
|
-
|
|
329
|
-
expect(@parser.upgrade?).to be true
|
|
330
|
-
expect(@parser.upgrade_data).to eq('')
|
|
331
|
-
end
|
|
332
|
-
|
|
333
|
-
it 'should stop parsing headers when instructed' do
|
|
334
|
-
request = "GET /websocket HTTP/1.1\r\n" +
|
|
335
|
-
"host: localhost\r\n" +
|
|
336
|
-
"connection: Upgrade\r\n" +
|
|
337
|
-
"upgrade: websocket\r\n" +
|
|
338
|
-
"sec-websocket-key: SD6/hpYbKjQ6Sown7pBbWQ==\r\n" +
|
|
339
|
-
"sec-websocket-version: 13\r\n" +
|
|
340
|
-
"\r\n"
|
|
341
|
-
|
|
342
|
-
@parser.on_headers_complete = proc { |e| :stop }
|
|
343
|
-
offset = (@parser << request)
|
|
344
|
-
expect(@parser.upgrade?).to be true
|
|
345
|
-
expect(@parser.upgrade_data).to eq('')
|
|
346
|
-
expect(offset).to eq(request.length)
|
|
347
|
-
end
|
|
348
|
-
|
|
349
|
-
it "should execute on_body on requests with no content-length" do
|
|
350
|
-
expect(@parser.reset!).to be true
|
|
351
|
-
|
|
352
|
-
@head, @complete, @body = 0, 0, 0
|
|
353
|
-
@parser.on_headers_complete = proc {|h| @head += 1 }
|
|
354
|
-
@parser.on_message_complete = proc { @complete += 1 }
|
|
355
|
-
@parser.on_body = proc {|b| @body += 1 }
|
|
356
|
-
|
|
357
|
-
head_response = "HTTP/1.1 200 OK\r\n\r\nstuff"
|
|
358
|
-
|
|
359
|
-
@parser << head_response
|
|
360
|
-
@parser << ''
|
|
361
|
-
expect(@head).to eq(1)
|
|
362
|
-
expect(@complete).to eq(1)
|
|
363
|
-
expect(@body).to eq(1)
|
|
364
|
-
end
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
%w[ request response ].each do |type|
|
|
368
|
-
JSON.parse(File.read(File.expand_path("../support/#{type}s.json", __FILE__))).each do |test|
|
|
369
|
-
test['headers'] ||= {}
|
|
370
|
-
next if !defined?(JRUBY_VERSION) and HTTP::Parser.strict? != test['strict']
|
|
371
|
-
|
|
372
|
-
it "should parse #{type}: #{test['name']}" do
|
|
373
|
-
@parser << test['raw']
|
|
374
|
-
|
|
375
|
-
expect(@parser.http_method).to eq(test['method'])
|
|
376
|
-
expect(@parser.keep_alive?).to eq(test['should_keep_alive'])
|
|
377
|
-
|
|
378
|
-
if test.has_key?('upgrade') and test['upgrade'] != 0
|
|
379
|
-
expect(@parser.upgrade?).to be true
|
|
380
|
-
expect(@parser.upgrade_data).to eq(test['upgrade'])
|
|
381
|
-
end
|
|
382
|
-
|
|
383
|
-
expect(@parser.send("http_major")).to eq(test["http_major"])
|
|
384
|
-
expect(@parser.send("http_minor")).to eq(test["http_minor"])
|
|
385
|
-
|
|
386
|
-
if test['type'] == 'HTTP_REQUEST'
|
|
387
|
-
expect(@parser.send("request_url")).to eq(test["request_url"].force_encoding(Encoding::BINARY))
|
|
388
|
-
else
|
|
389
|
-
expect(@parser.send("status_code")).to eq(test["status_code"])
|
|
390
|
-
expect(@parser.send("status")).to eq(test["status"].force_encoding(Encoding::BINARY))
|
|
391
|
-
end
|
|
392
|
-
|
|
393
|
-
expect(@headers.size).to eq(test['num_headers'])
|
|
394
|
-
expect(@headers).to eq(test['headers'])
|
|
395
|
-
|
|
396
|
-
expect(@body).to eq(test['body'])
|
|
397
|
-
expect(@body.size).to eq(test['body_size']) if test['body_size']
|
|
398
|
-
end
|
|
399
|
-
end
|
|
400
|
-
end
|
|
401
|
-
end
|
data/spec/spec_helper.rb
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
require "http_parser"
|