ffi-http-parser 0.1.0

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.
@@ -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