ffi-http-parser 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
data/.document ADDED
@@ -0,0 +1,3 @@
1
+ -
2
+ ChangeLog.md
3
+ LICENSE.txt
data/.gitignore ADDED
@@ -0,0 +1,2 @@
1
+ doc/
2
+ pkg/
data/.rspec ADDED
@@ -0,0 +1 @@
1
+ --colour --format documentation
data/.yardopts ADDED
@@ -0,0 +1 @@
1
+ --markup markdown --title "FFI::HTTP::Parser Documentation" --protected
data/ChangeLog.md ADDED
@@ -0,0 +1,11 @@
1
+ ### 0.1.0 / 2012-06-23
2
+
3
+ * Initial release:
4
+ * Provides the same API as [http-parser-lite].
5
+ * Supports:
6
+ * Ruby 1.8.7
7
+ * Ruby >= 1.9.1
8
+ * JRuby >= 1.6.7
9
+
10
+ [http-parser]: https://github.com/joyent/http-parser#readme
11
+ [http-parser-lite]: https://github.com/deepfryed/http-parser-lite#readme
data/LICENSE.txt ADDED
@@ -0,0 +1,20 @@
1
+ Copyright (c) 2012 Hal Brodigan
2
+
3
+ Permission is hereby granted, free of charge, to any person obtaining
4
+ a copy of this software and associated documentation files (the
5
+ "Software"), to deal in the Software without restriction, including
6
+ without limitation the rights to use, copy, modify, merge, publish,
7
+ distribute, sublicense, and/or sell copies of the Software, and to
8
+ permit persons to whom the Software is furnished to do so, subject to
9
+ the following conditions:
10
+
11
+ The above copyright notice and this permission notice shall be
12
+ included in all copies or substantial portions of the Software.
13
+
14
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
15
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
16
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
17
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
18
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
19
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
20
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,66 @@
1
+ # ffi-http-parser
2
+
3
+ * [Homepage](https://github.com/postmodern/ffi-http-parser#readme)
4
+ * [Issues](https://github.com/postmodern/ffi-http-parser/issues)
5
+ * [Documentation](http://rubydoc.info/gems/ffi-http-parser/frames)
6
+ * [Email](mailto:postmodern.mod3 at gmail.com)
7
+
8
+ ## Description
9
+
10
+ Ruby FFI bindings to the [http-parser][1] library.
11
+
12
+ ## Features
13
+
14
+ * Provides the same API as [http-parser-lite][2].
15
+ * Supports:
16
+ * Ruby 1.8.7
17
+ * Ruby >= 1.9.1
18
+ * JRuby >= 1.6.7
19
+
20
+ ## Examples
21
+
22
+ require 'ffi/http/parser'
23
+
24
+ parser = FFI::HTTP::Parser.new do |parser|
25
+ parser.on_message_begin do
26
+ puts "message begin"
27
+ end
28
+
29
+ parser.on_message_complete do
30
+ puts "message end"
31
+ end
32
+
33
+ parser.on_url do |data|
34
+ puts "url: #{data}"
35
+ end
36
+
37
+ parser.on_header_field do |data|
38
+ puts "field: #{data}"
39
+ end
40
+
41
+ parser.on_header_value do |data|
42
+ puts "value: #{data}"
43
+ end
44
+
45
+ parser.on_body do |data|
46
+ puts "body: #{data}"
47
+ end
48
+ end
49
+
50
+ ## Requirements
51
+
52
+ * [http-parser](https://github.com/joyent/http-parser#readme) 1.0
53
+ * [ffi](https://github.com/ffi/ffi#readme) ~> 1.0
54
+
55
+ ## Install
56
+
57
+ $ gem install ffi-http-parser
58
+
59
+ ## Copyright
60
+
61
+ Copyright (c) 2012 Hal Brodigan
62
+
63
+ See {file:LICENSE.txt} for details.
64
+
65
+ [1]: https://github.com/joyent/http-parser#readme
66
+ [2]: https://github.com/deepfryed/http-parser-lite#readme
data/Rakefile ADDED
@@ -0,0 +1,40 @@
1
+ # encoding: utf-8
2
+
3
+ require 'rubygems'
4
+ require 'rake'
5
+
6
+ begin
7
+ gem 'rubygems-tasks', '~> 0.2'
8
+ require 'rubygems/tasks'
9
+
10
+ Gem::Tasks.new
11
+ rescue LoadError => e
12
+ warn e.message
13
+ warn "Run `gem install rubygems-tasks` to install 'rubygems/tasks'."
14
+ end
15
+
16
+ begin
17
+ gem 'rspec', '~> 2.4'
18
+ require 'rspec/core/rake_task'
19
+
20
+ RSpec::Core::RakeTask.new
21
+ rescue LoadError => e
22
+ task :spec do
23
+ abort "Please run `gem install rspec` to install RSpec."
24
+ end
25
+ end
26
+
27
+ task :test => :spec
28
+ task :default => :spec
29
+
30
+ begin
31
+ gem 'yard', '~> 0.7'
32
+ require 'yard'
33
+
34
+ YARD::Rake::YardocTask.new
35
+ rescue LoadError => e
36
+ task :yard do
37
+ abort "Please run `gem install yard` to install YARD."
38
+ end
39
+ end
40
+ task :doc => :yard
@@ -0,0 +1,60 @@
1
+ # encoding: utf-8
2
+
3
+ require 'yaml'
4
+
5
+ Gem::Specification.new do |gem|
6
+ gemspec = YAML.load_file('gemspec.yml')
7
+
8
+ gem.name = gemspec.fetch('name')
9
+ gem.version = gemspec.fetch('version') do
10
+ lib_dir = File.join(File.dirname(__FILE__),'lib')
11
+ $LOAD_PATH << lib_dir unless $LOAD_PATH.include?(lib_dir)
12
+
13
+ require 'ffi/http/parser/version'
14
+ FFI::HTTP::Parser::VERSION
15
+ end
16
+
17
+ gem.summary = gemspec['summary']
18
+ gem.description = gemspec['description']
19
+ gem.licenses = Array(gemspec['license'])
20
+ gem.authors = Array(gemspec['authors'])
21
+ gem.email = gemspec['email']
22
+ gem.homepage = gemspec['homepage']
23
+
24
+ glob = lambda { |patterns| gem.files & Dir[*patterns] }
25
+
26
+ gem.files = `git ls-files`.split($/)
27
+ gem.files = glob[gemspec['files']] if gemspec['files']
28
+
29
+ gem.executables = gemspec.fetch('executables') do
30
+ glob['bin/*'].map { |path| File.basename(path) }
31
+ end
32
+ gem.default_executable = gem.executables.first if Gem::VERSION < '1.7.'
33
+
34
+ gem.extensions = glob[gemspec['extensions'] || 'ext/**/extconf.rb']
35
+ gem.test_files = glob[gemspec['test_files'] || '{test/{**/}*_test.rb']
36
+ gem.extra_rdoc_files = glob[gemspec['extra_doc_files'] || '*.{txt,md}']
37
+
38
+ gem.require_paths = Array(gemspec.fetch('require_paths') {
39
+ %w[ext lib].select { |dir| File.directory?(dir) }
40
+ })
41
+
42
+ gem.requirements = gemspec['requirements']
43
+ gem.required_ruby_version = gemspec['required_ruby_version']
44
+ gem.required_rubygems_version = gemspec['required_rubygems_version']
45
+ gem.post_install_message = gemspec['post_install_message']
46
+
47
+ split = lambda { |string| string.split(/,\s*/) }
48
+
49
+ if gemspec['dependencies']
50
+ gemspec['dependencies'].each do |name,versions|
51
+ gem.add_dependency(name,split[versions])
52
+ end
53
+ end
54
+
55
+ if gemspec['development_dependencies']
56
+ gemspec['development_dependencies'].each do |name,versions|
57
+ gem.add_development_dependency(name,split[versions])
58
+ end
59
+ end
60
+ end
data/gemspec.yml ADDED
@@ -0,0 +1,17 @@
1
+ name: ffi-http-parser
2
+ summary: FFI bindings for http-parser
3
+ description: Ruby FFI bindings to the http-parser library.
4
+ license: MIT
5
+ authors: Postmodern
6
+ email: postmodern.mod3@gmail.com
7
+ homepage: https://github.com/postmodern/ffi-http-parser#readme
8
+
9
+ requirements: http-parser 1.0
10
+
11
+ dependencies:
12
+ ffi: ~> 1.0
13
+
14
+ development_dependencies:
15
+ rubygems-tasks: ~> 0.2
16
+ rspec: ~> 2.4
17
+ yard: ~> 0.7
@@ -0,0 +1,2 @@
1
+ require 'ffi/http/parser/parser'
2
+ require 'ffi/http/parser/version'
@@ -0,0 +1,398 @@
1
+ require 'ffi/http/parser/parser'
2
+ require 'ffi/http/parser/settings'
3
+
4
+ require 'ffi'
5
+
6
+ module FFI
7
+ module HTTP
8
+ module Parser
9
+ class Instance < FFI::Struct
10
+
11
+ layout :type_flags, :uchar,
12
+ :state, :uchar,
13
+ :header_state, :uchar,
14
+ :index, :uchar,
15
+
16
+ :nread, :uint32,
17
+ :content_length, :int64,
18
+
19
+ # READ-ONLY
20
+ :http_major, :ushort,
21
+ :http_minor, :ushort,
22
+ :status_code, :ushort, # responses only
23
+ :method, :uchar, # requests only
24
+
25
+ # 1 = Upgrade header was present and the parser has exited because of that.
26
+ # 0 = No upgrade header present.
27
+ #
28
+ # Should be checked when http_parser_execute() returns in addition to
29
+ # error checking.
30
+ :upgrade, :char,
31
+
32
+ # PUBLIC
33
+ :data, :pointer
34
+
35
+ # The parser type (`:request`, `:response` or `:both`)
36
+ attr_accessor :type
37
+
38
+ #
39
+ # Initializes the Parser instance.
40
+ #
41
+ # @param [FFI::Pointer] ptr
42
+ # Optional pointer to an existing `http_parser` struct.
43
+ #
44
+ def initialize(ptr=nil)
45
+ if ptr then super(ptr)
46
+ else super()
47
+ end
48
+
49
+ @settings = Settings.new
50
+
51
+ yield self if block_given?
52
+
53
+ Parser.http_parser_init(self,type) unless ptr
54
+ end
55
+
56
+ #
57
+ # Registers an `on_message_begin` callback.
58
+ #
59
+ # @yield []
60
+ # The given block will be called when the HTTP message begins.
61
+ #
62
+ def on_message_begin(&block)
63
+ @settings[:on_message_begin] = wrap_callback(block)
64
+ end
65
+
66
+ #
67
+ # Registers an `on_path` callback.
68
+ #
69
+ # @yield [path]
70
+ # The given block will be called when the path is recognized within
71
+ # the Request URI.
72
+ #
73
+ # @yieldparam [String] path
74
+ # The recognized URI path.
75
+ #
76
+ # @see http://www.w3.org/Protocols/rfc2616/rfc2616-sec5.html#sec5.1.2
77
+ #
78
+ def on_path(&block)
79
+ @settings[:on_path] = wrap_data_callback(block)
80
+ end
81
+
82
+ #
83
+ # Registers an `on_query_string` callback.
84
+ #
85
+ # @yield [query]
86
+ # The given block will be called when the query-string is recognized
87
+ # within the Request URI.
88
+ #
89
+ # @yieldparam [String] query
90
+ # The recognized URI query-string.
91
+ #
92
+ # @see http://www.w3.org/Protocols/rfc2616/rfc2616-sec5.html#sec5.1.2
93
+ #
94
+ def on_query_string(&block)
95
+ @settings[:on_query_string] = wrap_data_callback(block)
96
+ end
97
+
98
+ #
99
+ # Registers an `on_fragment` callback.
100
+ #
101
+ # @yield [fragment]
102
+ # The given block will be called when the fragment is recognized
103
+ # within the Request URI.
104
+ #
105
+ # @yieldparam [String] fragment
106
+ # The recognized URI fragment.
107
+ #
108
+ # @see http://www.w3.org/Protocols/rfc2616/rfc2616-sec5.html#sec5.1.2
109
+ #
110
+ def on_fragment(&block)
111
+ @settings[:on_fragment] = wrap_data_callback(block)
112
+ end
113
+
114
+ #
115
+ # Registers an `on_url` callback.
116
+ #
117
+ # @yield [url]
118
+ # The given block will be called when the Request URI is recognized
119
+ # within the Request-Line.
120
+ #
121
+ # @yieldparam [String] url
122
+ # The recognized Request URI.
123
+ #
124
+ # @see http://www.w3.org/Protocols/rfc2616/rfc2616-sec5.html#sec5.1.2
125
+ #
126
+ def on_url(&block)
127
+ @settings[:on_url] = wrap_data_callback(block)
128
+ end
129
+
130
+ #
131
+ # Registers an `on_header_field` callback.
132
+ #
133
+ # @yield [field]
134
+ # The given block will be called when a Header name is recognized
135
+ # in the Headers.
136
+ #
137
+ # @yieldparam [String] field
138
+ # A recognized Header name.
139
+ #
140
+ # @see http://www.w3.org/Protocols/rfc2616/rfc2616-sec4.html#sec4.5
141
+ #
142
+ def on_header_field(&block)
143
+ @settings[:on_header_field] = wrap_data_callback(block)
144
+ end
145
+
146
+ #
147
+ # Registers an `on_header_value` callback.
148
+ #
149
+ # @yield [value]
150
+ # The given block will be called when a Header value is recognized
151
+ # in the Headers.
152
+ #
153
+ # @yieldparam [String] value
154
+ # A recognized Header value.
155
+ #
156
+ # @see http://www.w3.org/Protocols/rfc2616/rfc2616-sec4.html#sec4.5
157
+ #
158
+ def on_header_value(&block)
159
+ @settings[:on_header_value] = wrap_data_callback(block)
160
+ end
161
+
162
+ #
163
+ # Registers an `on_headers_complete` callback.
164
+ #
165
+ # @yield []
166
+ # The given block will be called when the Headers stop.
167
+ #
168
+ def on_headers_complete(&block)
169
+ @settings[:on_headers_complete] = proc { |parser|
170
+ case block.call()
171
+ when :error then -1
172
+ when :stop then 1
173
+ else 0
174
+ end
175
+ }
176
+ end
177
+
178
+ #
179
+ # Registers an `on_body` callback.
180
+ #
181
+ # @yield [body]
182
+ # The given block will be called when the body is recognized in the
183
+ # message body.
184
+ #
185
+ # @yieldparam [String] body
186
+ # The full body or a chunk of the body from a chunked
187
+ # Transfer-Encoded stream.
188
+ #
189
+ # @see http://www.w3.org/Protocols/rfc2616/rfc2616-sec4.html#sec4.5
190
+ #
191
+ def on_body(&block)
192
+ @settings[:on_body] = wrap_data_callback(block)
193
+ end
194
+
195
+ #
196
+ # Registers an `on_message_begin` callback.
197
+ #
198
+ # @yield []
199
+ # The given block will be called when the message completes.
200
+ #
201
+ def on_message_complete(&block)
202
+ @settings[:on_message_complete] = wrap_callback(block)
203
+ end
204
+
205
+ #
206
+ # Parses data.
207
+ #
208
+ # @param [String] data
209
+ # The data to parse.
210
+ #
211
+ # @return [Integer]
212
+ # The number of bytes parsed. `0` will be returned if the parser
213
+ # encountered an error.
214
+ #
215
+ def parse(data)
216
+ Parser.http_parser_execute(self,@settings,data,data.length)
217
+ end
218
+
219
+ #
220
+ # Parses data.
221
+ #
222
+ # @param [String] data
223
+ # The data to parse.
224
+ #
225
+ # @return [Instance]
226
+ # The Instance parser.
227
+ #
228
+ def <<(data)
229
+ parse(data)
230
+ return self
231
+ end
232
+
233
+ #
234
+ # Resets the parser.
235
+ #
236
+ def reset!
237
+ Parser.http_parser_init(self,type)
238
+ end
239
+
240
+ alias reset reset!
241
+
242
+ #
243
+ # The type of the parser.
244
+ #
245
+ # @return [:request, :response, :both]
246
+ # The parser type.
247
+ #
248
+ def type
249
+ TYPES[self[:type_flags] & 0x3]
250
+ end
251
+
252
+ #
253
+ # Sets the type of the parser.
254
+ #
255
+ # @param [:request, :response, :both] new_type
256
+ # The new parser type.
257
+ #
258
+ def type=(new_type)
259
+ self[:type_flags] = ((flags << 2) | TYPES[new_type])
260
+ end
261
+
262
+ #
263
+ # Flags for the parser.
264
+ #
265
+ # @return [Integer]
266
+ # Parser flags.
267
+ #
268
+ def flags
269
+ (self[:type_flags] & 0xfc) >> 2
270
+ end
271
+
272
+ #
273
+ # The parsed HTTP major version number.
274
+ #
275
+ # @return [Integer]
276
+ # The HTTP major version number.
277
+ #
278
+ def http_major
279
+ self[:http_major]
280
+ end
281
+
282
+ #
283
+ # The parsed HTTP minor version number.
284
+ #
285
+ # @return [Integer]
286
+ # The HTTP minor version number.
287
+ #
288
+ def http_minor
289
+ self[:http_minor]
290
+ end
291
+
292
+ #
293
+ # The parsed HTTP version.
294
+ #
295
+ # @return [String]
296
+ # The HTTP version.
297
+ #
298
+ def http_version
299
+ "%d.%d" % [self[:http_major], self[:http_minor]]
300
+ end
301
+
302
+ #
303
+ # The parsed HTTP response Status Code.
304
+ #
305
+ # @return [Integer]
306
+ # The HTTP Status Code.
307
+ #
308
+ # @see http://www.w3.org/Protocols/rfc2616/rfc2616-sec6.html#sec6.1.1
309
+ #
310
+ def http_status
311
+ self[:status_code]
312
+ end
313
+
314
+ #
315
+ # The parsed HTTP Method.
316
+ #
317
+ # @return [Symbol]
318
+ # The HTTP Method name.
319
+ #
320
+ # @see http://www.w3.org/Protocols/rfc2616/rfc2616-sec5.html#sec5.1.1
321
+ #
322
+ def http_method
323
+ METHODS[self[:method]]
324
+ end
325
+
326
+ #
327
+ # Determines whether the `Upgrade` header has been parsed.
328
+ #
329
+ # @return [Boolean]
330
+ # Specifies whether the `Upgrade` header has been seen.
331
+ #
332
+ # @see http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html#sec14.42
333
+ #
334
+ def upgrade?
335
+ self[:upgrade] == 1
336
+ end
337
+
338
+ #
339
+ # Additional data attached to the parser.
340
+ #
341
+ # @return [FFI::Pointer]
342
+ # Pointer to the additional data.
343
+ #
344
+ def data
345
+ self[:data]
346
+ end
347
+
348
+ #
349
+ # Determines whether the `Connection: keep-alive` header has been
350
+ # parsed.
351
+ #
352
+ # @return [Boolean]
353
+ # Specifies whether the Connection should be kept alive.
354
+ #
355
+ # @see http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html#sec14.10
356
+ #
357
+ def keep_alive?
358
+ Parser.http_should_keep_alive(self) > 0
359
+ end
360
+
361
+ protected
362
+
363
+ #
364
+ # Wraps a callback, so if it returns `:error`, `-1` will be returned.
365
+ # `0` will be returned by default.
366
+ #
367
+ # @param [Proc] callback
368
+ # The callback to wrap.
369
+ #
370
+ # @return [Proc]
371
+ # The wrapped callback.
372
+ #
373
+ def wrap_callback(callback)
374
+ proc { |parser| (callback.call() == :error) ? -1 : 0 }
375
+ end
376
+
377
+ #
378
+ # Wraps a data callback, so if it returns `:error`, `-1` will be
379
+ # returned. `0` will be returned by default.
380
+ #
381
+ # @param [Proc] callback
382
+ # The callback to wrap.
383
+ #
384
+ # @return [Proc]
385
+ # The wrapped callback.
386
+ #
387
+ def wrap_data_callback(callback)
388
+ proc { |parser,buffer,length|
389
+ data = buffer.get_bytes(0,length)
390
+
391
+ (callback.call(data) == :error) ? -1 : 0
392
+ }
393
+ end
394
+
395
+ end
396
+ end
397
+ end
398
+ end