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