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
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: 25f61a99e110d77bed323a6a79233dd6d3e6cf21
4
+ data.tar.gz: 19b1528911bb14d4f4345665831823e23f344698
5
+ SHA512:
6
+ metadata.gz: 64c0568a7e8a52a3bb9247b248d7ce28efdc21b20fd59dcd36a055ca0cf4ae547abe46f154a061e8f38b0b992fd97c28da44ffc305219529d02d1f48ef8db9ca
7
+ data.tar.gz: 27122c720af64647404aef664076bacc01162d58bbaf65c8b08bf0745983c4f3f7174abff95d307b6c87da4a67dd799c6011ffcea99a4e5fed58ba30fddc3f16
data/.gitignore ADDED
@@ -0,0 +1,12 @@
1
+ *.bundle
2
+ *.bundle
3
+ *.gem
4
+ *.jar
5
+ *.o
6
+ *.rbc
7
+ *.so
8
+ *.swp
9
+ Gemfile.lock
10
+ Makefile
11
+ tags
12
+ tmp
data/.gitmodules ADDED
@@ -0,0 +1,6 @@
1
+ [submodule "http-parser"]
2
+ path = ext/ruby_http_parser/vendor/http-parser
3
+ url = git://github.com/joyent/http-parser.git
4
+ [submodule "http-parser-java"]
5
+ path = ext/ruby_http_parser/vendor/http-parser-java
6
+ url=git://github.com/heckpsi-lab/http-parser.java
data/.travis.yml ADDED
@@ -0,0 +1,33 @@
1
+ language: ruby
2
+ rvm: 2.1.0
3
+ sudo: false
4
+ matrix:
5
+ include:
6
+ - rvm: 2.2.6
7
+ - rvm: 2.3.3
8
+ - rvm: 2.4.0
9
+ - rvm: ruby-head
10
+ - rvm: jruby-9.0.4.0
11
+ jdk: oraclejdk7
12
+ - rvm: jruby-9.0.4.0
13
+ jdk: oraclejdk8
14
+ - rvm: jruby-9.0.4.0
15
+ jdk: openjdk7
16
+ - rvm: jruby-head
17
+ jdk: oraclejdk7
18
+ - rvm: jruby-head
19
+ jdk: oraclejdk8
20
+ - rvm: jruby-head
21
+ jdk: openjdk7
22
+ allow_failures:
23
+ - rvm: jruby-head
24
+ - rvm: ruby-head
25
+
26
+ os:
27
+ - linux
28
+
29
+ before_install:
30
+ - gem install bundler
31
+
32
+ notifications:
33
+ email: false
data/Gemfile ADDED
@@ -0,0 +1,2 @@
1
+ source 'https://rubygems.org'
2
+ gemspec
data/LICENSE-MIT ADDED
@@ -0,0 +1,20 @@
1
+ Copyright 2009,2010 Marc-André Cournoyer <macournoyer@gmail.com>
2
+ Copyright 2010,2011 Aman Gupta <aman@tmm1.net>
3
+
4
+ Permission is hereby granted, free of charge, to any person obtaining a copy
5
+ of this software and associated documentation files (the "Software"), to
6
+ deal in the Software without restriction, including without limitation the
7
+ rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
8
+ sell copies of the Software, and to permit persons to whom the Software is
9
+ furnished to do so, subject to the following conditions:
10
+
11
+ The above copyright notice and this permission notice shall be included in
12
+ all copies or substantial portions of the Software.
13
+
14
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
15
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
16
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
17
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
18
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
19
+ FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
20
+ IN THE SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,90 @@
1
+ # http_parser.rb
2
+
3
+ A simple callback-based HTTP request/response parser for writing http
4
+ servers, clients and proxies.
5
+
6
+ This gem is built on top of [joyent/http-parser](https://github.com/joyent/http-parser) and its java port [http-parser/http-parser.java](https://github.com/http-parser/http-parser.java).
7
+
8
+ ## Supported Platforms
9
+
10
+ This gem aims to work on all major Ruby platforms, including:
11
+
12
+ - MRI 1.8, 1.9 and 2.0
13
+ - Rubinius
14
+ - JRuby
15
+ - win32
16
+
17
+ ## Usage
18
+
19
+ ```ruby
20
+ require "http/parser"
21
+
22
+ parser = Http::Parser.new
23
+
24
+ parser.on_headers_complete = proc do
25
+ p parser.http_version
26
+
27
+ p parser.http_method # for requests
28
+ p parser.request_url
29
+
30
+ p parser.status_code # for responses
31
+
32
+ p parser.headers
33
+ end
34
+
35
+ parser.on_body = proc do |chunk|
36
+ # One chunk of the body
37
+ p chunk
38
+ end
39
+
40
+ parser.on_message_complete = proc do |env|
41
+ # Headers and body is all parsed
42
+ puts "Done!"
43
+ end
44
+ ```
45
+
46
+ # Feed raw data from the socket to the parser
47
+ `parser << raw_data`
48
+
49
+ ## Advanced Usage
50
+
51
+ ### Accept callbacks on an object
52
+
53
+ ```ruby
54
+ module MyHttpConnection
55
+ def connection_completed
56
+ @parser = Http::Parser.new(self)
57
+ end
58
+
59
+ def receive_data(data)
60
+ @parser << data
61
+ end
62
+
63
+ def on_message_begin
64
+ @headers = nil
65
+ @body = ''
66
+ end
67
+
68
+ def on_headers_complete(headers)
69
+ @headers = headers
70
+ end
71
+
72
+ def on_body(chunk)
73
+ @body << chunk
74
+ end
75
+
76
+ def on_message_complete
77
+ p [@headers, @body]
78
+ end
79
+ end
80
+ ```
81
+
82
+ ### Stop parsing after headers
83
+
84
+ ```ruby
85
+ parser = Http::Parser.new
86
+ parser.on_headers_complete = proc{ :stop }
87
+
88
+ offset = parser << request_data
89
+ body = request_data[offset..-1]
90
+ ```
data/Rakefile ADDED
@@ -0,0 +1,6 @@
1
+ # load tasks
2
+ Dir['tasks/*.rake'].sort.each { |f| load f }
3
+
4
+ # default task
5
+ task :compile => :submodules
6
+ task :default => [:compile, :spec]
@@ -0,0 +1,23 @@
1
+ #!/usr/bin/env ruby
2
+ $:.unshift File.dirname(__FILE__) + "/../lib"
3
+ require "rubygems"
4
+ require "http/parser"
5
+ require "benchmark/ips"
6
+
7
+ request = <<-REQUEST
8
+ GET / HTTP/1.1
9
+ Host: www.example.com
10
+ Connection: keep-alive
11
+ User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_7_3) AppleWebKit/535.11 (KHTML, like Gecko) Chrome/17.0.963.78 S
12
+ Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8
13
+ Accept-Encoding: gzip,deflate,sdch
14
+ Accept-Language: en-US,en;q=0.8
15
+ Accept-Charset: ISO-8859-1,utf-8;q=0.7,*;q=0.3
16
+
17
+ REQUEST
18
+ request.gsub!(/\n/m, "\r\n")
19
+
20
+ Benchmark.ips do |ips|
21
+ ips.report("instance") { Http::Parser.new }
22
+ ips.report("parsing") { Http::Parser.new << request }
23
+ end
data/bench/thin.rb ADDED
@@ -0,0 +1,58 @@
1
+ $:.unshift File.dirname(__FILE__) + "/../lib"
2
+ require "rubygems"
3
+ require "thin_parser"
4
+ require "http_parser"
5
+ require "benchmark"
6
+ require "stringio"
7
+
8
+ data = "POST /postit HTTP/1.1\r\n" +
9
+ "Host: localhost:3000\r\n" +
10
+ "User-Agent: Mozilla/5.0 (Macintosh; U; Intel Mac OS X; en-US; rv:1.8.1.9) Gecko/20071025 Firefox/2.0.0.9\r\n" +
11
+ "Accept: text/xml,application/xml,application/xhtml+xml,text/html;q=0.9,text/plain;q=0.8,image/png,*/*;q=0.5\r\n" +
12
+ "Accept-Language: en-us,en;q=0.5\r\n" +
13
+ "Accept-Encoding: gzip,deflate\r\n" +
14
+ "Accept-Charset: ISO-8859-1,utf-8;q=0.7,*;q=0.7\r\n" +
15
+ "Keep-Alive: 300\r\n" +
16
+ "Connection: keep-alive\r\n" +
17
+ "Content-Type: text/html\r\n" +
18
+ "Content-Length: 37\r\n" +
19
+ "\r\n" +
20
+ "name=marc&email=macournoyer@gmail.com"
21
+
22
+ def thin(data)
23
+ env = {"rack.input" => StringIO.new}
24
+ Thin::HttpParser.new.execute(env, data, 0)
25
+ env
26
+ end
27
+
28
+ def http_parser(data)
29
+ body = StringIO.new
30
+ env = nil
31
+
32
+ parser = HTTP::RequestParser.new
33
+ parser.on_headers_complete = proc { |e| env = e }
34
+ parser.on_body = proc { |c| body << c }
35
+ parser << data
36
+
37
+ env["rack-input"] = body
38
+ env
39
+ end
40
+
41
+ # p thin(data)
42
+ # p http_parser(data)
43
+
44
+ TESTS = 30_000
45
+ Benchmark.bmbm do |results|
46
+ results.report("thin:") { TESTS.times { thin data } }
47
+ results.report("http-parser:") { TESTS.times { http_parser data } }
48
+ end
49
+
50
+ # On my MBP core duo 2.2Ghz
51
+ # Rehearsal ------------------------------------------------
52
+ # thin: 1.470000 0.000000 1.470000 ( 1.474737)
53
+ # http-parser: 1.270000 0.020000 1.290000 ( 1.292758)
54
+ # --------------------------------------- total: 2.760000sec
55
+ #
56
+ # user system total real
57
+ # thin: 1.150000 0.030000 1.180000 ( 1.173767)
58
+ # http-parser: 1.250000 0.010000 1.260000 ( 1.263796)
@@ -0,0 +1 @@
1
+ ryah_http_parser.*
@@ -0,0 +1,18 @@
1
+ import java.io.IOException;
2
+
3
+ import org.jruby.Ruby;
4
+ import org.jruby.RubyClass;
5
+ import org.jruby.RubyModule;
6
+ import org.jruby.runtime.load.BasicLibraryService;
7
+
8
+ import org.ruby_http_parser.*;
9
+
10
+ public class RubyHttpParserService implements BasicLibraryService {
11
+ public boolean basicLoad(final Ruby runtime) throws IOException {
12
+ RubyModule mHTTP = runtime.defineModule("HTTP");
13
+ RubyClass cParser = mHTTP.defineClassUnder("Parser", runtime.getObject(), RubyHttpParser.ALLOCATOR);
14
+ cParser.defineAnnotatedMethods(RubyHttpParser.class);
15
+ cParser.defineClassUnder("Error", runtime.getClass("IOError"),runtime.getClass("IOError").getAllocator());
16
+ return true;
17
+ }
18
+ }
@@ -0,0 +1,18 @@
1
+ #ifndef ext_help_h
2
+ #define ext_help_h
3
+
4
+ #define RAISE_NOT_NULL(T) if(T == NULL) rb_raise(rb_eArgError, "NULL found for " # T " when shouldn't be.");
5
+ #define DATA_GET(from,type,name) Data_Get_Struct(from,type,name); RAISE_NOT_NULL(name);
6
+ #define REQUIRE_TYPE(V, T) if(TYPE(V) != T) rb_raise(rb_eTypeError, "Wrong argument type for " # V " required " # T);
7
+
8
+ /* for compatibility with Ruby 1.8.5, which doesn't declare RSTRING_PTR */
9
+ #ifndef RSTRING_PTR
10
+ #define RSTRING_PTR(s) (RSTRING(s)->ptr)
11
+ #endif
12
+
13
+ /* for compatibility with Ruby 1.8.5, which doesn't declare RSTRING_LEN */
14
+ #ifndef RSTRING_LEN
15
+ #define RSTRING_LEN(s) (RSTRING(s)->len)
16
+ #endif
17
+
18
+ #endif
@@ -0,0 +1,24 @@
1
+ require 'mkmf'
2
+
3
+ # check out code if it hasn't been already
4
+ if Dir[File.expand_path('../vendor/http-parser/*', __FILE__)].empty?
5
+ Dir.chdir(File.expand_path('../../../', __FILE__)) do
6
+ xsystem 'git submodule init'
7
+ xsystem 'git submodule update'
8
+ end
9
+ end
10
+
11
+ # mongrel and http-parser both define http_parser_(init|execute), so we
12
+ # rename functions in http-parser before using them.
13
+ vendor_dir = File.expand_path('../vendor/http-parser/', __FILE__)
14
+ src_dir = File.expand_path('../', __FILE__)
15
+ %w[ http_parser.c http_parser.h ].each do |file|
16
+ File.open(File.join(src_dir, "ryah_#{file}"), 'w'){ |f|
17
+ f.write File.read(File.join(vendor_dir, file)).gsub('http_parser', 'ryah_http_parser')
18
+ }
19
+ end
20
+
21
+ $CFLAGS << " -I#{src_dir}"
22
+
23
+ dir_config("ruby_http_parser")
24
+ create_makefile("ruby_http_parser")
@@ -0,0 +1,495 @@
1
+ package org.ruby_http_parser;
2
+
3
+ import http_parser.HTTPException;
4
+ import http_parser.HTTPMethod;
5
+ import http_parser.HTTPParser;
6
+ import http_parser.lolevel.HTTPCallback;
7
+ import http_parser.lolevel.HTTPDataCallback;
8
+ import http_parser.lolevel.ParserSettings;
9
+
10
+ import java.nio.ByteBuffer;
11
+
12
+ import org.jcodings.Encoding;
13
+ import org.jcodings.specific.UTF8Encoding;
14
+ import org.jruby.Ruby;
15
+ import org.jruby.RubyArray;
16
+ import org.jruby.RubyClass;
17
+ import org.jruby.RubyHash;
18
+ import org.jruby.RubyNumeric;
19
+ import org.jruby.RubyObject;
20
+ import org.jruby.RubyString;
21
+ import org.jruby.RubySymbol;
22
+ import org.jruby.anno.JRubyMethod;
23
+ import org.jruby.exceptions.RaiseException;
24
+ import org.jruby.runtime.ObjectAllocator;
25
+ import org.jruby.runtime.ThreadContext;
26
+ import org.jruby.runtime.builtin.IRubyObject;
27
+ import org.jruby.util.ByteList;
28
+
29
+ public class RubyHttpParser extends RubyObject {
30
+
31
+ @JRubyMethod(name = "strict?", module = true)
32
+ public static IRubyObject strict(IRubyObject recv) {
33
+ return recv.getRuntime().newBoolean(true);
34
+ }
35
+
36
+ public static ObjectAllocator ALLOCATOR = new ObjectAllocator() {
37
+ public IRubyObject allocate(Ruby runtime, RubyClass klass) {
38
+ return new RubyHttpParser(runtime, klass);
39
+ }
40
+ };
41
+
42
+ byte[] fetchBytes(ByteBuffer b, int pos, int len) {
43
+ byte[] by = new byte[len];
44
+ int saved = b.position();
45
+ b.position(pos);
46
+ b.get(by);
47
+ b.position(saved);
48
+ return by;
49
+ }
50
+
51
+ public class StopException extends RuntimeException {
52
+ }
53
+
54
+ private Ruby runtime;
55
+ private HTTPParser parser;
56
+ private ParserSettings settings;
57
+
58
+ private RubyClass eParserError;
59
+
60
+ private RubyHash headers;
61
+
62
+ private IRubyObject on_message_begin;
63
+ private IRubyObject on_headers_complete;
64
+ private IRubyObject on_body;
65
+ private IRubyObject on_message_complete;
66
+
67
+ private IRubyObject requestUrl;
68
+ private IRubyObject requestPath;
69
+ private IRubyObject queryString;
70
+ private IRubyObject fragment;
71
+
72
+ private IRubyObject header_value_type;
73
+ private IRubyObject upgradeData;
74
+
75
+ private IRubyObject callback_object;
76
+
77
+ private boolean completed;
78
+
79
+ private byte[] _current_header;
80
+ private byte[] _last_header;
81
+
82
+ private static final Encoding UTF8 = UTF8Encoding.INSTANCE;
83
+
84
+ public RubyHttpParser(final Ruby runtime, RubyClass clazz) {
85
+ super(runtime, clazz);
86
+
87
+ this.runtime = runtime;
88
+ this.eParserError = (RubyClass) runtime.getModule("HTTP").getClass("Parser").getConstant("Error");
89
+
90
+ this.on_message_begin = null;
91
+ this.on_headers_complete = null;
92
+ this.on_body = null;
93
+ this.on_message_complete = null;
94
+
95
+ this.callback_object = null;
96
+
97
+ this.completed = false;
98
+
99
+ this.header_value_type = runtime.getModule("HTTP").getClass("Parser")
100
+ .getInstanceVariable("@default_header_value_type");
101
+
102
+ initSettings();
103
+ init();
104
+ }
105
+
106
+ private void initSettings() {
107
+ this.settings = new ParserSettings();
108
+
109
+ this.settings.on_url = new HTTPDataCallback() {
110
+ public int cb(http_parser.lolevel.HTTPParser p, ByteBuffer buf, int pos, int len) {
111
+ byte[] data = fetchBytes(buf, pos, len);
112
+ if (runtime.is1_9() || runtime.is2_0()) {
113
+ ((RubyString) requestUrl).cat(data, 0, data.length, UTF8);
114
+ } else {
115
+ ((RubyString) requestUrl).cat(data);
116
+ }
117
+ return 0;
118
+ }
119
+ };
120
+
121
+ this.settings.on_header_field = new HTTPDataCallback() {
122
+ public int cb(http_parser.lolevel.HTTPParser p, ByteBuffer buf, int pos, int len) {
123
+ byte[] data = fetchBytes(buf, pos, len);
124
+
125
+ if (_current_header == null)
126
+ _current_header = data;
127
+ else {
128
+ byte[] tmp = new byte[_current_header.length + data.length];
129
+ System.arraycopy(_current_header, 0, tmp, 0, _current_header.length);
130
+ System.arraycopy(data, 0, tmp, _current_header.length, data.length);
131
+ _current_header = tmp;
132
+ }
133
+
134
+ return 0;
135
+ }
136
+ };
137
+ final RubySymbol arraysSym = runtime.newSymbol("arrays");
138
+ final RubySymbol mixedSym = runtime.newSymbol("mixed");
139
+ final RubySymbol stopSym = runtime.newSymbol("stop");
140
+ final RubySymbol resetSym = runtime.newSymbol("reset");
141
+ this.settings.on_header_value = new HTTPDataCallback() {
142
+ public int cb(http_parser.lolevel.HTTPParser p, ByteBuffer buf, int pos, int len) {
143
+ byte[] data = fetchBytes(buf, pos, len);
144
+ ThreadContext context = headers.getRuntime().getCurrentContext();
145
+ IRubyObject key, val;
146
+ int new_field = 0;
147
+
148
+ if (_current_header != null) {
149
+ new_field = 1;
150
+ _last_header = _current_header;
151
+ _current_header = null;
152
+ }
153
+
154
+ key = RubyString.newString(runtime, new ByteList(_last_header, UTF8, false));
155
+ val = headers.op_aref(context, key);
156
+
157
+ if (new_field == 1) {
158
+ if (val.isNil()) {
159
+ if (header_value_type == arraysSym) {
160
+ headers.op_aset(context, key,
161
+ RubyArray.newArrayLight(runtime, RubyString.newStringLight(runtime, 10, UTF8)));
162
+ } else {
163
+ headers.op_aset(context, key, RubyString.newStringLight(runtime, 10, UTF8));
164
+ }
165
+ } else {
166
+ if (header_value_type == mixedSym) {
167
+ if (val instanceof RubyString) {
168
+ headers.op_aset(context, key,
169
+ RubyArray.newArrayLight(runtime, val, RubyString.newStringLight(runtime, 10, UTF8)));
170
+ } else {
171
+ ((RubyArray) val).add(RubyString.newStringLight(runtime, 10, UTF8));
172
+ }
173
+ } else if (header_value_type == arraysSym) {
174
+ ((RubyArray) val).add(RubyString.newStringLight(runtime, 10, UTF8));
175
+ } else {
176
+ if (runtime.is1_9() || runtime.is2_0()) {
177
+ ((RubyString) val).cat(',', UTF8).cat(' ', UTF8);
178
+ } else {
179
+ ((RubyString) val).cat(',').cat(' ');
180
+ }
181
+ }
182
+ }
183
+ val = headers.op_aref(context, key);
184
+ }
185
+
186
+ if (val instanceof RubyArray) {
187
+ val = ((RubyArray) val).entry(-1);
188
+ }
189
+
190
+ if (runtime.is1_9() || runtime.is2_0()) {
191
+ ((RubyString) val).cat(data, 0, data.length, UTF8);
192
+ } else {
193
+ ((RubyString) val).cat(data);
194
+ }
195
+
196
+ return 0;
197
+ }
198
+ };
199
+
200
+ this.settings.on_message_begin = new HTTPCallback() {
201
+ public int cb(http_parser.lolevel.HTTPParser p) {
202
+ headers = new RubyHash(runtime);
203
+
204
+ if (runtime.is1_9() || runtime.is2_0()) {
205
+ requestUrl = RubyString.newEmptyString(runtime, UTF8);
206
+ requestPath = RubyString.newEmptyString(runtime, UTF8);
207
+ queryString = RubyString.newEmptyString(runtime, UTF8);
208
+ fragment = RubyString.newEmptyString(runtime, UTF8);
209
+ upgradeData = RubyString.newEmptyString(runtime, UTF8);
210
+ } else {
211
+ requestUrl = RubyString.newEmptyString(runtime);
212
+ requestPath = RubyString.newEmptyString(runtime);
213
+ queryString = RubyString.newEmptyString(runtime);
214
+ fragment = RubyString.newEmptyString(runtime);
215
+ upgradeData = RubyString.newEmptyString(runtime);
216
+ }
217
+
218
+ IRubyObject ret = runtime.getNil();
219
+
220
+ if (callback_object != null) {
221
+ if (((RubyObject) callback_object).respondsTo("on_message_begin")) {
222
+ ThreadContext context = callback_object.getRuntime().getCurrentContext();
223
+ ret = callback_object.callMethod(context, "on_message_begin");
224
+ }
225
+ } else if (on_message_begin != null) {
226
+ ThreadContext context = on_message_begin.getRuntime().getCurrentContext();
227
+ ret = on_message_begin.callMethod(context, "call");
228
+ }
229
+
230
+ if (ret == stopSym) {
231
+ throw new StopException();
232
+ } else {
233
+ return 0;
234
+ }
235
+ }
236
+ };
237
+ this.settings.on_message_complete = new HTTPCallback() {
238
+ public int cb(http_parser.lolevel.HTTPParser p) {
239
+ IRubyObject ret = runtime.getNil();
240
+
241
+ completed = true;
242
+
243
+ if (callback_object != null) {
244
+ if (((RubyObject) callback_object).respondsTo("on_message_complete")) {
245
+ ThreadContext context = callback_object.getRuntime().getCurrentContext();
246
+ ret = callback_object.callMethod(context, "on_message_complete");
247
+ }
248
+ } else if (on_message_complete != null) {
249
+ ThreadContext context = on_message_complete.getRuntime().getCurrentContext();
250
+ ret = on_message_complete.callMethod(context, "call");
251
+ }
252
+
253
+ if (ret == stopSym) {
254
+ throw new StopException();
255
+ } else {
256
+ return 0;
257
+ }
258
+ }
259
+ };
260
+ this.settings.on_headers_complete = new HTTPCallback() {
261
+ public int cb(http_parser.lolevel.HTTPParser p) {
262
+ IRubyObject ret = runtime.getNil();
263
+
264
+ if (callback_object != null) {
265
+ if (((RubyObject) callback_object).respondsTo("on_headers_complete")) {
266
+ ThreadContext context = callback_object.getRuntime().getCurrentContext();
267
+ ret = callback_object.callMethod(context, "on_headers_complete", headers);
268
+ }
269
+ } else if (on_headers_complete != null) {
270
+ ThreadContext context = on_headers_complete.getRuntime().getCurrentContext();
271
+ ret = on_headers_complete.callMethod(context, "call", headers);
272
+ }
273
+
274
+ if (ret == stopSym) {
275
+ throw new StopException();
276
+ } else if (ret == resetSym) {
277
+ return 1;
278
+ } else {
279
+ return 0;
280
+ }
281
+ }
282
+ };
283
+ this.settings.on_body = new HTTPDataCallback() {
284
+ public int cb(http_parser.lolevel.HTTPParser p, ByteBuffer buf, int pos, int len) {
285
+ IRubyObject ret = runtime.getNil();
286
+ byte[] data = fetchBytes(buf, pos, len);
287
+
288
+ if (callback_object != null) {
289
+ if (((RubyObject) callback_object).respondsTo("on_body")) {
290
+ ThreadContext context = callback_object.getRuntime().getCurrentContext();
291
+ ret = callback_object.callMethod(context, "on_body",
292
+ RubyString.newString(runtime, new ByteList(data, UTF8, false)));
293
+ }
294
+ } else if (on_body != null) {
295
+ ThreadContext context = on_body.getRuntime().getCurrentContext();
296
+ ret = on_body.callMethod(context, "call", RubyString.newString(runtime, new ByteList(data, UTF8, false)));
297
+ }
298
+
299
+ if (ret == stopSym) {
300
+ throw new StopException();
301
+ } else {
302
+ return 0;
303
+ }
304
+ }
305
+ };
306
+ }
307
+
308
+ private void init() {
309
+ this.parser = new HTTPParser();
310
+ this.parser.HTTP_PARSER_STRICT = true;
311
+ this.headers = null;
312
+
313
+ this.requestUrl = runtime.getNil();
314
+ this.requestPath = runtime.getNil();
315
+ this.queryString = runtime.getNil();
316
+ this.fragment = runtime.getNil();
317
+
318
+ this.upgradeData = runtime.getNil();
319
+ }
320
+
321
+ @JRubyMethod(name = "initialize")
322
+ public IRubyObject initialize() {
323
+ return this;
324
+ }
325
+
326
+ @JRubyMethod(name = "initialize")
327
+ public IRubyObject initialize(IRubyObject arg) {
328
+ callback_object = arg;
329
+ return initialize();
330
+ }
331
+
332
+ @JRubyMethod(name = "initialize")
333
+ public IRubyObject initialize(IRubyObject arg, IRubyObject arg2) {
334
+ header_value_type = arg2;
335
+ return initialize(arg);
336
+ }
337
+
338
+ @JRubyMethod(name = "on_message_begin=")
339
+ public IRubyObject set_on_message_begin(IRubyObject cb) {
340
+ on_message_begin = cb;
341
+ return cb;
342
+ }
343
+
344
+ @JRubyMethod(name = "on_headers_complete=")
345
+ public IRubyObject set_on_headers_complete(IRubyObject cb) {
346
+ on_headers_complete = cb;
347
+ return cb;
348
+ }
349
+
350
+ @JRubyMethod(name = "on_body=")
351
+ public IRubyObject set_on_body(IRubyObject cb) {
352
+ on_body = cb;
353
+ return cb;
354
+ }
355
+
356
+ @JRubyMethod(name = "on_message_complete=")
357
+ public IRubyObject set_on_message_complete(IRubyObject cb) {
358
+ on_message_complete = cb;
359
+ return cb;
360
+ }
361
+
362
+ @JRubyMethod(name = "<<")
363
+ public IRubyObject execute(IRubyObject data) {
364
+ RubyString str = (RubyString) data;
365
+ ByteList byteList = str.getByteList();
366
+ ByteBuffer buf = ByteBuffer.wrap(byteList.getUnsafeBytes(), byteList.getBegin(), byteList.getRealSize());
367
+ boolean stopped = false;
368
+
369
+ try {
370
+ this.parser.execute(this.settings, buf);
371
+ } catch (HTTPException e) {
372
+ throw new RaiseException(runtime, eParserError, e.getMessage(), true);
373
+ } catch (StopException e) {
374
+ stopped = true;
375
+ }
376
+
377
+ if (parser.getUpgrade()) {
378
+ byte[] upData = fetchBytes(buf, buf.position(), buf.limit() - buf.position());
379
+ if (runtime.is1_9() || runtime.is2_0()) {
380
+ ((RubyString) upgradeData).cat(upData, 0, upData.length, UTF8);
381
+ } else {
382
+ ((RubyString) upgradeData).cat(upData);
383
+ }
384
+ } else if (buf.hasRemaining() && !completed) {
385
+ if (!stopped)
386
+ throw new RaiseException(runtime, eParserError, "Could not parse data entirely", true);
387
+ }
388
+
389
+ return RubyNumeric.int2fix(runtime, buf.position());
390
+ }
391
+
392
+ @JRubyMethod(name = "keep_alive?")
393
+ public IRubyObject shouldKeepAlive() {
394
+ return runtime.newBoolean(parser.shouldKeepAlive());
395
+ }
396
+
397
+ @JRubyMethod(name = "upgrade?")
398
+ public IRubyObject shouldUpgrade() {
399
+ return runtime.newBoolean(parser.getUpgrade());
400
+ }
401
+
402
+ @JRubyMethod(name = "http_major")
403
+ public IRubyObject httpMajor() {
404
+ if (parser.getMajor() == 0 && parser.getMinor() == 0)
405
+ return runtime.getNil();
406
+ else
407
+ return RubyNumeric.int2fix(runtime, parser.getMajor());
408
+ }
409
+
410
+ @JRubyMethod(name = "http_minor")
411
+ public IRubyObject httpMinor() {
412
+ if (parser.getMajor() == 0 && parser.getMinor() == 0)
413
+ return runtime.getNil();
414
+ else
415
+ return RubyNumeric.int2fix(runtime, parser.getMinor());
416
+ }
417
+
418
+ @JRubyMethod(name = "http_version")
419
+ public IRubyObject httpVersion() {
420
+ if (parser.getMajor() == 0 && parser.getMinor() == 0)
421
+ return runtime.getNil();
422
+ else
423
+ return runtime.newArray(httpMajor(), httpMinor());
424
+ }
425
+
426
+ @JRubyMethod(name = "http_method")
427
+ public IRubyObject httpMethod() {
428
+ HTTPMethod method = parser.getHTTPMethod();
429
+ if (method != null)
430
+ return runtime.newString(new String(method.bytes));
431
+ else
432
+ return runtime.getNil();
433
+ }
434
+
435
+ @JRubyMethod(name = "status_code")
436
+ public IRubyObject statusCode() {
437
+ int code = parser.getStatusCode();
438
+ if (code != 0)
439
+ return RubyNumeric.int2fix(runtime, code);
440
+ else
441
+ return runtime.getNil();
442
+ }
443
+
444
+ @JRubyMethod(name = "headers")
445
+ public IRubyObject getHeaders() {
446
+ return headers == null ? runtime.getNil() : headers;
447
+ }
448
+
449
+ @JRubyMethod(name = "request_url")
450
+ public IRubyObject getRequestUrl() {
451
+ return requestUrl == null ? runtime.getNil() : requestUrl;
452
+ }
453
+
454
+ @JRubyMethod(name = "request_path")
455
+ public IRubyObject getRequestPath() {
456
+ return requestPath == null ? runtime.getNil() : requestPath;
457
+ }
458
+
459
+ @JRubyMethod(name = "query_string")
460
+ public IRubyObject getQueryString() {
461
+ return queryString == null ? runtime.getNil() : queryString;
462
+ }
463
+
464
+ @JRubyMethod(name = "fragment")
465
+ public IRubyObject getFragment() {
466
+ return fragment == null ? runtime.getNil() : fragment;
467
+ }
468
+
469
+ @JRubyMethod(name = "header_value_type")
470
+ public IRubyObject getHeaderValueType() {
471
+ return header_value_type == null ? runtime.getNil() : header_value_type;
472
+ }
473
+
474
+ @JRubyMethod(name = "header_value_type=")
475
+ public IRubyObject set_header_value_type(IRubyObject val) {
476
+ String valString = val.toString();
477
+ if (valString != "mixed" && valString != "arrays" && valString != "strings") {
478
+ throw runtime.newArgumentError("Invalid header value type");
479
+ }
480
+ header_value_type = val;
481
+ return val;
482
+ }
483
+
484
+ @JRubyMethod(name = "upgrade_data")
485
+ public IRubyObject upgradeData() {
486
+ return upgradeData == null ? runtime.getNil() : upgradeData;
487
+ }
488
+
489
+ @JRubyMethod(name = "reset!")
490
+ public IRubyObject reset() {
491
+ init();
492
+ return runtime.getTrue();
493
+ }
494
+
495
+ }