ffi-http-parser 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,31 @@
1
+ require 'ffi/http/parser/types'
2
+ require 'ffi/http/parser/instance'
3
+
4
+ module FFI
5
+ module HTTP
6
+ module Parser
7
+ extend FFI::Library
8
+
9
+ ffi_lib ['http_parser', 'http_parser.so.1']
10
+
11
+ attach_function :http_parser_init, [:pointer, :http_parser_type], :void
12
+ attach_function :http_parser_execute, [:pointer, :pointer, :pointer, :size_t], :size_t
13
+
14
+ attach_function :http_should_keep_alive, [:pointer], :int
15
+ attach_function :http_method_str, [:http_method], :string
16
+
17
+ #
18
+ # Creates a new Parser.
19
+ #
20
+ # @return [Instance]
21
+ # A new parser instance.
22
+ #
23
+ # @see Instance
24
+ #
25
+ def self.new(&block)
26
+ Instance.new(&block)
27
+ end
28
+
29
+ end
30
+ end
31
+ end
@@ -0,0 +1,22 @@
1
+ require 'ffi/http/parser/types'
2
+
3
+ module FFI
4
+ module HTTP
5
+ module Parser
6
+ class Settings < FFI::Struct
7
+
8
+ layout :on_message_begin, :http_cb,
9
+ :on_path, :http_data_cb,
10
+ :on_query_string, :http_data_cb,
11
+ :on_url, :http_data_cb,
12
+ :on_fragment, :http_data_cb,
13
+ :on_header_field, :http_data_cb,
14
+ :on_header_value, :http_data_cb,
15
+ :on_headers_complete, :http_cb,
16
+ :on_body, :http_data_cb,
17
+ :on_message_complete, :http_cb
18
+
19
+ end
20
+ end
21
+ end
22
+ end
@@ -0,0 +1,50 @@
1
+ require 'ffi'
2
+
3
+ module FFI
4
+ module HTTP
5
+ module Parser
6
+ extend FFI::Library
7
+
8
+ # Maximum header size
9
+ HTTP_MAX_HEADER_SIZE = (80 * 1024)
10
+
11
+ callback :http_data_cb, [:pointer, :pointer, :size_t], :int
12
+ callback :http_cb, [:pointer], :int
13
+
14
+ # HTTP Methods
15
+ METHODS = enum :http_method, [
16
+ :DELETE,
17
+ :GET,
18
+ :HEAD,
19
+ :POST,
20
+ :PUT,
21
+ # pathological
22
+ :CONNECT,
23
+ :OPTIONS,
24
+ :TRACE,
25
+ # webdav
26
+ :COPY,
27
+ :LOCK,
28
+ :MKCOL,
29
+ :MOVE,
30
+ :PROPFIND,
31
+ :PROPPATCH,
32
+ :UNLOCK,
33
+ # subversion
34
+ :REPORT,
35
+ :MKACTIVITY,
36
+ :CHECKOUT,
37
+ :MERGE,
38
+ # upnp
39
+ :MSEARCH,
40
+ :NOTIFY,
41
+ :SUBSCRIBE,
42
+ :UNSUBSCRIBE
43
+ ]
44
+
45
+ # HTTP Parser types
46
+ TYPES = enum :http_parser_type, [:request, :response, :both]
47
+
48
+ end
49
+ end
50
+ end
@@ -0,0 +1,10 @@
1
+ require 'ffi'
2
+
3
+ module FFI
4
+ module HTTP
5
+ module Parser
6
+ # ffi-http-parser version
7
+ VERSION = "0.1.0"
8
+ end
9
+ end
10
+ end
@@ -0,0 +1,26 @@
1
+ shared_examples_for "callback" do |callback_pair|
2
+ context "when it returns :error" do
3
+ subject do
4
+ callback, next_callback = callback_pair.to_a.first
5
+
6
+ described_class.new do |parser|
7
+ parser.send(callback) { |*args| :error }
8
+
9
+ parser.send(next_callback) { @called = true }
10
+ end
11
+ end
12
+
13
+ it "should stop the parser" do
14
+ subject << "POST /path?q=1#fragment HTTP/1.1\r\n"
15
+ subject << "Transfer-Encoding: chunked\r\n"
16
+ subject << "\r\n"
17
+
18
+ subject << "4\r\n"
19
+ subject << "Body\r\n"
20
+
21
+ subject << "0\r\n"
22
+
23
+ @called.should_not be_true
24
+ end
25
+ end
26
+ end
@@ -0,0 +1,458 @@
1
+ require 'spec_helper'
2
+ require 'callback_examples'
3
+
4
+ require 'ffi/http/parser/instance'
5
+
6
+ describe Instance do
7
+ describe "#initialize" do
8
+ context "when initialized from a pointer" do
9
+ it "should not call http_parser_init" do
10
+ ptr = described_class.new.to_ptr
11
+
12
+ FFI::HTTP::Parser.should_not_receive(:http_parser_init)
13
+
14
+ described_class.new(ptr)
15
+ end
16
+ end
17
+
18
+ context "when given a block" do
19
+ it "should yield the new Instance" do
20
+ expected = nil
21
+
22
+ described_class.new { |parser| expected = parser }
23
+
24
+ expected.should be_kind_of(described_class)
25
+ end
26
+
27
+ it "should allow changing the parser type" do
28
+ parser = described_class.new do |parser|
29
+ parser.type = :both
30
+ end
31
+
32
+ parser.type.should == :both
33
+ end
34
+ end
35
+ end
36
+
37
+ describe "#type" do
38
+ it "should default to :request" do
39
+ subject.type.should == :request
40
+ end
41
+
42
+ it "should convert the type to a Symbol" do
43
+ subject[:type_flags] = TYPES[:both]
44
+
45
+ subject.type.should == :both
46
+ end
47
+
48
+ it "should extract the type from the type_flags field" do
49
+ subject[:type_flags] = ((0xff & ~0x3) | TYPES[:both])
50
+
51
+ subject.type.should == :both
52
+ end
53
+ end
54
+
55
+ describe "#type=" do
56
+ it "should set the type" do
57
+ subject.type = :both
58
+
59
+ subject.type.should == :both
60
+ end
61
+
62
+ it "should not change flags" do
63
+ flags = (0xff & ~0x3)
64
+ subject[:type_flags] = flags
65
+
66
+ subject.type = :both
67
+
68
+ subject[:type_flags].should == (flags | TYPES[:both])
69
+ end
70
+ end
71
+
72
+ describe "#<<" do
73
+ it "should call http_parser_execute" do
74
+ FFI::HTTP::Parser.should_receive(:http_parser_execute)
75
+
76
+ subject << "GET / HTTP/1.1\r\n"
77
+ end
78
+ end
79
+
80
+ describe "callbacks" do
81
+ describe "on_message_begin" do
82
+ include_examples "callback", {:on_message_begin => :on_path}
83
+
84
+ subject do
85
+ described_class.new do |parser|
86
+ parser.on_message_begin { @begun = true }
87
+ end
88
+ end
89
+
90
+ it "should trigger on a new request" do
91
+ subject << "GET / HTTP/1.1"
92
+
93
+ @begun.should be_true
94
+ end
95
+ end
96
+
97
+ describe "on_path" do
98
+ include_examples "callback", {:on_path => :on_query_string}
99
+
100
+ let(:expected) { '/foo' }
101
+
102
+ subject do
103
+ described_class.new do |parser|
104
+ parser.on_path { |data| @path = data }
105
+ end
106
+ end
107
+
108
+ it "should pass the recognized path" do
109
+ subject << "GET "
110
+
111
+ @path.should be_nil
112
+
113
+ subject << "#{expected} HTTP/1.1"
114
+
115
+ @path.should == expected
116
+ end
117
+ end
118
+
119
+ describe "on_query_string" do
120
+ include_examples "callback", {:on_query_string => :on_fragment}
121
+
122
+ let(:expected) { 'x=1&y=2' }
123
+
124
+ subject do
125
+ described_class.new do |parser|
126
+ parser.on_query_string { |data| @query_string = data }
127
+ end
128
+ end
129
+
130
+ it "should pass the recognized query_string" do
131
+ subject << "GET /foo"
132
+
133
+ @query_string.should be_nil
134
+
135
+ subject << "?#{expected} HTTP/1.1"
136
+
137
+ @query_string.should == expected
138
+ end
139
+ end
140
+
141
+ describe "on_fragment" do
142
+ include_examples "callback", {:on_fragment => :on_header_field}
143
+
144
+ let(:expected) { 'bar' }
145
+
146
+ subject do
147
+ described_class.new do |parser|
148
+ parser.on_fragment { |data| @fragment = data }
149
+ end
150
+ end
151
+
152
+ it "should pass the recognized fragment" do
153
+ subject << "GET /foo"
154
+
155
+ @fragment.should be_nil
156
+
157
+ subject << "##{expected} HTTP/1.1"
158
+
159
+ @fragment.should == expected
160
+ end
161
+ end
162
+
163
+ describe "on_url" do
164
+ include_examples "callback", {:on_url => :on_header_field}
165
+
166
+ let(:expected) { '/foo?q=1' }
167
+
168
+ subject do
169
+ described_class.new do |parser|
170
+ parser.on_url { |data| @url = data }
171
+ end
172
+ end
173
+
174
+ it "should pass the recognized url" do
175
+ subject << "GET "
176
+
177
+ @url.should be_nil
178
+
179
+ subject << "#{expected} HTTP/1.1"
180
+
181
+ @url.should == expected
182
+ end
183
+ end
184
+
185
+ describe "on_header_field" do
186
+ include_examples "callback", {:on_header_field => :on_header_value}
187
+
188
+ let(:expected) { 'Host' }
189
+
190
+ subject do
191
+ described_class.new do |parser|
192
+ parser.on_header_field { |data| @header_field = data }
193
+ end
194
+ end
195
+
196
+ it "should pass the recognized header-name" do
197
+ subject << "GET /foo HTTP/1.1\r\n"
198
+
199
+ @header_field.should be_nil
200
+
201
+ subject << "#{expected}: example.com\r\n"
202
+
203
+ @header_field.should == expected
204
+ end
205
+ end
206
+
207
+ describe "on_header_value" do
208
+ include_examples "callback", {:on_header_value => :on_body}
209
+
210
+ let(:expected) { 'example.com' }
211
+
212
+ subject do
213
+ described_class.new do |parser|
214
+ parser.on_header_value { |data| @header_value = data }
215
+ end
216
+ end
217
+
218
+ it "should pass the recognized header-value" do
219
+ subject << "GET /foo HTTP/1.1\r\n"
220
+
221
+ @header_value.should be_nil
222
+
223
+ subject << "Host: #{expected}\r\n"
224
+
225
+ @header_value.should == expected
226
+ end
227
+ end
228
+
229
+ describe "on_headers_complete" do
230
+ include_examples "callback", {:on_headers_complete => :on_body}
231
+
232
+ subject do
233
+ described_class.new do |parser|
234
+ parser.on_headers_complete { @header_complete = true }
235
+ end
236
+ end
237
+
238
+ it "should trigger on the last header" do
239
+ subject << "GET / HTTP/1.1\r\n"
240
+ subject << "Host: example.com\r\n"
241
+
242
+ @header_complete.should be_nil
243
+
244
+ subject << "\r\n"
245
+
246
+ @header_complete.should be_true
247
+ end
248
+
249
+ context "when the callback returns :stop" do
250
+ subject do
251
+ described_class.new do |parser|
252
+ parser.on_headers_complete { :stop }
253
+
254
+ parser.on_body { |data| @body = data }
255
+ end
256
+ end
257
+
258
+ it "should indicate there is no request body to parse" do
259
+ subject << "GET / HTTP/1.1\r\n"
260
+ subject << "Host: example.com\r\n"
261
+ subject << "\r\n"
262
+ subject << "Body"
263
+
264
+ @body.should be_nil
265
+ end
266
+ end
267
+ end
268
+
269
+ describe "on_body" do
270
+ include_examples "callback", {:on_body => :on_message_complete}
271
+
272
+ let(:expected) { "Body" }
273
+
274
+ subject do
275
+ described_class.new do |parser|
276
+ parser.on_body { |data| @body = data }
277
+ end
278
+ end
279
+
280
+ it "should trigger on the body" do
281
+ subject << "POST / HTTP/1.1\r\n"
282
+ subject << "Transfer-Encoding: chunked\r\n"
283
+ subject << "\r\n"
284
+
285
+ @body.should be_nil
286
+
287
+ subject << "#{"%x" % expected.length}\r\n"
288
+ subject << expected
289
+
290
+ @body.should == expected
291
+ end
292
+ end
293
+
294
+ describe "on_message_complete" do
295
+ subject do
296
+ described_class.new do |parser|
297
+ parser.on_message_complete { @message_complete = true }
298
+ end
299
+ end
300
+
301
+ it "should trigger at the end of the message" do
302
+ subject << "GET / HTTP/1.1\r\n"
303
+
304
+ @message_complete.should be_nil
305
+
306
+ subject << "Host: example.com\r\n\r\n"
307
+
308
+ @message_complete.should be_true
309
+ end
310
+ end
311
+ end
312
+
313
+ describe "#reset!" do
314
+ it "should call http_parser_init" do
315
+ parser = described_class.new
316
+
317
+ FFI::HTTP::Parser.should_receive(:http_parser_init)
318
+
319
+ parser.reset!
320
+ end
321
+
322
+ it "should not change the type" do
323
+ parser = described_class.new do |parser|
324
+ parser.type = :both
325
+ end
326
+
327
+ parser.reset!
328
+
329
+ parser.type.should == :both
330
+ end
331
+ end
332
+
333
+ describe "#http_method" do
334
+ let(:expected) { :POST }
335
+
336
+ it "should set the http_method field" do
337
+ subject << "#{expected} / HTTP/1.1\r\n"
338
+
339
+ subject.http_method.should == expected
340
+ end
341
+ end
342
+
343
+ describe "#http_major" do
344
+ let(:expected) { 1 }
345
+
346
+ context "when parsing requests" do
347
+ it "should set the http_major field" do
348
+ subject << "GET / HTTP/#{expected}."
349
+
350
+ subject.http_major.should == expected
351
+ end
352
+ end
353
+
354
+ context "when parsing responses" do
355
+ subject do
356
+ described_class.new do |parser|
357
+ parser.type = :response
358
+ end
359
+ end
360
+
361
+ it "should set the http_major field" do
362
+ subject << "HTTP/#{expected}."
363
+
364
+ subject.http_major.should == expected
365
+ end
366
+ end
367
+ end
368
+
369
+ describe "#http_minor" do
370
+ let(:expected) { 2 }
371
+
372
+ context "when parsing requests" do
373
+ it "should set the http_minor field" do
374
+ subject << "GET / HTTP/1.#{expected}\r\n"
375
+
376
+ subject.http_minor.should == expected
377
+ end
378
+ end
379
+
380
+ context "when parsing responses" do
381
+ subject do
382
+ described_class.new do |parser|
383
+ parser.type = :response
384
+ end
385
+ end
386
+
387
+ it "should set the http_major field" do
388
+ subject << "HTTP/1.#{expected} "
389
+
390
+ subject.http_minor.should == expected
391
+ end
392
+ end
393
+ end
394
+
395
+ describe "#http_version" do
396
+ let(:expected) { '1.1' }
397
+
398
+ before do
399
+ subject << "GET / HTTP/#{expected}\r\n"
400
+ end
401
+
402
+ it "should combine #http_major and #http_minor" do
403
+ subject.http_version.should == expected
404
+ end
405
+ end
406
+
407
+ describe "#http_status" do
408
+ context "when parsing requests" do
409
+ before do
410
+ subject << "GET / HTTP/1.1\r\n"
411
+ subject << "Host: example.com\r\n"
412
+ subject << "\r\n"
413
+ end
414
+
415
+ it "should not be set" do
416
+ subject.http_status.should be_zero
417
+ end
418
+ end
419
+
420
+ context "when parsing responses" do
421
+ let(:expected) { 200 }
422
+
423
+ subject do
424
+ described_class.new do |parser|
425
+ parser.type = :response
426
+ end
427
+ end
428
+
429
+ before do
430
+ subject << "HTTP/1.1 #{expected} OK\r\n"
431
+ subject << "Location: http://example.com/\r\n"
432
+ subject << "\r\n"
433
+ end
434
+
435
+ it "should set the http_status field" do
436
+ subject.http_status.should == expected
437
+ end
438
+ end
439
+ end
440
+
441
+ describe "#upgrade?" do
442
+ let(:upgrade) { 'WebSocket' }
443
+
444
+ before do
445
+ subject << "GET /demo HTTP/1.1\r\n"
446
+ subject << "Upgrade: #{upgrade}\r\n"
447
+ subject << "Connection: Upgrade\r\n"
448
+ subject << "Host: example.com\r\n"
449
+ subject << "Origin: http://example.com\r\n"
450
+ subject << "WebSocket-Protocol: sample\r\n"
451
+ subject << "\r\n"
452
+ end
453
+
454
+ it "should return true if the Upgrade header was set" do
455
+ subject.upgrade?.should be_true
456
+ end
457
+ end
458
+ end