midori_http_parser 0.6.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (86) hide show
  1. checksums.yaml +7 -0
  2. data/.gitignore +12 -0
  3. data/.gitmodules +6 -0
  4. data/.travis.yml +33 -0
  5. data/Gemfile +2 -0
  6. data/LICENSE-MIT +20 -0
  7. data/README.md +90 -0
  8. data/Rakefile +6 -0
  9. data/bench/standalone.rb +23 -0
  10. data/bench/thin.rb +58 -0
  11. data/ext/ruby_http_parser/.gitignore +1 -0
  12. data/ext/ruby_http_parser/RubyHttpParserService.java +18 -0
  13. data/ext/ruby_http_parser/ext_help.h +18 -0
  14. data/ext/ruby_http_parser/extconf.rb +24 -0
  15. data/ext/ruby_http_parser/org/ruby_http_parser/RubyHttpParser.java +495 -0
  16. data/ext/ruby_http_parser/ruby_http_parser.c +516 -0
  17. data/ext/ruby_http_parser/vendor/.gitkeep +0 -0
  18. data/ext/ruby_http_parser/vendor/http-parser-java/AUTHORS +32 -0
  19. data/ext/ruby_http_parser/vendor/http-parser-java/LICENSE-MIT +48 -0
  20. data/ext/ruby_http_parser/vendor/http-parser-java/README.md +183 -0
  21. data/ext/ruby_http_parser/vendor/http-parser-java/TODO +28 -0
  22. data/ext/ruby_http_parser/vendor/http-parser-java/build.xml +74 -0
  23. data/ext/ruby_http_parser/vendor/http-parser-java/http_parser.c +2175 -0
  24. data/ext/ruby_http_parser/vendor/http-parser-java/http_parser.gyp +79 -0
  25. data/ext/ruby_http_parser/vendor/http-parser-java/http_parser.h +304 -0
  26. data/ext/ruby_http_parser/vendor/http-parser-java/src/Http-parser.java.iml +22 -0
  27. data/ext/ruby_http_parser/vendor/http-parser-java/src/impl/http_parser/FieldData.java +41 -0
  28. data/ext/ruby_http_parser/vendor/http-parser-java/src/impl/http_parser/HTTPCallback.java +8 -0
  29. data/ext/ruby_http_parser/vendor/http-parser-java/src/impl/http_parser/HTTPDataCallback.java +34 -0
  30. data/ext/ruby_http_parser/vendor/http-parser-java/src/impl/http_parser/HTTPErrorCallback.java +12 -0
  31. data/ext/ruby_http_parser/vendor/http-parser-java/src/impl/http_parser/HTTPException.java +9 -0
  32. data/ext/ruby_http_parser/vendor/http-parser-java/src/impl/http_parser/HTTPMethod.java +113 -0
  33. data/ext/ruby_http_parser/vendor/http-parser-java/src/impl/http_parser/HTTPParser.java +36 -0
  34. data/ext/ruby_http_parser/vendor/http-parser-java/src/impl/http_parser/HTTPParserUrl.java +76 -0
  35. data/ext/ruby_http_parser/vendor/http-parser-java/src/impl/http_parser/ParserSettings.java +256 -0
  36. data/ext/ruby_http_parser/vendor/http-parser-java/src/impl/http_parser/ParserType.java +13 -0
  37. data/ext/ruby_http_parser/vendor/http-parser-java/src/impl/http_parser/Util.java +111 -0
  38. data/ext/ruby_http_parser/vendor/http-parser-java/src/impl/http_parser/lolevel/HTTPCallback.java +5 -0
  39. data/ext/ruby_http_parser/vendor/http-parser-java/src/impl/http_parser/lolevel/HTTPDataCallback.java +25 -0
  40. data/ext/ruby_http_parser/vendor/http-parser-java/src/impl/http_parser/lolevel/HTTPErrorCallback.java +7 -0
  41. data/ext/ruby_http_parser/vendor/http-parser-java/src/impl/http_parser/lolevel/HTTPParser.java +2171 -0
  42. data/ext/ruby_http_parser/vendor/http-parser-java/src/impl/http_parser/lolevel/ParserSettings.java +83 -0
  43. data/ext/ruby_http_parser/vendor/http-parser-java/src/test/http_parser/lolevel/Message.java +374 -0
  44. data/ext/ruby_http_parser/vendor/http-parser-java/src/test/http_parser/lolevel/ParseUrl.java +51 -0
  45. data/ext/ruby_http_parser/vendor/http-parser-java/src/test/http_parser/lolevel/Requests.java +69 -0
  46. data/ext/ruby_http_parser/vendor/http-parser-java/src/test/http_parser/lolevel/Responses.java +52 -0
  47. data/ext/ruby_http_parser/vendor/http-parser-java/src/test/http_parser/lolevel/Test.java +16 -0
  48. data/ext/ruby_http_parser/vendor/http-parser-java/src/test/http_parser/lolevel/TestHeaderOverflowError.java +48 -0
  49. data/ext/ruby_http_parser/vendor/http-parser-java/src/test/http_parser/lolevel/TestLoaderNG.java +212 -0
  50. data/ext/ruby_http_parser/vendor/http-parser-java/src/test/http_parser/lolevel/TestNoOverflowLongBody.java +62 -0
  51. data/ext/ruby_http_parser/vendor/http-parser-java/src/test/http_parser/lolevel/UnitTest.java +117 -0
  52. data/ext/ruby_http_parser/vendor/http-parser-java/src/test/http_parser/lolevel/Upgrade.java +27 -0
  53. data/ext/ruby_http_parser/vendor/http-parser-java/src/test/http_parser/lolevel/Url.java +127 -0
  54. data/ext/ruby_http_parser/vendor/http-parser-java/src/test/http_parser/lolevel/Util.java +236 -0
  55. data/ext/ruby_http_parser/vendor/http-parser-java/src/test/http_parser/lolevel/WrongContentLength.java +59 -0
  56. data/ext/ruby_http_parser/vendor/http-parser-java/test.c +3425 -0
  57. data/ext/ruby_http_parser/vendor/http-parser-java/tests.dumped +845 -0
  58. data/ext/ruby_http_parser/vendor/http-parser-java/tests.utf8 +17 -0
  59. data/ext/ruby_http_parser/vendor/http-parser-java/tools/byte_constants.rb +6 -0
  60. data/ext/ruby_http_parser/vendor/http-parser-java/tools/const_char.rb +13 -0
  61. data/ext/ruby_http_parser/vendor/http-parser-java/tools/lowcase.rb +15 -0
  62. data/ext/ruby_http_parser/vendor/http-parser-java/tools/parse_tests.rb +33 -0
  63. data/ext/ruby_http_parser/vendor/http-parser/AUTHORS +68 -0
  64. data/ext/ruby_http_parser/vendor/http-parser/LICENSE-MIT +23 -0
  65. data/ext/ruby_http_parser/vendor/http-parser/README.md +246 -0
  66. data/ext/ruby_http_parser/vendor/http-parser/bench.c +111 -0
  67. data/ext/ruby_http_parser/vendor/http-parser/contrib/parsertrace.c +160 -0
  68. data/ext/ruby_http_parser/vendor/http-parser/contrib/url_parser.c +47 -0
  69. data/ext/ruby_http_parser/vendor/http-parser/http_parser.c +2470 -0
  70. data/ext/ruby_http_parser/vendor/http-parser/http_parser.gyp +111 -0
  71. data/ext/ruby_http_parser/vendor/http-parser/http_parser.h +432 -0
  72. data/ext/ruby_http_parser/vendor/http-parser/test.c +4226 -0
  73. data/ext/ruby_http_parser/vendor/http-parser/test_fast +0 -0
  74. data/ext/ruby_http_parser/vendor/http-parser/test_g +0 -0
  75. data/lib/http/parser.rb +1 -0
  76. data/lib/http_parser.rb +21 -0
  77. data/midori_http_parser.gemspec +24 -0
  78. data/spec/parser_spec.rb +376 -0
  79. data/spec/spec_helper.rb +1 -0
  80. data/spec/support/requests.json +631 -0
  81. data/spec/support/responses.json +375 -0
  82. data/tasks/compile.rake +42 -0
  83. data/tasks/fixtures.rake +71 -0
  84. data/tasks/spec.rake +5 -0
  85. data/tasks/submodules.rake +7 -0
  86. metadata +206 -0
@@ -0,0 +1 @@
1
+ require 'http_parser'
@@ -0,0 +1,21 @@
1
+ $:.unshift File.expand_path('../', __FILE__)
2
+ require 'ruby_http_parser'
3
+
4
+ Http = HTTP
5
+
6
+ module HTTP
7
+ class Parser
8
+ class << self
9
+ attr_reader :default_header_value_type
10
+
11
+ def default_header_value_type=(val)
12
+ if (val != :mixed && val != :strings && val != :arrays)
13
+ raise ArgumentError, "Invalid header value type"
14
+ end
15
+ @default_header_value_type = val
16
+ end
17
+ end
18
+ end
19
+ end
20
+
21
+ HTTP::Parser.default_header_value_type = :mixed
@@ -0,0 +1,24 @@
1
+ Gem::Specification.new do |s|
2
+ s.name = "midori_http_parser"
3
+ s.version = "0.6.1"
4
+ s.summary = "Simple callback-based HTTP request/response parser"
5
+ s.description = "Fast Ruby Parser inpired by Node"
6
+
7
+ s.authors = ["Delton Ding"]
8
+ s.email = ["delton@heckpsi.com"]
9
+ s.license = 'MIT'
10
+
11
+ s.homepage = "https://github.com/heckpsi-lab/http_parser.rb"
12
+ s.files = `git ls-files`.split("\n") + Dir['ext/ruby_http_parser/vendor/**/*']
13
+
14
+ s.require_paths = ["lib"]
15
+ s.extensions = ["ext/ruby_http_parser/extconf.rb"]
16
+
17
+ s.add_development_dependency 'rake-compiler', '>= 0.7.9'
18
+ s.add_development_dependency 'rspec', '>= 3'
19
+ s.add_development_dependency 'json', '>= 1.4.6'
20
+ s.add_development_dependency 'benchmark_suite'
21
+ s.add_development_dependency 'ffi'
22
+
23
+ s.add_development_dependency 'yajl-ruby', '>= 0.8.1' unless RUBY_PLATFORM =~ /java/
24
+ end
@@ -0,0 +1,376 @@
1
+ require "spec_helper"
2
+ require "json"
3
+
4
+ describe HTTP::Parser do
5
+ before do
6
+ @parser = HTTP::Parser.new
7
+
8
+ @headers = nil
9
+ @body = ""
10
+ @started = false
11
+ @done = false
12
+
13
+ @parser.on_message_begin = proc{ @started = true }
14
+ @parser.on_headers_complete = proc { |e| @headers = e }
15
+ @parser.on_body = proc { |chunk| @body << chunk }
16
+ @parser.on_message_complete = proc{ @done = true }
17
+ end
18
+
19
+ it "should have initial state" do
20
+ expect(@parser.headers).to be_nil
21
+
22
+ expect(@parser.http_version).to be_nil
23
+ expect(@parser.http_method).to be_nil
24
+ expect(@parser.status_code).to be_nil
25
+
26
+ expect(@parser.request_url).to be_nil
27
+
28
+ expect(@parser.header_value_type).to eq(:mixed)
29
+ end
30
+
31
+ it "should allow us to set the header value type" do
32
+ [:mixed, :arrays, :strings].each do |type|
33
+ @parser.header_value_type = type
34
+ expect(@parser.header_value_type).to eq(type)
35
+
36
+ parser_tmp = HTTP::Parser.new(nil, type)
37
+ expect(parser_tmp.header_value_type).to eq(type)
38
+ end
39
+ end
40
+
41
+ it "should allow us to set the default header value type" do
42
+ [:mixed, :arrays, :strings].each do |type|
43
+ HTTP::Parser.default_header_value_type = type
44
+
45
+ parser = HTTP::Parser.new
46
+ expect(parser.header_value_type).to eq(type)
47
+ end
48
+ end
49
+
50
+ it "should throw an Argument Error if header value type is invalid" do
51
+ expect{ @parser.header_value_type = 'bob' }.to raise_error(ArgumentError)
52
+ end
53
+
54
+ it "should throw an Argument Error if default header value type is invalid" do
55
+ expect{ HTTP::Parser.default_header_value_type = 'bob' }.to raise_error(ArgumentError)
56
+ end
57
+
58
+ it "should implement basic api" do
59
+ @parser <<
60
+ "GET /test?ok=1 HTTP/1.1\r\n" +
61
+ "User-Agent: curl/7.18.0\r\n" +
62
+ "Host: 0.0.0.0:5000\r\n" +
63
+ "Accept: */*\r\n" +
64
+ "Content-Length: 5\r\n" +
65
+ "\r\n" +
66
+ "World"
67
+
68
+ expect(@started).to be true
69
+ expect(@done).to be true
70
+
71
+ expect(@parser.http_major).to eq(1)
72
+ expect(@parser.http_minor).to eq(1)
73
+ expect(@parser.http_version).to eq([1,1])
74
+ expect(@parser.http_method).to eq('GET')
75
+ expect(@parser.status_code).to be_nil
76
+
77
+ expect(@parser.request_url).to eq('/test?ok=1')
78
+
79
+ expect(@parser.headers).to eq(@headers)
80
+ expect(@parser.headers['User-Agent']).to eq('curl/7.18.0')
81
+ expect(@parser.headers['Host']).to eq('0.0.0.0:5000')
82
+
83
+ expect(@body).to eq("World")
84
+ end
85
+
86
+ it "should raise errors on invalid data" do
87
+ expect{ @parser << "BLAH" }.to raise_error(HTTP::Parser::Error)
88
+ end
89
+
90
+ it "should abort parser via header complete callback with a body" do
91
+ @parser.on_headers_complete = proc { |e| @headers = e; :stop }
92
+
93
+ data =
94
+ "GET / HTTP/1.0\r\n" +
95
+ "Content-Length: 5\r\n" +
96
+ "\r\n" +
97
+ "World"
98
+
99
+ bytes = @parser << data
100
+
101
+ expect(bytes).to eq(37)
102
+ expect(data[bytes..-1]).to eq('World')
103
+
104
+ expect(@headers).to eq({'Content-Length' => '5'})
105
+ expect(@body).to be_empty
106
+ expect(@done).to be false
107
+ end
108
+
109
+ it "should abort parser via header complete callback without a body" do
110
+ @parser.on_headers_complete = proc { |e| @headers = e; :stop }
111
+
112
+ data =
113
+ "GET / HTTP/1.0\r\n" +
114
+ "Content-Length: 0\r\n" +
115
+ "\r\n"
116
+
117
+ bytes = @parser << data
118
+
119
+ expect(bytes).to eq(37)
120
+ expect(data[bytes..-1]).to eq('')
121
+
122
+ expect(@headers).to eq({'Content-Length' => '0'})
123
+ expect(@body).to be_empty
124
+ expect(@done).to be false
125
+ end
126
+
127
+ it "should abort parser via message complete callback with a body" do
128
+ @parser.on_message_complete = proc { :stop }
129
+
130
+ data =
131
+ "CONNECT www.example.com:443 HTTP/1.0\r\n" +
132
+ "Connection: keep-alive\r\n" +
133
+ "\r\n" +
134
+ "World"
135
+
136
+ bytes = @parser << data
137
+
138
+ expect(bytes).to eq(64)
139
+ expect(data[bytes..-1]).to eq('World')
140
+
141
+ expect(@headers).to eq({'Connection' => 'keep-alive'})
142
+ expect(@parser.upgrade_data).to eq('World')
143
+ expect(@body).to be_empty
144
+ expect(@done).to be false
145
+ end
146
+
147
+ it "should abort parser via message complete callback without a body" do
148
+ @parser.on_message_complete = proc { :stop }
149
+
150
+ data =
151
+ "CONNECT www.example.com:443 HTTP/1.0\r\n" +
152
+ "Connection: keep-alive\r\n" +
153
+ "\r\n"
154
+
155
+ bytes = @parser << data
156
+
157
+ expect(bytes).to eq(64)
158
+ expect(data[bytes..-1]).to eq('')
159
+
160
+ expect(@headers).to eq({'Connection' => 'keep-alive'})
161
+ expect(@parser.upgrade_data).to eq('')
162
+ expect(@body).to be_empty
163
+ expect(@done).to be false
164
+ end
165
+
166
+ it "should reset to initial state" do
167
+ @parser << "GET / HTTP/1.0\r\n\r\n"
168
+
169
+ expect(@parser.http_method).to eq('GET')
170
+ expect(@parser.http_version).to eq([1,0])
171
+
172
+ expect(@parser.request_url).to eq('/')
173
+
174
+ expect(@parser.reset!).to be true
175
+
176
+ expect(@parser.http_version).to be_nil
177
+ expect(@parser.http_method).to be_nil
178
+ expect(@parser.status_code).to be_nil
179
+
180
+ expect(@parser.request_url).to be_nil
181
+ end
182
+
183
+ it "should retain callbacks after reset" do
184
+ expect(@parser.reset!).to be true
185
+
186
+ @parser << "GET / HTTP/1.0\r\n\r\n"
187
+ expect(@started).to be true
188
+ expect(@headers).to eq({})
189
+ expect(@done).to be true
190
+ end
191
+
192
+ it "should parse headers incrementally" do
193
+ request =
194
+ "GET / HTTP/1.0\r\n" +
195
+ "Header1: value 1\r\n" +
196
+ "Header2: value 2\r\n" +
197
+ "\r\n"
198
+
199
+ while chunk = request.slice!(0,2) and !chunk.empty?
200
+ @parser << chunk
201
+ end
202
+
203
+ expect(@parser.headers).to eq({
204
+ 'Header1' => 'value 1',
205
+ 'Header2' => 'value 2'
206
+ })
207
+ end
208
+
209
+ it "should handle multiple headers using strings" do
210
+ @parser.header_value_type = :strings
211
+
212
+ @parser <<
213
+ "GET / HTTP/1.0\r\n" +
214
+ "Set-Cookie: PREF=ID=a7d2c98; expires=Fri, 05-Apr-2013 05:00:45 GMT; path=/; domain=.bob.com\r\n" +
215
+ "Set-Cookie: NID=46jSHxPM; path=/; domain=.bob.com; HttpOnly\r\n" +
216
+ "\r\n"
217
+
218
+ 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")
219
+ end
220
+
221
+ it "should handle multiple headers using strings" do
222
+ @parser.header_value_type = :arrays
223
+
224
+ @parser <<
225
+ "GET / HTTP/1.0\r\n" +
226
+ "Set-Cookie: PREF=ID=a7d2c98; expires=Fri, 05-Apr-2013 05:00:45 GMT; path=/; domain=.bob.com\r\n" +
227
+ "Set-Cookie: NID=46jSHxPM; path=/; domain=.bob.com; HttpOnly\r\n" +
228
+ "\r\n"
229
+
230
+ expect(@parser.headers["Set-Cookie"]).to eq([
231
+ "PREF=ID=a7d2c98; expires=Fri, 05-Apr-2013 05:00:45 GMT; path=/; domain=.bob.com",
232
+ "NID=46jSHxPM; path=/; domain=.bob.com; HttpOnly"
233
+ ])
234
+ end
235
+
236
+ it "should handle multiple headers using mixed" do
237
+ @parser.header_value_type = :mixed
238
+
239
+ @parser <<
240
+ "GET / HTTP/1.0\r\n" +
241
+ "Set-Cookie: PREF=ID=a7d2c98; expires=Fri, 05-Apr-2013 05:00:45 GMT; path=/; domain=.bob.com\r\n" +
242
+ "Set-Cookie: NID=46jSHxPM; path=/; domain=.bob.com; HttpOnly\r\n" +
243
+ "\r\n"
244
+
245
+ expect(@parser.headers["Set-Cookie"]).to eq([
246
+ "PREF=ID=a7d2c98; expires=Fri, 05-Apr-2013 05:00:45 GMT; path=/; domain=.bob.com",
247
+ "NID=46jSHxPM; path=/; domain=.bob.com; HttpOnly"
248
+ ])
249
+ end
250
+
251
+ it "should handle a single cookie using mixed" do
252
+ @parser.header_value_type = :mixed
253
+
254
+ @parser <<
255
+ "GET / HTTP/1.0\r\n" +
256
+ "Set-Cookie: PREF=ID=a7d2c98; expires=Fri, 05-Apr-2013 05:00:45 GMT; path=/; domain=.bob.com\r\n" +
257
+ "\r\n"
258
+
259
+ expect(@parser.headers["Set-Cookie"]).to eq("PREF=ID=a7d2c98; expires=Fri, 05-Apr-2013 05:00:45 GMT; path=/; domain=.bob.com")
260
+ end
261
+
262
+ it "should support alternative api" do
263
+ callbacks = double('callbacks')
264
+ allow(callbacks).to receive(:on_message_begin){ @started = true }
265
+ allow(callbacks).to receive(:on_headers_complete){ |e| @headers = e }
266
+ allow(callbacks).to receive(:on_body){ |chunk| @body << chunk }
267
+ allow(callbacks).to receive(:on_message_complete){ @done = true }
268
+
269
+ @parser = HTTP::Parser.new(callbacks)
270
+ @parser << "GET / HTTP/1.0\r\n\r\n"
271
+
272
+ expect(@started).to be true
273
+ expect(@headers).to eq({})
274
+ expect(@body).to eq('')
275
+ expect(@done).to be true
276
+ end
277
+
278
+ it 'sets upgrade_data if available' do
279
+ @parser <<
280
+ "GET /demo HTTP/1.1\r\n" +
281
+ "Connection: Upgrade\r\n" +
282
+ "Upgrade: WebSocket\r\n\r\n" +
283
+ "third key data"
284
+
285
+ expect(@parser.upgrade?).to be true
286
+ expect(@parser.upgrade_data).to eq('third key data')
287
+ end
288
+
289
+ it 'sets upgrade_data to blank if un-available' do
290
+ @parser <<
291
+ "GET /demo HTTP/1.1\r\n" +
292
+ "Connection: Upgrade\r\n" +
293
+ "Upgrade: WebSocket\r\n\r\n"
294
+
295
+ expect(@parser.upgrade?).to be true
296
+ expect(@parser.upgrade_data).to eq('')
297
+ end
298
+
299
+ it 'should stop parsing headers when instructed' do
300
+ request = "GET /websocket HTTP/1.1\r\n" +
301
+ "host: localhost\r\n" +
302
+ "connection: Upgrade\r\n" +
303
+ "upgrade: websocket\r\n" +
304
+ "sec-websocket-key: SD6/hpYbKjQ6Sown7pBbWQ==\r\n" +
305
+ "sec-websocket-version: 13\r\n" +
306
+ "\r\n"
307
+
308
+ @parser.on_headers_complete = proc { |e| :stop }
309
+ offset = (@parser << request)
310
+ expect(@parser.upgrade?).to be true
311
+ expect(@parser.upgrade_data).to eq('')
312
+ expect(offset).to eq(request.length)
313
+ end
314
+
315
+ it "should execute on_body on requests with no content-length" do
316
+ expect(@parser.reset!).to be true
317
+
318
+ @head, @complete, @body = 0, 0, 0
319
+ @parser.on_headers_complete = proc {|h| @head += 1 }
320
+ @parser.on_message_complete = proc { @complete += 1 }
321
+ @parser.on_body = proc {|b| @body += 1 }
322
+
323
+ head_response = "HTTP/1.1 200 OK\r\n\r\nstuff"
324
+
325
+ @parser << head_response
326
+ @parser << ''
327
+ expect(@head).to eq(1)
328
+ expect(@complete).to eq(1)
329
+ expect(@body).to eq(1)
330
+ end
331
+
332
+
333
+ %w[ request response ].each do |type|
334
+ JSON.parse(File.read(File.expand_path("../support/#{type}s.json", __FILE__))).each do |test|
335
+ test['headers'] ||= {}
336
+ next if !defined?(JRUBY_VERSION) and HTTP::Parser.strict? != test['strict']
337
+
338
+ it "should parse #{type}: #{test['name']}" do
339
+ @parser << test['raw']
340
+
341
+ expect(@parser.http_method).to eq(test['method'])
342
+ expect(@parser.keep_alive?).to eq(test['should_keep_alive'])
343
+
344
+ if test.has_key?('upgrade') and test['upgrade'] != 0
345
+ expect(@parser.upgrade?).to be true
346
+ expect(@parser.upgrade_data).to eq(test['upgrade'])
347
+ end
348
+
349
+ fields = %w[
350
+ http_major
351
+ http_minor
352
+ ]
353
+
354
+ if test['type'] == 'HTTP_REQUEST'
355
+ fields += %w[
356
+ request_url
357
+ ]
358
+ else
359
+ fields += %w[
360
+ status_code
361
+ ]
362
+ end
363
+
364
+ fields.each do |field|
365
+ expect(@parser.send(field)).to eq(test[field])
366
+ end
367
+
368
+ expect(@headers.size).to eq(test['num_headers'])
369
+ expect(@headers).to eq(test['headers'])
370
+
371
+ expect(@body).to eq(test['body'])
372
+ expect(@body.size).to eq(test['body_size']) if test['body_size']
373
+ end
374
+ end
375
+ end
376
+ end
@@ -0,0 +1 @@
1
+ require "http_parser"
@@ -0,0 +1,631 @@
1
+ [
2
+ {
3
+ "name": "curl get",
4
+ "type": "HTTP_REQUEST",
5
+ "raw": "GET /test HTTP/1.1\r\nUser-Agent: curl/7.18.0 (i486-pc-linux-gnu) libcurl/7.18.0 OpenSSL/0.9.8g zlib/1.2.3.3 libidn/1.1\r\nHost: 0.0.0.0=5000\r\nAccept: */*\r\n\r\n",
6
+ "should_keep_alive": true,
7
+ "message_complete_on_eof": false,
8
+ "http_major": 1,
9
+ "http_minor": 1,
10
+ "method": "GET",
11
+ "query_string": "",
12
+ "fragment": "",
13
+ "request_path": "/test",
14
+ "request_url": "/test",
15
+ "num_headers": 3,
16
+ "headers": {
17
+ "User-Agent": "curl/7.18.0 (i486-pc-linux-gnu) libcurl/7.18.0 OpenSSL/0.9.8g zlib/1.2.3.3 libidn/1.1",
18
+ "Host": "0.0.0.0=5000",
19
+ "Accept": "*/*"
20
+ },
21
+ "body": "",
22
+ "strict": true
23
+ },
24
+ {
25
+ "name": "firefox get",
26
+ "type": "HTTP_REQUEST",
27
+ "raw": "GET /favicon.ico HTTP/1.1\r\nHost: 0.0.0.0=5000\r\nUser-Agent: Mozilla/5.0 (X11; U; Linux i686; en-US; rv:1.9) Gecko/2008061015 Firefox/3.0\r\nAccept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8\r\nAccept-Language: en-us,en;q=0.5\r\nAccept-Encoding: gzip,deflate\r\nAccept-Charset: ISO-8859-1,utf-8;q=0.7,*;q=0.7\r\nKeep-Alive: 300\r\nConnection: keep-alive\r\n\r\n",
28
+ "should_keep_alive": true,
29
+ "message_complete_on_eof": false,
30
+ "http_major": 1,
31
+ "http_minor": 1,
32
+ "method": "GET",
33
+ "query_string": "",
34
+ "fragment": "",
35
+ "request_path": "/favicon.ico",
36
+ "request_url": "/favicon.ico",
37
+ "num_headers": 8,
38
+ "headers": {
39
+ "Host": "0.0.0.0=5000",
40
+ "User-Agent": "Mozilla/5.0 (X11; U; Linux i686; en-US; rv:1.9) Gecko/2008061015 Firefox/3.0",
41
+ "Accept": "text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8",
42
+ "Accept-Language": "en-us,en;q=0.5",
43
+ "Accept-Encoding": "gzip,deflate",
44
+ "Accept-Charset": "ISO-8859-1,utf-8;q=0.7,*;q=0.7",
45
+ "Keep-Alive": "300",
46
+ "Connection": "keep-alive"
47
+ },
48
+ "body": "",
49
+ "strict": true
50
+ },
51
+ {
52
+ "name": "dumbfuck",
53
+ "type": "HTTP_REQUEST",
54
+ "raw": "GET /dumbfuck HTTP/1.1\r\naaaaaaaaaaaaa:++++++++++\r\n\r\n",
55
+ "should_keep_alive": true,
56
+ "message_complete_on_eof": false,
57
+ "http_major": 1,
58
+ "http_minor": 1,
59
+ "method": "GET",
60
+ "query_string": "",
61
+ "fragment": "",
62
+ "request_path": "/dumbfuck",
63
+ "request_url": "/dumbfuck",
64
+ "num_headers": 1,
65
+ "headers": {
66
+ "aaaaaaaaaaaaa": "++++++++++"
67
+ },
68
+ "body": "",
69
+ "strict": true
70
+ },
71
+ {
72
+ "name": "fragment in url",
73
+ "type": "HTTP_REQUEST",
74
+ "raw": "GET /forums/1/topics/2375?page=1#posts-17408 HTTP/1.1\r\n\r\n",
75
+ "should_keep_alive": true,
76
+ "message_complete_on_eof": false,
77
+ "http_major": 1,
78
+ "http_minor": 1,
79
+ "method": "GET",
80
+ "query_string": "page=1",
81
+ "fragment": "posts-17408",
82
+ "request_path": "/forums/1/topics/2375",
83
+ "request_url": "/forums/1/topics/2375?page=1#posts-17408",
84
+ "num_headers": 0,
85
+ "body": "",
86
+ "strict": true
87
+ },
88
+ {
89
+ "name": "get no headers no body",
90
+ "type": "HTTP_REQUEST",
91
+ "raw": "GET /get_no_headers_no_body/world HTTP/1.1\r\n\r\n",
92
+ "should_keep_alive": true,
93
+ "message_complete_on_eof": false,
94
+ "http_major": 1,
95
+ "http_minor": 1,
96
+ "method": "GET",
97
+ "query_string": "",
98
+ "fragment": "",
99
+ "request_path": "/get_no_headers_no_body/world",
100
+ "request_url": "/get_no_headers_no_body/world",
101
+ "num_headers": 0,
102
+ "body": "",
103
+ "strict": true
104
+ },
105
+ {
106
+ "name": "get one header no body",
107
+ "type": "HTTP_REQUEST",
108
+ "raw": "GET /get_one_header_no_body HTTP/1.1\r\nAccept: */*\r\n\r\n",
109
+ "should_keep_alive": true,
110
+ "message_complete_on_eof": false,
111
+ "http_major": 1,
112
+ "http_minor": 1,
113
+ "method": "GET",
114
+ "query_string": "",
115
+ "fragment": "",
116
+ "request_path": "/get_one_header_no_body",
117
+ "request_url": "/get_one_header_no_body",
118
+ "num_headers": 1,
119
+ "headers": {
120
+ "Accept": "*/*"
121
+ },
122
+ "body": "",
123
+ "strict": true
124
+ },
125
+ {
126
+ "name": "get funky content length body hello",
127
+ "type": "HTTP_REQUEST",
128
+ "raw": "GET /get_funky_content_length_body_hello HTTP/1.0\r\nconTENT-Length: 5\r\n\r\nHELLO",
129
+ "should_keep_alive": false,
130
+ "message_complete_on_eof": false,
131
+ "http_major": 1,
132
+ "http_minor": 0,
133
+ "method": "GET",
134
+ "query_string": "",
135
+ "fragment": "",
136
+ "request_path": "/get_funky_content_length_body_hello",
137
+ "request_url": "/get_funky_content_length_body_hello",
138
+ "num_headers": 1,
139
+ "headers": {
140
+ "conTENT-Length": "5"
141
+ },
142
+ "body": "HELLO",
143
+ "strict": true
144
+ },
145
+ {
146
+ "name": "post identity body world",
147
+ "type": "HTTP_REQUEST",
148
+ "raw": "POST /post_identity_body_world?q=search#hey HTTP/1.1\r\nAccept: */*\r\nTransfer-Encoding: identity\r\nContent-Length: 5\r\n\r\nWorld",
149
+ "should_keep_alive": true,
150
+ "message_complete_on_eof": false,
151
+ "http_major": 1,
152
+ "http_minor": 1,
153
+ "method": "POST",
154
+ "query_string": "q=search",
155
+ "fragment": "hey",
156
+ "request_path": "/post_identity_body_world",
157
+ "request_url": "/post_identity_body_world?q=search#hey",
158
+ "num_headers": 3,
159
+ "headers": {
160
+ "Accept": "*/*",
161
+ "Transfer-Encoding": "identity",
162
+ "Content-Length": "5"
163
+ },
164
+ "body": "World",
165
+ "strict": true
166
+ },
167
+ {
168
+ "name": "post - chunked body: all your base are belong to us",
169
+ "type": "HTTP_REQUEST",
170
+ "raw": "POST /post_chunked_all_your_base HTTP/1.1\r\nTransfer-Encoding: chunked\r\n\r\n1e\r\nall your base are belong to us\r\n0\r\n\r\n",
171
+ "should_keep_alive": true,
172
+ "message_complete_on_eof": false,
173
+ "http_major": 1,
174
+ "http_minor": 1,
175
+ "method": "POST",
176
+ "query_string": "",
177
+ "fragment": "",
178
+ "request_path": "/post_chunked_all_your_base",
179
+ "request_url": "/post_chunked_all_your_base",
180
+ "num_headers": 1,
181
+ "headers": {
182
+ "Transfer-Encoding": "chunked"
183
+ },
184
+ "body": "all your base are belong to us",
185
+ "strict": true
186
+ },
187
+ {
188
+ "name": "two chunks ; triple zero ending",
189
+ "type": "HTTP_REQUEST",
190
+ "raw": "POST /two_chunks_mult_zero_end HTTP/1.1\r\nTransfer-Encoding: chunked\r\n\r\n5\r\nhello\r\n6\r\n world\r\n000\r\n\r\n",
191
+ "should_keep_alive": true,
192
+ "message_complete_on_eof": false,
193
+ "http_major": 1,
194
+ "http_minor": 1,
195
+ "method": "POST",
196
+ "query_string": "",
197
+ "fragment": "",
198
+ "request_path": "/two_chunks_mult_zero_end",
199
+ "request_url": "/two_chunks_mult_zero_end",
200
+ "num_headers": 1,
201
+ "headers": {
202
+ "Transfer-Encoding": "chunked"
203
+ },
204
+ "body": "hello world",
205
+ "strict": true
206
+ },
207
+ {
208
+ "name": "chunked with trailing headers. blech.",
209
+ "type": "HTTP_REQUEST",
210
+ "raw": "POST /chunked_w_trailing_headers HTTP/1.1\r\nTransfer-Encoding: chunked\r\n\r\n5\r\nhello\r\n6\r\n world\r\n0\r\nVary: *\r\nContent-Type: text/plain\r\n\r\n",
211
+ "should_keep_alive": true,
212
+ "message_complete_on_eof": false,
213
+ "http_major": 1,
214
+ "http_minor": 1,
215
+ "method": "POST",
216
+ "query_string": "",
217
+ "fragment": "",
218
+ "request_path": "/chunked_w_trailing_headers",
219
+ "request_url": "/chunked_w_trailing_headers",
220
+ "num_headers": 3,
221
+ "headers": {
222
+ "Transfer-Encoding": "chunked",
223
+ "Vary": "*",
224
+ "Content-Type": "text/plain"
225
+ },
226
+ "body": "hello world",
227
+ "strict": true
228
+ },
229
+ {
230
+ "name": "with bullshit after the length",
231
+ "type": "HTTP_REQUEST",
232
+ "raw": "POST /chunked_w_bullshit_after_length HTTP/1.1\r\nTransfer-Encoding: chunked\r\n\r\n5; ihatew3;whatthefuck=aretheseparametersfor\r\nhello\r\n6; blahblah; blah\r\n world\r\n0\r\n\r\n",
233
+ "should_keep_alive": true,
234
+ "message_complete_on_eof": false,
235
+ "http_major": 1,
236
+ "http_minor": 1,
237
+ "method": "POST",
238
+ "query_string": "",
239
+ "fragment": "",
240
+ "request_path": "/chunked_w_bullshit_after_length",
241
+ "request_url": "/chunked_w_bullshit_after_length",
242
+ "num_headers": 1,
243
+ "headers": {
244
+ "Transfer-Encoding": "chunked"
245
+ },
246
+ "body": "hello world",
247
+ "strict": true
248
+ },
249
+ {
250
+ "name": "with quotes",
251
+ "type": "HTTP_REQUEST",
252
+ "raw": "GET /with_\"stupid\"_quotes?foo=\"bar\" HTTP/1.1\r\n\r\n",
253
+ "should_keep_alive": true,
254
+ "message_complete_on_eof": false,
255
+ "http_major": 1,
256
+ "http_minor": 1,
257
+ "method": "GET",
258
+ "query_string": "foo=\"bar\"",
259
+ "fragment": "",
260
+ "request_path": "/with_\"stupid\"_quotes",
261
+ "request_url": "/with_\"stupid\"_quotes?foo=\"bar\"",
262
+ "num_headers": 0,
263
+ "headers": {
264
+
265
+ },
266
+ "body": "",
267
+ "strict": true
268
+ },
269
+ {
270
+ "name": "apachebench get",
271
+ "type": "HTTP_REQUEST",
272
+ "raw": "GET /test HTTP/1.0\r\nHost: 0.0.0.0:5000\r\nUser-Agent: ApacheBench/2.3\r\nAccept: */*\r\n\r\n",
273
+ "should_keep_alive": false,
274
+ "message_complete_on_eof": false,
275
+ "http_major": 1,
276
+ "http_minor": 0,
277
+ "method": "GET",
278
+ "query_string": "",
279
+ "fragment": "",
280
+ "request_path": "/test",
281
+ "request_url": "/test",
282
+ "num_headers": 3,
283
+ "headers": {
284
+ "Host": "0.0.0.0:5000",
285
+ "User-Agent": "ApacheBench/2.3",
286
+ "Accept": "*/*"
287
+ },
288
+ "body": "",
289
+ "strict": true
290
+ },
291
+ {
292
+ "name": "query url with question mark",
293
+ "type": "HTTP_REQUEST",
294
+ "raw": "GET /test.cgi?foo=bar?baz HTTP/1.1\r\n\r\n",
295
+ "should_keep_alive": true,
296
+ "message_complete_on_eof": false,
297
+ "http_major": 1,
298
+ "http_minor": 1,
299
+ "method": "GET",
300
+ "query_string": "foo=bar?baz",
301
+ "fragment": "",
302
+ "request_path": "/test.cgi",
303
+ "request_url": "/test.cgi?foo=bar?baz",
304
+ "num_headers": 0,
305
+ "headers": {
306
+
307
+ },
308
+ "body": "",
309
+ "strict": true
310
+ },
311
+ {
312
+ "name": "newline prefix get",
313
+ "type": "HTTP_REQUEST",
314
+ "raw": "\r\nGET /test HTTP/1.1\r\n\r\n",
315
+ "should_keep_alive": true,
316
+ "message_complete_on_eof": false,
317
+ "http_major": 1,
318
+ "http_minor": 1,
319
+ "method": "GET",
320
+ "query_string": "",
321
+ "fragment": "",
322
+ "request_path": "/test",
323
+ "request_url": "/test",
324
+ "num_headers": 0,
325
+ "headers": {
326
+
327
+ },
328
+ "body": "",
329
+ "strict": true
330
+ },
331
+ {
332
+ "name": "upgrade request",
333
+ "type": "HTTP_REQUEST",
334
+ "raw": "GET /demo HTTP/1.1\r\nHost: example.com\r\nConnection: Upgrade\r\nSec-WebSocket-Key2: 12998 5 Y3 1 .P00\r\nSec-WebSocket-Protocol: sample\r\nUpgrade: WebSocket\r\nSec-WebSocket-Key1: 4 @1 46546xW%0l 1 5\r\nOrigin: http://example.com\r\n\r\nHot diggity dogg",
335
+ "should_keep_alive": true,
336
+ "message_complete_on_eof": false,
337
+ "http_major": 1,
338
+ "http_minor": 1,
339
+ "method": "GET",
340
+ "query_string": "",
341
+ "fragment": "",
342
+ "request_path": "/demo",
343
+ "request_url": "/demo",
344
+ "num_headers": 7,
345
+ "upgrade": "Hot diggity dogg",
346
+ "headers": {
347
+ "Host": "example.com",
348
+ "Connection": "Upgrade",
349
+ "Sec-WebSocket-Key2": "12998 5 Y3 1 .P00",
350
+ "Sec-WebSocket-Protocol": "sample",
351
+ "Upgrade": "WebSocket",
352
+ "Sec-WebSocket-Key1": "4 @1 46546xW%0l 1 5",
353
+ "Origin": "http://example.com"
354
+ },
355
+ "body": "",
356
+ "strict": true
357
+ },
358
+ {
359
+ "name": "connect request",
360
+ "type": "HTTP_REQUEST",
361
+ "raw": "CONNECT 0-home0.netscape.com:443 HTTP/1.0\r\nUser-agent: Mozilla/1.1N\r\nProxy-authorization: basic aGVsbG86d29ybGQ=\r\n\r\nsome data\r\nand yet even more data",
362
+ "should_keep_alive": false,
363
+ "message_complete_on_eof": false,
364
+ "http_major": 1,
365
+ "http_minor": 0,
366
+ "method": "CONNECT",
367
+ "query_string": "",
368
+ "fragment": "",
369
+ "request_path": "",
370
+ "request_url": "0-home0.netscape.com:443",
371
+ "num_headers": 2,
372
+ "upgrade": "some data\r\nand yet even more data",
373
+ "headers": {
374
+ "User-agent": "Mozilla/1.1N",
375
+ "Proxy-authorization": "basic aGVsbG86d29ybGQ="
376
+ },
377
+ "body": "",
378
+ "strict": true
379
+ },
380
+ {
381
+ "name": "report request",
382
+ "type": "HTTP_REQUEST",
383
+ "raw": "REPORT /test HTTP/1.1\r\n\r\n",
384
+ "should_keep_alive": true,
385
+ "message_complete_on_eof": false,
386
+ "http_major": 1,
387
+ "http_minor": 1,
388
+ "method": "REPORT",
389
+ "query_string": "",
390
+ "fragment": "",
391
+ "request_path": "/test",
392
+ "request_url": "/test",
393
+ "num_headers": 0,
394
+ "headers": {
395
+
396
+ },
397
+ "body": "",
398
+ "strict": true
399
+ },
400
+ {
401
+ "name": "request with no http version",
402
+ "type": "HTTP_REQUEST",
403
+ "raw": "GET /\r\n\r\n",
404
+ "should_keep_alive": false,
405
+ "message_complete_on_eof": false,
406
+ "http_major": 0,
407
+ "http_minor": 9,
408
+ "method": "GET",
409
+ "query_string": "",
410
+ "fragment": "",
411
+ "request_path": "/",
412
+ "request_url": "/",
413
+ "num_headers": 0,
414
+ "headers": {
415
+
416
+ },
417
+ "body": "",
418
+ "strict": true
419
+ },
420
+ {
421
+ "name": "link request",
422
+ "type": "HTTP_REQUEST",
423
+ "raw": "LINK /test HTTP/1.1\r\n\r\n",
424
+ "should_keep_alive": true,
425
+ "message_complete_on_eof": false,
426
+ "http_major": 1,
427
+ "http_minor": 1,
428
+ "method": "LINK",
429
+ "query_string": "",
430
+ "fragment": "",
431
+ "request_path": "/test",
432
+ "request_url": "/test",
433
+ "num_headers": 0,
434
+ "headers": {
435
+
436
+ },
437
+ "body": "",
438
+ "strict": true
439
+ },
440
+ {
441
+ "name": "unlink request",
442
+ "type": "HTTP_REQUEST",
443
+ "raw": "UNLINK /test HTTP/1.1\r\n\r\n",
444
+ "should_keep_alive": true,
445
+ "message_complete_on_eof": false,
446
+ "http_major": 1,
447
+ "http_minor": 1,
448
+ "method": "UNLINK",
449
+ "query_string": "",
450
+ "fragment": "",
451
+ "request_path": "/test",
452
+ "request_url": "/test",
453
+ "num_headers": 0,
454
+ "headers": {
455
+
456
+ },
457
+ "body": "",
458
+ "strict": true
459
+ },
460
+ {
461
+ "name": "m-search request",
462
+ "type": "HTTP_REQUEST",
463
+ "raw": "M-SEARCH * HTTP/1.1\r\nHOST: 239.255.255.250:1900\r\nMAN: \"ssdp:discover\"\r\nST: \"ssdp:all\"\r\n\r\n",
464
+ "should_keep_alive": true,
465
+ "message_complete_on_eof": false,
466
+ "http_major": 1,
467
+ "http_minor": 1,
468
+ "method": "M-SEARCH",
469
+ "query_string": "",
470
+ "fragment": "",
471
+ "request_path": "*",
472
+ "request_url": "*",
473
+ "num_headers": 3,
474
+ "headers": {
475
+ "HOST": "239.255.255.250:1900",
476
+ "MAN": "\"ssdp:discover\"",
477
+ "ST": "\"ssdp:all\""
478
+ },
479
+ "body": "",
480
+ "strict": true
481
+ },
482
+ {
483
+ "name": "host terminated by a query string",
484
+ "type": "HTTP_REQUEST",
485
+ "raw": "GET http://hypnotoad.org?hail=all HTTP/1.1\r\n\r\n",
486
+ "should_keep_alive": true,
487
+ "message_complete_on_eof": false,
488
+ "http_major": 1,
489
+ "http_minor": 1,
490
+ "method": "GET",
491
+ "query_string": "hail=all",
492
+ "fragment": "",
493
+ "request_path": "",
494
+ "request_url": "http://hypnotoad.org?hail=all",
495
+ "num_headers": 0,
496
+ "headers": {
497
+
498
+ },
499
+ "body": "",
500
+ "strict": true
501
+ },
502
+ {
503
+ "name": "host:port terminated by a query string",
504
+ "type": "HTTP_REQUEST",
505
+ "raw": "GET http://hypnotoad.org:1234?hail=all HTTP/1.1\r\n\r\n",
506
+ "should_keep_alive": true,
507
+ "message_complete_on_eof": false,
508
+ "http_major": 1,
509
+ "http_minor": 1,
510
+ "method": "GET",
511
+ "query_string": "hail=all",
512
+ "fragment": "",
513
+ "request_path": "",
514
+ "request_url": "http://hypnotoad.org:1234?hail=all",
515
+ "port": 1234,
516
+ "num_headers": 0,
517
+ "headers": {
518
+
519
+ },
520
+ "body": "",
521
+ "strict": true
522
+ },
523
+ {
524
+ "name": "host:port terminated by a space",
525
+ "type": "HTTP_REQUEST",
526
+ "raw": "GET http://hypnotoad.org:1234 HTTP/1.1\r\n\r\n",
527
+ "should_keep_alive": true,
528
+ "message_complete_on_eof": false,
529
+ "http_major": 1,
530
+ "http_minor": 1,
531
+ "method": "GET",
532
+ "query_string": "",
533
+ "fragment": "",
534
+ "request_path": "",
535
+ "request_url": "http://hypnotoad.org:1234",
536
+ "port": 1234,
537
+ "num_headers": 0,
538
+ "headers": {
539
+
540
+ },
541
+ "body": "",
542
+ "strict": true
543
+ },
544
+ {
545
+ "name": "PATCH request",
546
+ "type": "HTTP_REQUEST",
547
+ "raw": "PATCH /file.txt HTTP/1.1\r\nHost: www.example.com\r\nContent-Type: application/example\r\nIf-Match: \"e0023aa4e\"\r\nContent-Length: 10\r\n\r\ncccccccccc",
548
+ "should_keep_alive": true,
549
+ "message_complete_on_eof": false,
550
+ "http_major": 1,
551
+ "http_minor": 1,
552
+ "method": "PATCH",
553
+ "query_string": "",
554
+ "fragment": "",
555
+ "request_path": "/file.txt",
556
+ "request_url": "/file.txt",
557
+ "num_headers": 4,
558
+ "headers": {
559
+ "Host": "www.example.com",
560
+ "Content-Type": "application/example",
561
+ "If-Match": "\"e0023aa4e\"",
562
+ "Content-Length": "10"
563
+ },
564
+ "body": "cccccccccc",
565
+ "strict": true
566
+ },
567
+ {
568
+ "name": "connect caps request",
569
+ "type": "HTTP_REQUEST",
570
+ "raw": "CONNECT HOME0.NETSCAPE.COM:443 HTTP/1.0\r\nUser-agent: Mozilla/1.1N\r\nProxy-authorization: basic aGVsbG86d29ybGQ=\r\n\r\n",
571
+ "should_keep_alive": false,
572
+ "message_complete_on_eof": false,
573
+ "http_major": 1,
574
+ "http_minor": 0,
575
+ "method": "CONNECT",
576
+ "query_string": "",
577
+ "fragment": "",
578
+ "request_path": "",
579
+ "request_url": "HOME0.NETSCAPE.COM:443",
580
+ "num_headers": 2,
581
+ "upgrade": "",
582
+ "headers": {
583
+ "User-agent": "Mozilla/1.1N",
584
+ "Proxy-authorization": "basic aGVsbG86d29ybGQ="
585
+ },
586
+ "body": "",
587
+ "strict": true
588
+ },
589
+ {
590
+ "name": "utf-8 path request",
591
+ "type": "HTTP_REQUEST",
592
+ "strict": false,
593
+ "raw": "GET /δ¶/δt/pope?q=1#narf HTTP/1.1\r\nHost: github.com\r\n\r\n",
594
+ "should_keep_alive": true,
595
+ "message_complete_on_eof": false,
596
+ "http_major": 1,
597
+ "http_minor": 1,
598
+ "method": "GET",
599
+ "query_string": "q=1",
600
+ "fragment": "narf",
601
+ "request_path": "/δ¶/δt/pope",
602
+ "request_url": "/δ¶/δt/pope?q=1#narf",
603
+ "num_headers": 1,
604
+ "headers": {
605
+ "Host": "github.com"
606
+ },
607
+ "body": ""
608
+ },
609
+ {
610
+ "name": "hostname underscore",
611
+ "type": "HTTP_REQUEST",
612
+ "strict": false,
613
+ "raw": "CONNECT home_0.netscape.com:443 HTTP/1.0\r\nUser-agent: Mozilla/1.1N\r\nProxy-authorization: basic aGVsbG86d29ybGQ=\r\n\r\n",
614
+ "should_keep_alive": false,
615
+ "message_complete_on_eof": false,
616
+ "http_major": 1,
617
+ "http_minor": 0,
618
+ "method": "CONNECT",
619
+ "query_string": "",
620
+ "fragment": "",
621
+ "request_path": "",
622
+ "request_url": "home_0.netscape.com:443",
623
+ "num_headers": 2,
624
+ "upgrade": "",
625
+ "headers": {
626
+ "User-agent": "Mozilla/1.1N",
627
+ "Proxy-authorization": "basic aGVsbG86d29ybGQ="
628
+ },
629
+ "body": ""
630
+ }
631
+ ]