pax_global_header00006660000000000000000000000064122517300020014502gustar00rootroot0000000000000052 comment=ec53181a4c944f8c308830b84b08fc8f71075ba6 http_parser.rb-0.6.0/000077500000000000000000000000001225173000200144425ustar00rootroot00000000000000http_parser.rb-0.6.0/.gitignore000066400000000000000000000001051225173000200164260ustar00rootroot00000000000000tmp *.bundle *.gem *.o *.so *.bundle *.jar *.swp Makefile tags *.rbc http_parser.rb-0.6.0/.gitmodules000066400000000000000000000003731225173000200166220ustar00rootroot00000000000000[submodule "http-parser"] path = ext/ruby_http_parser/vendor/http-parser url = git://github.com/joyent/http-parser.git [submodule "http-parser-java"] path = ext/ruby_http_parser/vendor/http-parser-java url = git://github.com/tmm1/http-parser.java http_parser.rb-0.6.0/Gemfile000066400000000000000000000000461225173000200157350ustar00rootroot00000000000000source 'https://rubygems.org' gemspec http_parser.rb-0.6.0/Gemfile.lock000066400000000000000000000012641225173000200166670ustar00rootroot00000000000000PATH remote: . specs: http_parser.rb (0.6.0.beta.2) GEM remote: https://rubygems.org/ specs: benchmark_suite (0.8.0) diff-lcs (1.1.2) ffi (1.0.11) ffi (1.0.11-java) json (1.8.0) json (1.8.0-java) rake (0.9.2) rake-compiler (0.7.9) rake rspec (2.4.0) rspec-core (~> 2.4.0) rspec-expectations (~> 2.4.0) rspec-mocks (~> 2.4.0) rspec-core (2.4.0) rspec-expectations (2.4.0) diff-lcs (~> 1.1.2) rspec-mocks (2.4.0) yajl-ruby (1.1.0) PLATFORMS java ruby DEPENDENCIES benchmark_suite ffi http_parser.rb! json (>= 1.4.6) rake-compiler (>= 0.7.9) rspec (>= 2.0.1) yajl-ruby (>= 0.8.1) http_parser.rb-0.6.0/LICENSE-MIT000066400000000000000000000021611225173000200160760ustar00rootroot00000000000000Copyright 2009,2010 Marc-André Cournoyer Copyright 2010,2011 Aman Gupta Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. http_parser.rb-0.6.0/README.md000066400000000000000000000030361225173000200157230ustar00rootroot00000000000000# http_parser.rb A simple callback-based HTTP request/response parser for writing http servers, clients and proxies. This gem is built on top of [joyent/http-parser](http://github.com/joyent/http-parser) and its java port [http-parser/http-parser.java](http://github.com/http-parser/http-parser.java). ## Supported Platforms This gem aims to work on all major Ruby platforms, including: - MRI 1.8 and 1.9 - Rubinius - JRuby - win32 ## Usage ```ruby require "http/parser" parser = Http::Parser.new parser.on_headers_complete = proc do p parser.http_version p parser.http_method # for requests p parser.request_url p parser.status_code # for responses p parser.headers end parser.on_body = proc do |chunk| # One chunk of the body p chunk end parser.on_message_complete = proc do |env| # Headers and body is all parsed puts "Done!" end ``` # Feed raw data from the socket to the parser `parser << raw_data` ## Advanced Usage ### Accept callbacks on an object ```ruby module MyHttpConnection def connection_completed @parser = Http::Parser.new(self) end def receive_data(data) @parser << data end def on_message_begin @headers = nil @body = '' end def on_headers_complete(headers) @headers = headers end def on_body(chunk) @body << chunk end def on_message_complete p [@headers, @body] end end ``` ### Stop parsing after headers ```ruby parser = Http::Parser.new parser.on_headers_complete = proc{ :stop } offset = parser << request_data body = request_data[offset..-1] ``` http_parser.rb-0.6.0/Rakefile000066400000000000000000000002121225173000200161020ustar00rootroot00000000000000# load tasks Dir['tasks/*.rake'].sort.each { |f| load f } # default task task :compile => :submodules task :default => [:compile, :spec] http_parser.rb-0.6.0/bench/000077500000000000000000000000001225173000200155215ustar00rootroot00000000000000http_parser.rb-0.6.0/bench/standalone.rb000077500000000000000000000012571225173000200202060ustar00rootroot00000000000000#!/usr/bin/env ruby $:.unshift File.dirname(__FILE__) + "/../lib" require "rubygems" require "http/parser" require "benchmark/ips" request = <<-REQUEST GET / HTTP/1.1 Host: www.example.com Connection: keep-alive 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 Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8 Accept-Encoding: gzip,deflate,sdch Accept-Language: en-US,en;q=0.8 Accept-Charset: ISO-8859-1,utf-8;q=0.7,*;q=0.3 REQUEST request.gsub!(/\n/m, "\r\n") Benchmark.ips do |ips| ips.report("instance") { Http::Parser.new } ips.report("parsing") { Http::Parser.new << request } end http_parser.rb-0.6.0/bench/thin.rb000066400000000000000000000034651225173000200170200ustar00rootroot00000000000000$:.unshift File.dirname(__FILE__) + "/../lib" require "rubygems" require "thin_parser" require "http_parser" require "benchmark" require "stringio" data = "POST /postit HTTP/1.1\r\n" + "Host: localhost:3000\r\n" + "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" + "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" + "Accept-Language: en-us,en;q=0.5\r\n" + "Accept-Encoding: gzip,deflate\r\n" + "Accept-Charset: ISO-8859-1,utf-8;q=0.7,*;q=0.7\r\n" + "Keep-Alive: 300\r\n" + "Connection: keep-alive\r\n" + "Content-Type: text/html\r\n" + "Content-Length: 37\r\n" + "\r\n" + "name=marc&email=macournoyer@gmail.com" def thin(data) env = {"rack.input" => StringIO.new} Thin::HttpParser.new.execute(env, data, 0) env end def http_parser(data) body = StringIO.new env = nil parser = HTTP::RequestParser.new parser.on_headers_complete = proc { |e| env = e } parser.on_body = proc { |c| body << c } parser << data env["rack-input"] = body env end # p thin(data) # p http_parser(data) TESTS = 30_000 Benchmark.bmbm do |results| results.report("thin:") { TESTS.times { thin data } } results.report("http-parser:") { TESTS.times { http_parser data } } end # On my MBP core duo 2.2Ghz # Rehearsal ------------------------------------------------ # thin: 1.470000 0.000000 1.470000 ( 1.474737) # http-parser: 1.270000 0.020000 1.290000 ( 1.292758) # --------------------------------------- total: 2.760000sec # # user system total real # thin: 1.150000 0.030000 1.180000 ( 1.173767) # http-parser: 1.250000 0.010000 1.260000 ( 1.263796) http_parser.rb-0.6.0/ext/000077500000000000000000000000001225173000200152425ustar00rootroot00000000000000http_parser.rb-0.6.0/ext/ruby_http_parser/000077500000000000000000000000001225173000200206365ustar00rootroot00000000000000http_parser.rb-0.6.0/ext/ruby_http_parser/.gitignore000066400000000000000000000000231225173000200226210ustar00rootroot00000000000000ryah_http_parser.* http_parser.rb-0.6.0/ext/ruby_http_parser/RubyHttpParserService.java000066400000000000000000000012471225173000200257640ustar00rootroot00000000000000import java.io.IOException; import org.jruby.Ruby; import org.jruby.RubyClass; import org.jruby.RubyModule; import org.jruby.runtime.load.BasicLibraryService; import org.ruby_http_parser.*; public class RubyHttpParserService implements BasicLibraryService { public boolean basicLoad(final Ruby runtime) throws IOException { RubyModule mHTTP = runtime.defineModule("HTTP"); RubyClass cParser = mHTTP.defineClassUnder("Parser", runtime.getObject(), RubyHttpParser.ALLOCATOR); cParser.defineAnnotatedMethods(RubyHttpParser.class); cParser.defineClassUnder("Error", runtime.getClass("IOError"),runtime.getClass("IOError").getAllocator()); return true; } } http_parser.rb-0.6.0/ext/ruby_http_parser/ext_help.h000066400000000000000000000012121225173000200226130ustar00rootroot00000000000000#ifndef ext_help_h #define ext_help_h #define RAISE_NOT_NULL(T) if(T == NULL) rb_raise(rb_eArgError, "NULL found for " # T " when shouldn't be."); #define DATA_GET(from,type,name) Data_Get_Struct(from,type,name); RAISE_NOT_NULL(name); #define REQUIRE_TYPE(V, T) if(TYPE(V) != T) rb_raise(rb_eTypeError, "Wrong argument type for " # V " required " # T); /* for compatibility with Ruby 1.8.5, which doesn't declare RSTRING_PTR */ #ifndef RSTRING_PTR #define RSTRING_PTR(s) (RSTRING(s)->ptr) #endif /* for compatibility with Ruby 1.8.5, which doesn't declare RSTRING_LEN */ #ifndef RSTRING_LEN #define RSTRING_LEN(s) (RSTRING(s)->len) #endif #endif http_parser.rb-0.6.0/ext/ruby_http_parser/extconf.rb000066400000000000000000000014401225173000200226300ustar00rootroot00000000000000require 'mkmf' # check out code if it hasn't been already if Dir[File.expand_path('../vendor/http-parser/*', __FILE__)].empty? Dir.chdir(File.expand_path('../../../', __FILE__)) do xsystem 'git submodule init' xsystem 'git submodule update' end end # mongrel and http-parser both define http_parser_(init|execute), so we # rename functions in http-parser before using them. vendor_dir = File.expand_path('../vendor/http-parser/', __FILE__) src_dir = File.expand_path('../', __FILE__) %w[ http_parser.c http_parser.h ].each do |file| File.open(File.join(src_dir, "ryah_#{file}"), 'w'){ |f| f.write File.read(File.join(vendor_dir, file)).gsub('http_parser', 'ryah_http_parser') } end $CFLAGS << " -I#{src_dir}" dir_config("ruby_http_parser") create_makefile("ruby_http_parser") http_parser.rb-0.6.0/ext/ruby_http_parser/org/000077500000000000000000000000001225173000200214255ustar00rootroot00000000000000http_parser.rb-0.6.0/ext/ruby_http_parser/org/ruby_http_parser/000077500000000000000000000000001225173000200250215ustar00rootroot00000000000000http_parser.rb-0.6.0/ext/ruby_http_parser/org/ruby_http_parser/RubyHttpParser.java000066400000000000000000000367001225173000200306300ustar00rootroot00000000000000package org.ruby_http_parser; import http_parser.HTTPException; import http_parser.HTTPMethod; import http_parser.HTTPParser; import http_parser.lolevel.HTTPCallback; import http_parser.lolevel.HTTPDataCallback; import http_parser.lolevel.ParserSettings; import java.nio.ByteBuffer; import org.jcodings.Encoding; import org.jcodings.specific.UTF8Encoding; import org.jruby.Ruby; import org.jruby.RubyArray; import org.jruby.RubyClass; import org.jruby.RubyHash; import org.jruby.RubyNumeric; import org.jruby.RubyObject; import org.jruby.RubyString; import org.jruby.RubySymbol; import org.jruby.anno.JRubyMethod; import org.jruby.exceptions.RaiseException; import org.jruby.runtime.ObjectAllocator; import org.jruby.runtime.ThreadContext; import org.jruby.runtime.builtin.IRubyObject; import org.jruby.util.ByteList; public class RubyHttpParser extends RubyObject { @JRubyMethod(name = "strict?", module = true) public static IRubyObject strict(IRubyObject recv) { return recv.getRuntime().newBoolean(true); } public static ObjectAllocator ALLOCATOR = new ObjectAllocator() { public IRubyObject allocate(Ruby runtime, RubyClass klass) { return new RubyHttpParser(runtime, klass); } }; byte[] fetchBytes(ByteBuffer b, int pos, int len) { byte[] by = new byte[len]; int saved = b.position(); b.position(pos); b.get(by); b.position(saved); return by; } public class StopException extends RuntimeException { } private Ruby runtime; private HTTPParser parser; private ParserSettings settings; private RubyClass eParserError; private RubyHash headers; private IRubyObject on_message_begin; private IRubyObject on_headers_complete; private IRubyObject on_body; private IRubyObject on_message_complete; private IRubyObject requestUrl; private IRubyObject requestPath; private IRubyObject queryString; private IRubyObject fragment; private IRubyObject header_value_type; private IRubyObject upgradeData; private IRubyObject callback_object; private boolean completed; private byte[] _current_header; private byte[] _last_header; private static final Encoding UTF8 = UTF8Encoding.INSTANCE; public RubyHttpParser(final Ruby runtime, RubyClass clazz) { super(runtime, clazz); this.runtime = runtime; this.eParserError = (RubyClass) runtime.getModule("HTTP").getClass("Parser").getConstant("Error"); this.on_message_begin = null; this.on_headers_complete = null; this.on_body = null; this.on_message_complete = null; this.callback_object = null; this.completed = false; this.header_value_type = runtime.getModule("HTTP").getClass("Parser") .getInstanceVariable("@default_header_value_type"); initSettings(); init(); } private void initSettings() { this.settings = new ParserSettings(); this.settings.on_url = new HTTPDataCallback() { public int cb(http_parser.lolevel.HTTPParser p, ByteBuffer buf, int pos, int len) { byte[] data = fetchBytes(buf, pos, len); if (runtime.is1_9() || runtime.is2_0()) { ((RubyString) requestUrl).cat(data, 0, data.length, UTF8); } else { ((RubyString) requestUrl).cat(data); } return 0; } }; this.settings.on_header_field = new HTTPDataCallback() { public int cb(http_parser.lolevel.HTTPParser p, ByteBuffer buf, int pos, int len) { byte[] data = fetchBytes(buf, pos, len); if (_current_header == null) _current_header = data; else { byte[] tmp = new byte[_current_header.length + data.length]; System.arraycopy(_current_header, 0, tmp, 0, _current_header.length); System.arraycopy(data, 0, tmp, _current_header.length, data.length); _current_header = tmp; } return 0; } }; final RubySymbol arraysSym = runtime.newSymbol("arrays"); final RubySymbol mixedSym = runtime.newSymbol("mixed"); final RubySymbol stopSym = runtime.newSymbol("stop"); final RubySymbol resetSym = runtime.newSymbol("reset"); this.settings.on_header_value = new HTTPDataCallback() { public int cb(http_parser.lolevel.HTTPParser p, ByteBuffer buf, int pos, int len) { byte[] data = fetchBytes(buf, pos, len); ThreadContext context = headers.getRuntime().getCurrentContext(); IRubyObject key, val; int new_field = 0; if (_current_header != null) { new_field = 1; _last_header = _current_header; _current_header = null; } key = RubyString.newString(runtime, new ByteList(_last_header, UTF8, false)); val = headers.op_aref(context, key); if (new_field == 1) { if (val.isNil()) { if (header_value_type == arraysSym) { headers.op_aset(context, key, RubyArray.newArrayLight(runtime, RubyString.newStringLight(runtime, 10, UTF8))); } else { headers.op_aset(context, key, RubyString.newStringLight(runtime, 10, UTF8)); } } else { if (header_value_type == mixedSym) { if (val instanceof RubyString) { headers.op_aset(context, key, RubyArray.newArrayLight(runtime, val, RubyString.newStringLight(runtime, 10, UTF8))); } else { ((RubyArray) val).add(RubyString.newStringLight(runtime, 10, UTF8)); } } else if (header_value_type == arraysSym) { ((RubyArray) val).add(RubyString.newStringLight(runtime, 10, UTF8)); } else { if (runtime.is1_9() || runtime.is2_0()) { ((RubyString) val).cat(',', UTF8).cat(' ', UTF8); } else { ((RubyString) val).cat(',').cat(' '); } } } val = headers.op_aref(context, key); } if (val instanceof RubyArray) { val = ((RubyArray) val).entry(-1); } if (runtime.is1_9() || runtime.is2_0()) { ((RubyString) val).cat(data, 0, data.length, UTF8); } else { ((RubyString) val).cat(data); } return 0; } }; this.settings.on_message_begin = new HTTPCallback() { public int cb(http_parser.lolevel.HTTPParser p) { headers = new RubyHash(runtime); if (runtime.is1_9() || runtime.is2_0()) { requestUrl = RubyString.newEmptyString(runtime, UTF8); requestPath = RubyString.newEmptyString(runtime, UTF8); queryString = RubyString.newEmptyString(runtime, UTF8); fragment = RubyString.newEmptyString(runtime, UTF8); upgradeData = RubyString.newEmptyString(runtime, UTF8); } else { requestUrl = RubyString.newEmptyString(runtime); requestPath = RubyString.newEmptyString(runtime); queryString = RubyString.newEmptyString(runtime); fragment = RubyString.newEmptyString(runtime); upgradeData = RubyString.newEmptyString(runtime); } IRubyObject ret = runtime.getNil(); if (callback_object != null) { if (((RubyObject) callback_object).respondsTo("on_message_begin")) { ThreadContext context = callback_object.getRuntime().getCurrentContext(); ret = callback_object.callMethod(context, "on_message_begin"); } } else if (on_message_begin != null) { ThreadContext context = on_message_begin.getRuntime().getCurrentContext(); ret = on_message_begin.callMethod(context, "call"); } if (ret == stopSym) { throw new StopException(); } else { return 0; } } }; this.settings.on_message_complete = new HTTPCallback() { public int cb(http_parser.lolevel.HTTPParser p) { IRubyObject ret = runtime.getNil(); completed = true; if (callback_object != null) { if (((RubyObject) callback_object).respondsTo("on_message_complete")) { ThreadContext context = callback_object.getRuntime().getCurrentContext(); ret = callback_object.callMethod(context, "on_message_complete"); } } else if (on_message_complete != null) { ThreadContext context = on_message_complete.getRuntime().getCurrentContext(); ret = on_message_complete.callMethod(context, "call"); } if (ret == stopSym) { throw new StopException(); } else { return 0; } } }; this.settings.on_headers_complete = new HTTPCallback() { public int cb(http_parser.lolevel.HTTPParser p) { IRubyObject ret = runtime.getNil(); if (callback_object != null) { if (((RubyObject) callback_object).respondsTo("on_headers_complete")) { ThreadContext context = callback_object.getRuntime().getCurrentContext(); ret = callback_object.callMethod(context, "on_headers_complete", headers); } } else if (on_headers_complete != null) { ThreadContext context = on_headers_complete.getRuntime().getCurrentContext(); ret = on_headers_complete.callMethod(context, "call", headers); } if (ret == stopSym) { throw new StopException(); } else if (ret == resetSym) { return 1; } else { return 0; } } }; this.settings.on_body = new HTTPDataCallback() { public int cb(http_parser.lolevel.HTTPParser p, ByteBuffer buf, int pos, int len) { IRubyObject ret = runtime.getNil(); byte[] data = fetchBytes(buf, pos, len); if (callback_object != null) { if (((RubyObject) callback_object).respondsTo("on_body")) { ThreadContext context = callback_object.getRuntime().getCurrentContext(); ret = callback_object.callMethod(context, "on_body", RubyString.newString(runtime, new ByteList(data, UTF8, false))); } } else if (on_body != null) { ThreadContext context = on_body.getRuntime().getCurrentContext(); ret = on_body.callMethod(context, "call", RubyString.newString(runtime, new ByteList(data, UTF8, false))); } if (ret == stopSym) { throw new StopException(); } else { return 0; } } }; } private void init() { this.parser = new HTTPParser(); this.parser.HTTP_PARSER_STRICT = true; this.headers = null; this.requestUrl = runtime.getNil(); this.requestPath = runtime.getNil(); this.queryString = runtime.getNil(); this.fragment = runtime.getNil(); this.upgradeData = runtime.getNil(); } @JRubyMethod(name = "initialize") public IRubyObject initialize() { return this; } @JRubyMethod(name = "initialize") public IRubyObject initialize(IRubyObject arg) { callback_object = arg; return initialize(); } @JRubyMethod(name = "initialize") public IRubyObject initialize(IRubyObject arg, IRubyObject arg2) { header_value_type = arg2; return initialize(arg); } @JRubyMethod(name = "on_message_begin=") public IRubyObject set_on_message_begin(IRubyObject cb) { on_message_begin = cb; return cb; } @JRubyMethod(name = "on_headers_complete=") public IRubyObject set_on_headers_complete(IRubyObject cb) { on_headers_complete = cb; return cb; } @JRubyMethod(name = "on_body=") public IRubyObject set_on_body(IRubyObject cb) { on_body = cb; return cb; } @JRubyMethod(name = "on_message_complete=") public IRubyObject set_on_message_complete(IRubyObject cb) { on_message_complete = cb; return cb; } @JRubyMethod(name = "<<") public IRubyObject execute(IRubyObject data) { RubyString str = (RubyString) data; ByteList byteList = str.getByteList(); ByteBuffer buf = ByteBuffer.wrap(byteList.getUnsafeBytes(), byteList.getBegin(), byteList.getRealSize()); boolean stopped = false; try { this.parser.execute(this.settings, buf); } catch (HTTPException e) { throw new RaiseException(runtime, eParserError, e.getMessage(), true); } catch (StopException e) { stopped = true; } if (parser.getUpgrade()) { byte[] upData = fetchBytes(buf, buf.position(), buf.limit() - buf.position()); if (runtime.is1_9() || runtime.is2_0()) { ((RubyString) upgradeData).cat(upData, 0, upData.length, UTF8); } else { ((RubyString) upgradeData).cat(upData); } } else if (buf.hasRemaining() && !completed) { if (!stopped) throw new RaiseException(runtime, eParserError, "Could not parse data entirely", true); } return RubyNumeric.int2fix(runtime, buf.position()); } @JRubyMethod(name = "keep_alive?") public IRubyObject shouldKeepAlive() { return runtime.newBoolean(parser.shouldKeepAlive()); } @JRubyMethod(name = "upgrade?") public IRubyObject shouldUpgrade() { return runtime.newBoolean(parser.getUpgrade()); } @JRubyMethod(name = "http_major") public IRubyObject httpMajor() { if (parser.getMajor() == 0 && parser.getMinor() == 0) return runtime.getNil(); else return RubyNumeric.int2fix(runtime, parser.getMajor()); } @JRubyMethod(name = "http_minor") public IRubyObject httpMinor() { if (parser.getMajor() == 0 && parser.getMinor() == 0) return runtime.getNil(); else return RubyNumeric.int2fix(runtime, parser.getMinor()); } @JRubyMethod(name = "http_version") public IRubyObject httpVersion() { if (parser.getMajor() == 0 && parser.getMinor() == 0) return runtime.getNil(); else return runtime.newArray(httpMajor(), httpMinor()); } @JRubyMethod(name = "http_method") public IRubyObject httpMethod() { HTTPMethod method = parser.getHTTPMethod(); if (method != null) return runtime.newString(new String(method.bytes)); else return runtime.getNil(); } @JRubyMethod(name = "status_code") public IRubyObject statusCode() { int code = parser.getStatusCode(); if (code != 0) return RubyNumeric.int2fix(runtime, code); else return runtime.getNil(); } @JRubyMethod(name = "headers") public IRubyObject getHeaders() { return headers == null ? runtime.getNil() : headers; } @JRubyMethod(name = "request_url") public IRubyObject getRequestUrl() { return requestUrl == null ? runtime.getNil() : requestUrl; } @JRubyMethod(name = "request_path") public IRubyObject getRequestPath() { return requestPath == null ? runtime.getNil() : requestPath; } @JRubyMethod(name = "query_string") public IRubyObject getQueryString() { return queryString == null ? runtime.getNil() : queryString; } @JRubyMethod(name = "fragment") public IRubyObject getFragment() { return fragment == null ? runtime.getNil() : fragment; } @JRubyMethod(name = "header_value_type") public IRubyObject getHeaderValueType() { return header_value_type == null ? runtime.getNil() : header_value_type; } @JRubyMethod(name = "header_value_type=") public IRubyObject set_header_value_type(IRubyObject val) { String valString = val.toString(); if (valString != "mixed" && valString != "arrays" && valString != "strings") { throw runtime.newArgumentError("Invalid header value type"); } header_value_type = val; return val; } @JRubyMethod(name = "upgrade_data") public IRubyObject upgradeData() { return upgradeData == null ? runtime.getNil() : upgradeData; } @JRubyMethod(name = "reset!") public IRubyObject reset() { init(); return runtime.getTrue(); } } http_parser.rb-0.6.0/ext/ruby_http_parser/ruby_http_parser.c000066400000000000000000000345021225173000200244020ustar00rootroot00000000000000#include "ruby.h" #include "ext_help.h" #include "ryah_http_parser.h" #define GET_WRAPPER(N, from) ParserWrapper *N = (ParserWrapper *)(from)->data; #define HASH_CAT(h, k, ptr, len) \ do { \ VALUE __v = rb_hash_aref(h, k); \ if (__v != Qnil) { \ rb_str_cat(__v, ptr, len); \ } else { \ rb_hash_aset(h, k, rb_str_new(ptr, len)); \ } \ } while(0) typedef struct ParserWrapper { ryah_http_parser parser; VALUE request_url; VALUE headers; VALUE upgrade_data; VALUE on_message_begin; VALUE on_headers_complete; VALUE on_body; VALUE on_message_complete; VALUE callback_object; VALUE stopped; VALUE completed; VALUE header_value_type; VALUE last_field_name; VALUE curr_field_name; enum ryah_http_parser_type type; } ParserWrapper; void ParserWrapper_init(ParserWrapper *wrapper) { ryah_http_parser_init(&wrapper->parser, wrapper->type); wrapper->parser.status_code = 0; wrapper->parser.http_major = 0; wrapper->parser.http_minor = 0; wrapper->request_url = Qnil; wrapper->upgrade_data = Qnil; wrapper->headers = Qnil; wrapper->completed = Qfalse; wrapper->last_field_name = Qnil; wrapper->curr_field_name = Qnil; } void ParserWrapper_mark(void *data) { if(data) { ParserWrapper *wrapper = (ParserWrapper *) data; rb_gc_mark_maybe(wrapper->request_url); rb_gc_mark_maybe(wrapper->upgrade_data); rb_gc_mark_maybe(wrapper->headers); rb_gc_mark_maybe(wrapper->on_message_begin); rb_gc_mark_maybe(wrapper->on_headers_complete); rb_gc_mark_maybe(wrapper->on_body); rb_gc_mark_maybe(wrapper->on_message_complete); rb_gc_mark_maybe(wrapper->callback_object); rb_gc_mark_maybe(wrapper->last_field_name); rb_gc_mark_maybe(wrapper->curr_field_name); } } void ParserWrapper_free(void *data) { if(data) { free(data); } } static VALUE cParser; static VALUE cRequestParser; static VALUE cResponseParser; static VALUE eParserError; static ID Icall; static ID Ion_message_begin; static ID Ion_headers_complete; static ID Ion_body; static ID Ion_message_complete; static VALUE Sstop; static VALUE Sreset; static VALUE Sarrays; static VALUE Sstrings; static VALUE Smixed; /** Callbacks **/ int on_message_begin(ryah_http_parser *parser) { GET_WRAPPER(wrapper, parser); wrapper->request_url = rb_str_new2(""); wrapper->headers = rb_hash_new(); wrapper->upgrade_data = rb_str_new2(""); VALUE ret = Qnil; if (wrapper->callback_object != Qnil && rb_respond_to(wrapper->callback_object, Ion_message_begin)) { ret = rb_funcall(wrapper->callback_object, Ion_message_begin, 0); } else if (wrapper->on_message_begin != Qnil) { ret = rb_funcall(wrapper->on_message_begin, Icall, 0); } if (ret == Sstop) { wrapper->stopped = Qtrue; return -1; } else { return 0; } } int on_url(ryah_http_parser *parser, const char *at, size_t length) { GET_WRAPPER(wrapper, parser); rb_str_cat(wrapper->request_url, at, length); return 0; } int on_header_field(ryah_http_parser *parser, const char *at, size_t length) { GET_WRAPPER(wrapper, parser); if (wrapper->curr_field_name == Qnil) { wrapper->last_field_name = Qnil; wrapper->curr_field_name = rb_str_new(at, length); } else { rb_str_cat(wrapper->curr_field_name, at, length); } return 0; } int on_header_value(ryah_http_parser *parser, const char *at, size_t length) { GET_WRAPPER(wrapper, parser); int new_field = 0; VALUE current_value; if (wrapper->last_field_name == Qnil) { new_field = 1; wrapper->last_field_name = wrapper->curr_field_name; wrapper->curr_field_name = Qnil; } current_value = rb_hash_aref(wrapper->headers, wrapper->last_field_name); if (new_field == 1) { if (current_value == Qnil) { if (wrapper->header_value_type == Sarrays) { rb_hash_aset(wrapper->headers, wrapper->last_field_name, rb_ary_new3(1, rb_str_new2(""))); } else { rb_hash_aset(wrapper->headers, wrapper->last_field_name, rb_str_new2("")); } } else { if (wrapper->header_value_type == Smixed) { if (TYPE(current_value) == T_STRING) { rb_hash_aset(wrapper->headers, wrapper->last_field_name, rb_ary_new3(2, current_value, rb_str_new2(""))); } else { rb_ary_push(current_value, rb_str_new2("")); } } else if (wrapper->header_value_type == Sarrays) { rb_ary_push(current_value, rb_str_new2("")); } else { rb_str_cat(current_value, ", ", 2); } } current_value = rb_hash_aref(wrapper->headers, wrapper->last_field_name); } if (TYPE(current_value) == T_ARRAY) { current_value = rb_ary_entry(current_value, -1); } rb_str_cat(current_value, at, length); return 0; } int on_headers_complete(ryah_http_parser *parser) { GET_WRAPPER(wrapper, parser); VALUE ret = Qnil; if (wrapper->callback_object != Qnil && rb_respond_to(wrapper->callback_object, Ion_headers_complete)) { ret = rb_funcall(wrapper->callback_object, Ion_headers_complete, 1, wrapper->headers); } else if (wrapper->on_headers_complete != Qnil) { ret = rb_funcall(wrapper->on_headers_complete, Icall, 1, wrapper->headers); } if (ret == Sstop) { wrapper->stopped = Qtrue; return -1; } else if (ret == Sreset){ return 1; } else { return 0; } } int on_body(ryah_http_parser *parser, const char *at, size_t length) { GET_WRAPPER(wrapper, parser); VALUE ret = Qnil; if (wrapper->callback_object != Qnil && rb_respond_to(wrapper->callback_object, Ion_body)) { ret = rb_funcall(wrapper->callback_object, Ion_body, 1, rb_str_new(at, length)); } else if (wrapper->on_body != Qnil) { ret = rb_funcall(wrapper->on_body, Icall, 1, rb_str_new(at, length)); } if (ret == Sstop) { wrapper->stopped = Qtrue; return -1; } else { return 0; } } int on_message_complete(ryah_http_parser *parser) { GET_WRAPPER(wrapper, parser); VALUE ret = Qnil; wrapper->completed = Qtrue; if (wrapper->callback_object != Qnil && rb_respond_to(wrapper->callback_object, Ion_message_complete)) { ret = rb_funcall(wrapper->callback_object, Ion_message_complete, 0); } else if (wrapper->on_message_complete != Qnil) { ret = rb_funcall(wrapper->on_message_complete, Icall, 0); } if (ret == Sstop) { wrapper->stopped = Qtrue; return -1; } else { return 0; } } static ryah_http_parser_settings settings = { .on_message_begin = on_message_begin, .on_url = on_url, .on_header_field = on_header_field, .on_header_value = on_header_value, .on_headers_complete = on_headers_complete, .on_body = on_body, .on_message_complete = on_message_complete }; VALUE Parser_alloc_by_type(VALUE klass, enum ryah_http_parser_type type) { ParserWrapper *wrapper = ALLOC_N(ParserWrapper, 1); wrapper->type = type; wrapper->parser.data = wrapper; wrapper->on_message_begin = Qnil; wrapper->on_headers_complete = Qnil; wrapper->on_body = Qnil; wrapper->on_message_complete = Qnil; wrapper->callback_object = Qnil; ParserWrapper_init(wrapper); return Data_Wrap_Struct(klass, ParserWrapper_mark, ParserWrapper_free, wrapper); } VALUE Parser_alloc(VALUE klass) { return Parser_alloc_by_type(klass, HTTP_BOTH); } VALUE RequestParser_alloc(VALUE klass) { return Parser_alloc_by_type(klass, HTTP_REQUEST); } VALUE ResponseParser_alloc(VALUE klass) { return Parser_alloc_by_type(klass, HTTP_RESPONSE); } VALUE Parser_strict_p(VALUE klass) { return HTTP_PARSER_STRICT == 1 ? Qtrue : Qfalse; } VALUE Parser_initialize(int argc, VALUE *argv, VALUE self) { ParserWrapper *wrapper = NULL; DATA_GET(self, ParserWrapper, wrapper); wrapper->header_value_type = rb_iv_get(CLASS_OF(self), "@default_header_value_type"); if (argc == 1) { wrapper->callback_object = argv[0]; } if (argc == 2) { wrapper->callback_object = argv[0]; wrapper->header_value_type = argv[1]; } return self; } VALUE Parser_execute(VALUE self, VALUE data) { ParserWrapper *wrapper = NULL; Check_Type(data, T_STRING); char *ptr = RSTRING_PTR(data); long len = RSTRING_LEN(data); DATA_GET(self, ParserWrapper, wrapper); wrapper->stopped = Qfalse; size_t nparsed = ryah_http_parser_execute(&wrapper->parser, &settings, ptr, len); if (wrapper->parser.upgrade) { if (RTEST(wrapper->stopped)) nparsed += 1; rb_str_cat(wrapper->upgrade_data, ptr + nparsed, len - nparsed); } else if (nparsed != (size_t)len) { if (!RTEST(wrapper->stopped) && !RTEST(wrapper->completed)) rb_raise(eParserError, "Could not parse data entirely (%zu != %zu)", nparsed, len); else nparsed += 1; // error states fail on the current character } return INT2FIX(nparsed); } VALUE Parser_set_on_message_begin(VALUE self, VALUE callback) { ParserWrapper *wrapper = NULL; DATA_GET(self, ParserWrapper, wrapper); wrapper->on_message_begin = callback; return callback; } VALUE Parser_set_on_headers_complete(VALUE self, VALUE callback) { ParserWrapper *wrapper = NULL; DATA_GET(self, ParserWrapper, wrapper); wrapper->on_headers_complete = callback; return callback; } VALUE Parser_set_on_body(VALUE self, VALUE callback) { ParserWrapper *wrapper = NULL; DATA_GET(self, ParserWrapper, wrapper); wrapper->on_body = callback; return callback; } VALUE Parser_set_on_message_complete(VALUE self, VALUE callback) { ParserWrapper *wrapper = NULL; DATA_GET(self, ParserWrapper, wrapper); wrapper->on_message_complete = callback; return callback; } VALUE Parser_keep_alive_p(VALUE self) { ParserWrapper *wrapper = NULL; DATA_GET(self, ParserWrapper, wrapper); return http_should_keep_alive(&wrapper->parser) == 1 ? Qtrue : Qfalse; } VALUE Parser_upgrade_p(VALUE self) { ParserWrapper *wrapper = NULL; DATA_GET(self, ParserWrapper, wrapper); return wrapper->parser.upgrade ? Qtrue : Qfalse; } VALUE Parser_http_version(VALUE self) { ParserWrapper *wrapper = NULL; DATA_GET(self, ParserWrapper, wrapper); if (wrapper->parser.http_major == 0 && wrapper->parser.http_minor == 0) return Qnil; else return rb_ary_new3(2, INT2FIX(wrapper->parser.http_major), INT2FIX(wrapper->parser.http_minor)); } VALUE Parser_http_major(VALUE self) { ParserWrapper *wrapper = NULL; DATA_GET(self, ParserWrapper, wrapper); if (wrapper->parser.http_major == 0 && wrapper->parser.http_minor == 0) return Qnil; else return INT2FIX(wrapper->parser.http_major); } VALUE Parser_http_minor(VALUE self) { ParserWrapper *wrapper = NULL; DATA_GET(self, ParserWrapper, wrapper); if (wrapper->parser.http_major == 0 && wrapper->parser.http_minor == 0) return Qnil; else return INT2FIX(wrapper->parser.http_minor); } VALUE Parser_http_method(VALUE self) { ParserWrapper *wrapper = NULL; DATA_GET(self, ParserWrapper, wrapper); if (wrapper->parser.type == HTTP_REQUEST) return rb_str_new2(http_method_str(wrapper->parser.method)); else return Qnil; } VALUE Parser_status_code(VALUE self) { ParserWrapper *wrapper = NULL; DATA_GET(self, ParserWrapper, wrapper); if (wrapper->parser.status_code) return INT2FIX(wrapper->parser.status_code); else return Qnil; } #define DEFINE_GETTER(name) \ VALUE Parser_##name(VALUE self) { \ ParserWrapper *wrapper = NULL; \ DATA_GET(self, ParserWrapper, wrapper); \ return wrapper->name; \ } DEFINE_GETTER(request_url); DEFINE_GETTER(headers); DEFINE_GETTER(upgrade_data); DEFINE_GETTER(header_value_type); VALUE Parser_set_header_value_type(VALUE self, VALUE val) { if (val != Sarrays && val != Sstrings && val != Smixed) { rb_raise(rb_eArgError, "Invalid header value type"); } ParserWrapper *wrapper = NULL; DATA_GET(self, ParserWrapper, wrapper); wrapper->header_value_type = val; return wrapper->header_value_type; } VALUE Parser_reset(VALUE self) { ParserWrapper *wrapper = NULL; DATA_GET(self, ParserWrapper, wrapper); ParserWrapper_init(wrapper); return Qtrue; } void Init_ruby_http_parser() { VALUE mHTTP = rb_define_module("HTTP"); cParser = rb_define_class_under(mHTTP, "Parser", rb_cObject); cRequestParser = rb_define_class_under(mHTTP, "RequestParser", cParser); cResponseParser = rb_define_class_under(mHTTP, "ResponseParser", cParser); eParserError = rb_define_class_under(cParser, "Error", rb_eIOError); Icall = rb_intern("call"); Ion_message_begin = rb_intern("on_message_begin"); Ion_headers_complete = rb_intern("on_headers_complete"); Ion_body = rb_intern("on_body"); Ion_message_complete = rb_intern("on_message_complete"); Sstop = ID2SYM(rb_intern("stop")); Sreset = ID2SYM(rb_intern("reset")); Sarrays = ID2SYM(rb_intern("arrays")); Sstrings = ID2SYM(rb_intern("strings")); Smixed = ID2SYM(rb_intern("mixed")); rb_define_alloc_func(cParser, Parser_alloc); rb_define_alloc_func(cRequestParser, RequestParser_alloc); rb_define_alloc_func(cResponseParser, ResponseParser_alloc); rb_define_singleton_method(cParser, "strict?", Parser_strict_p, 0); rb_define_method(cParser, "initialize", Parser_initialize, -1); rb_define_method(cParser, "on_message_begin=", Parser_set_on_message_begin, 1); rb_define_method(cParser, "on_headers_complete=", Parser_set_on_headers_complete, 1); rb_define_method(cParser, "on_body=", Parser_set_on_body, 1); rb_define_method(cParser, "on_message_complete=", Parser_set_on_message_complete, 1); rb_define_method(cParser, "<<", Parser_execute, 1); rb_define_method(cParser, "keep_alive?", Parser_keep_alive_p, 0); rb_define_method(cParser, "upgrade?", Parser_upgrade_p, 0); rb_define_method(cParser, "http_version", Parser_http_version, 0); rb_define_method(cParser, "http_major", Parser_http_major, 0); rb_define_method(cParser, "http_minor", Parser_http_minor, 0); rb_define_method(cParser, "http_method", Parser_http_method, 0); rb_define_method(cParser, "status_code", Parser_status_code, 0); rb_define_method(cParser, "request_url", Parser_request_url, 0); rb_define_method(cParser, "headers", Parser_headers, 0); rb_define_method(cParser, "upgrade_data", Parser_upgrade_data, 0); rb_define_method(cParser, "header_value_type", Parser_header_value_type, 0); rb_define_method(cParser, "header_value_type=", Parser_set_header_value_type, 1); rb_define_method(cParser, "reset!", Parser_reset, 0); } http_parser.rb-0.6.0/ext/ruby_http_parser/vendor/000077500000000000000000000000001225173000200221335ustar00rootroot00000000000000http_parser.rb-0.6.0/ext/ruby_http_parser/vendor/.gitkeep000066400000000000000000000000001225173000200235520ustar00rootroot00000000000000http_parser.rb-0.6.0/ext/ruby_http_parser/vendor/http-parser/000077500000000000000000000000001225173000200244045ustar00rootroot00000000000000http_parser.rb-0.6.0/ext/ruby_http_parser/vendor/http-parser-java/000077500000000000000000000000001225173000200253235ustar00rootroot00000000000000http_parser.rb-0.6.0/http_parser.rb.gemspec000066400000000000000000000017721225173000200207530ustar00rootroot00000000000000Gem::Specification.new do |s| s.name = "http_parser.rb" s.version = "0.6.0" s.summary = "Simple callback-based HTTP request/response parser" s.description = "Ruby bindings to http://github.com/ry/http-parser and http://github.com/a2800276/http-parser.java" s.authors = ["Marc-Andre Cournoyer", "Aman Gupta"] s.email = ["macournoyer@gmail.com", "aman@tmm1.net"] s.license = 'MIT' s.homepage = "http://github.com/tmm1/http_parser.rb" s.files = `git ls-files`.split("\n") + Dir['ext/ruby_http_parser/vendor/**/*'] s.require_paths = ["lib"] s.extensions = ["ext/ruby_http_parser/extconf.rb"] s.add_development_dependency 'rake-compiler', '>= 0.7.9' s.add_development_dependency 'rspec', '>= 2.0.1' s.add_development_dependency 'json', '>= 1.4.6' s.add_development_dependency 'benchmark_suite' s.add_development_dependency 'ffi' if RUBY_PLATFORM =~ /java/ s.add_development_dependency 'jruby-openssl' else s.add_development_dependency 'yajl-ruby', '>= 0.8.1' end end http_parser.rb-0.6.0/lib/000077500000000000000000000000001225173000200152105ustar00rootroot00000000000000http_parser.rb-0.6.0/lib/http/000077500000000000000000000000001225173000200161675ustar00rootroot00000000000000http_parser.rb-0.6.0/lib/http/parser.rb000066400000000000000000000000261225173000200200060ustar00rootroot00000000000000require 'http_parser' http_parser.rb-0.6.0/lib/http_parser.rb000066400000000000000000000007301225173000200200700ustar00rootroot00000000000000$:.unshift File.expand_path('../', __FILE__) require 'ruby_http_parser' Http = HTTP module HTTP class Parser class << self attr_reader :default_header_value_type def default_header_value_type=(val) if (val != :mixed && val != :strings && val != :arrays) raise ArgumentError, "Invalid header value type" end @default_header_value_type = val end end end end HTTP::Parser.default_header_value_type = :mixed http_parser.rb-0.6.0/spec/000077500000000000000000000000001225173000200153745ustar00rootroot00000000000000http_parser.rb-0.6.0/spec/parser_spec.rb000066400000000000000000000230621225173000200202320ustar00rootroot00000000000000require "spec_helper" require "json" describe HTTP::Parser do before do @parser = HTTP::Parser.new @headers = nil @body = "" @started = false @done = false @parser.on_message_begin = proc{ @started = true } @parser.on_headers_complete = proc { |e| @headers = e } @parser.on_body = proc { |chunk| @body << chunk } @parser.on_message_complete = proc{ @done = true } end it "should have initial state" do @parser.headers.should be_nil @parser.http_version.should be_nil @parser.http_method.should be_nil @parser.status_code.should be_nil @parser.request_url.should be_nil @parser.header_value_type.should == :mixed end it "should allow us to set the header value type" do [:mixed, :arrays, :strings].each do |type| @parser.header_value_type = type @parser.header_value_type.should == type parser_tmp = HTTP::Parser.new(nil, type) parser_tmp.header_value_type.should == type end end it "should allow us to set the default header value type" do [:mixed, :arrays, :strings].each do |type| HTTP::Parser.default_header_value_type = type parser = HTTP::Parser.new parser.header_value_type.should == type end end it "should throw an Argument Error if header value type is invalid" do proc{ @parser.header_value_type = 'bob' }.should raise_error(ArgumentError) end it "should throw an Argument Error if default header value type is invalid" do proc{ HTTP::Parser.default_header_value_type = 'bob' }.should raise_error(ArgumentError) end it "should implement basic api" do @parser << "GET /test?ok=1 HTTP/1.1\r\n" + "User-Agent: curl/7.18.0\r\n" + "Host: 0.0.0.0:5000\r\n" + "Accept: */*\r\n" + "Content-Length: 5\r\n" + "\r\n" + "World" @started.should be_true @done.should be_true @parser.http_major.should == 1 @parser.http_minor.should == 1 @parser.http_version.should == [1,1] @parser.http_method.should == 'GET' @parser.status_code.should be_nil @parser.request_url.should == '/test?ok=1' @parser.headers.should == @headers @parser.headers['User-Agent'].should == 'curl/7.18.0' @parser.headers['Host'].should == '0.0.0.0:5000' @body.should == "World" end it "should raise errors on invalid data" do proc{ @parser << "BLAH" }.should raise_error(HTTP::Parser::Error) end it "should abort parser via callback" do @parser.on_headers_complete = proc { |e| @headers = e; :stop } data = "GET / HTTP/1.0\r\n" + "Content-Length: 5\r\n" + "\r\n" + "World" bytes = @parser << data bytes.should == 37 data[bytes..-1].should == 'World' @headers.should == {'Content-Length' => '5'} @body.should be_empty @done.should be_false end it "should reset to initial state" do @parser << "GET / HTTP/1.0\r\n\r\n" @parser.http_method.should == 'GET' @parser.http_version.should == [1,0] @parser.request_url.should == '/' @parser.reset!.should be_true @parser.http_version.should be_nil @parser.http_method.should be_nil @parser.status_code.should be_nil @parser.request_url.should be_nil end it "should optionally reset parser state on no-body responses" do @parser.reset!.should be_true @head, @complete = 0, 0 @parser.on_headers_complete = proc {|h| @head += 1; :reset } @parser.on_message_complete = proc { @complete += 1 } @parser.on_body = proc {|b| fail } head_response = "HTTP/1.1 200 OK\r\nContent-Length:10\r\n\r\n" @parser << head_response @head.should == 1 @complete.should == 1 @parser << head_response @head.should == 2 @complete.should == 2 end it "should retain callbacks after reset" do @parser.reset!.should be_true @parser << "GET / HTTP/1.0\r\n\r\n" @started.should be_true @headers.should == {} @done.should be_true end it "should parse headers incrementally" do request = "GET / HTTP/1.0\r\n" + "Header1: value 1\r\n" + "Header2: value 2\r\n" + "\r\n" while chunk = request.slice!(0,2) and !chunk.empty? @parser << chunk end @parser.headers.should == { 'Header1' => 'value 1', 'Header2' => 'value 2' } end it "should handle multiple headers using strings" do @parser.header_value_type = :strings @parser << "GET / HTTP/1.0\r\n" + "Set-Cookie: PREF=ID=a7d2c98; expires=Fri, 05-Apr-2013 05:00:45 GMT; path=/; domain=.bob.com\r\n" + "Set-Cookie: NID=46jSHxPM; path=/; domain=.bob.com; HttpOnly\r\n" + "\r\n" @parser.headers["Set-Cookie"].should == "PREF=ID=a7d2c98; expires=Fri, 05-Apr-2013 05:00:45 GMT; path=/; domain=.bob.com, NID=46jSHxPM; path=/; domain=.bob.com; HttpOnly" end it "should handle multiple headers using strings" do @parser.header_value_type = :arrays @parser << "GET / HTTP/1.0\r\n" + "Set-Cookie: PREF=ID=a7d2c98; expires=Fri, 05-Apr-2013 05:00:45 GMT; path=/; domain=.bob.com\r\n" + "Set-Cookie: NID=46jSHxPM; path=/; domain=.bob.com; HttpOnly\r\n" + "\r\n" @parser.headers["Set-Cookie"].should == [ "PREF=ID=a7d2c98; expires=Fri, 05-Apr-2013 05:00:45 GMT; path=/; domain=.bob.com", "NID=46jSHxPM; path=/; domain=.bob.com; HttpOnly" ] end it "should handle multiple headers using mixed" do @parser.header_value_type = :mixed @parser << "GET / HTTP/1.0\r\n" + "Set-Cookie: PREF=ID=a7d2c98; expires=Fri, 05-Apr-2013 05:00:45 GMT; path=/; domain=.bob.com\r\n" + "Set-Cookie: NID=46jSHxPM; path=/; domain=.bob.com; HttpOnly\r\n" + "\r\n" @parser.headers["Set-Cookie"].should == [ "PREF=ID=a7d2c98; expires=Fri, 05-Apr-2013 05:00:45 GMT; path=/; domain=.bob.com", "NID=46jSHxPM; path=/; domain=.bob.com; HttpOnly" ] end it "should handle a single cookie using mixed" do @parser.header_value_type = :mixed @parser << "GET / HTTP/1.0\r\n" + "Set-Cookie: PREF=ID=a7d2c98; expires=Fri, 05-Apr-2013 05:00:45 GMT; path=/; domain=.bob.com\r\n" + "\r\n" @parser.headers["Set-Cookie"].should == "PREF=ID=a7d2c98; expires=Fri, 05-Apr-2013 05:00:45 GMT; path=/; domain=.bob.com" end it "should support alternative api" do callbacks = double('callbacks') callbacks.stub(:on_message_begin){ @started = true } callbacks.stub(:on_headers_complete){ |e| @headers = e } callbacks.stub(:on_body){ |chunk| @body << chunk } callbacks.stub(:on_message_complete){ @done = true } @parser = HTTP::Parser.new(callbacks) @parser << "GET / HTTP/1.0\r\n\r\n" @started.should be_true @headers.should == {} @body.should == '' @done.should be_true end it "should ignore extra content beyond specified length" do @parser << "GET / HTTP/1.0\r\n" + "Content-Length: 5\r\n" + "\r\n" + "hello" + " \n" @body.should == 'hello' @done.should be_true end it 'sets upgrade_data if available' do @parser << "GET /demo HTTP/1.1\r\n" + "Connection: Upgrade\r\n" + "Upgrade: WebSocket\r\n\r\n" + "third key data" @parser.upgrade?.should be_true @parser.upgrade_data.should == 'third key data' end it 'sets upgrade_data to blank if un-available' do @parser << "GET /demo HTTP/1.1\r\n" + "Connection: Upgrade\r\n" + "Upgrade: WebSocket\r\n\r\n" @parser.upgrade?.should be_true @parser.upgrade_data.should == '' end it 'should stop parsing headers when instructed' do request = "GET /websocket HTTP/1.1\r\n" + "host: localhost\r\n" + "connection: Upgrade\r\n" + "upgrade: websocket\r\n" + "sec-websocket-key: SD6/hpYbKjQ6Sown7pBbWQ==\r\n" + "sec-websocket-version: 13\r\n" + "\r\n" @parser.on_headers_complete = proc { |e| :stop } offset = (@parser << request) @parser.upgrade?.should be_true @parser.upgrade_data.should == '' offset.should == request.length end it "should execute on_body on requests with no content-length" do @parser.reset!.should be_true @head, @complete, @body = 0, 0, 0 @parser.on_headers_complete = proc {|h| @head += 1 } @parser.on_message_complete = proc { @complete += 1 } @parser.on_body = proc {|b| @body += 1 } head_response = "HTTP/1.1 200 OK\r\n\r\nstuff" @parser << head_response @parser << '' @head.should == 1 @complete.should == 1 @body.should == 1 end %w[ request response ].each do |type| JSON.parse(File.read(File.expand_path("../support/#{type}s.json", __FILE__))).each do |test| test['headers'] ||= {} next if !defined?(JRUBY_VERSION) and HTTP::Parser.strict? != test['strict'] it "should parse #{type}: #{test['name']}" do @parser << test['raw'] @parser.http_method.should == test['method'] @parser.keep_alive?.should == test['should_keep_alive'] if test.has_key?('upgrade') and test['upgrade'] != 0 @parser.upgrade?.should be_true @parser.upgrade_data.should == test['upgrade'] end fields = %w[ http_major http_minor ] if test['type'] == 'HTTP_REQUEST' fields += %w[ request_url ] else fields += %w[ status_code ] end fields.each do |field| @parser.send(field).should == test[field] end @headers.size.should == test['num_headers'] @headers.should == test['headers'] @body.should == test['body'] @body.size.should == test['body_size'] if test['body_size'] end end end end http_parser.rb-0.6.0/spec/spec_helper.rb000066400000000000000000000000261225173000200202100ustar00rootroot00000000000000require "http_parser" http_parser.rb-0.6.0/spec/support/000077500000000000000000000000001225173000200171105ustar00rootroot00000000000000http_parser.rb-0.6.0/spec/support/requests.json000066400000000000000000000424141225173000200216630ustar00rootroot00000000000000[ { "name": "curl get", "type": "HTTP_REQUEST", "raw": "GET /test HTTP/1.1\r\nUser-Agent: curl/7.18.0 (i486-pc-linux-gnu) libcurl/7.18.0 OpenSSL/0.9.8g zlib/1.2.3.3 libidn/1.1\r\nHost: 0.0.0.0=5000\r\nAccept: */*\r\n\r\n", "should_keep_alive": true, "message_complete_on_eof": false, "http_major": 1, "http_minor": 1, "method": "GET", "query_string": "", "fragment": "", "request_path": "/test", "request_url": "/test", "num_headers": 3, "headers": { "User-Agent": "curl/7.18.0 (i486-pc-linux-gnu) libcurl/7.18.0 OpenSSL/0.9.8g zlib/1.2.3.3 libidn/1.1", "Host": "0.0.0.0=5000", "Accept": "*/*" }, "body": "", "strict": true }, { "name": "firefox get", "type": "HTTP_REQUEST", "raw": "GET /favicon.ico HTTP/1.1\r\nHost: 0.0.0.0=5000\r\nUser-Agent: Mozilla/5.0 (X11; U; Linux i686; en-US; rv:1.9) Gecko/2008061015 Firefox/3.0\r\nAccept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8\r\nAccept-Language: en-us,en;q=0.5\r\nAccept-Encoding: gzip,deflate\r\nAccept-Charset: ISO-8859-1,utf-8;q=0.7,*;q=0.7\r\nKeep-Alive: 300\r\nConnection: keep-alive\r\n\r\n", "should_keep_alive": true, "message_complete_on_eof": false, "http_major": 1, "http_minor": 1, "method": "GET", "query_string": "", "fragment": "", "request_path": "/favicon.ico", "request_url": "/favicon.ico", "num_headers": 8, "headers": { "Host": "0.0.0.0=5000", "User-Agent": "Mozilla/5.0 (X11; U; Linux i686; en-US; rv:1.9) Gecko/2008061015 Firefox/3.0", "Accept": "text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8", "Accept-Language": "en-us,en;q=0.5", "Accept-Encoding": "gzip,deflate", "Accept-Charset": "ISO-8859-1,utf-8;q=0.7,*;q=0.7", "Keep-Alive": "300", "Connection": "keep-alive" }, "body": "", "strict": true }, { "name": "dumbfuck", "type": "HTTP_REQUEST", "raw": "GET /dumbfuck HTTP/1.1\r\naaaaaaaaaaaaa:++++++++++\r\n\r\n", "should_keep_alive": true, "message_complete_on_eof": false, "http_major": 1, "http_minor": 1, "method": "GET", "query_string": "", "fragment": "", "request_path": "/dumbfuck", "request_url": "/dumbfuck", "num_headers": 1, "headers": { "aaaaaaaaaaaaa": "++++++++++" }, "body": "", "strict": true }, { "name": "fragment in url", "type": "HTTP_REQUEST", "raw": "GET /forums/1/topics/2375?page=1#posts-17408 HTTP/1.1\r\n\r\n", "should_keep_alive": true, "message_complete_on_eof": false, "http_major": 1, "http_minor": 1, "method": "GET", "query_string": "page=1", "fragment": "posts-17408", "request_path": "/forums/1/topics/2375", "request_url": "/forums/1/topics/2375?page=1#posts-17408", "num_headers": 0, "body": "", "strict": true }, { "name": "get no headers no body", "type": "HTTP_REQUEST", "raw": "GET /get_no_headers_no_body/world HTTP/1.1\r\n\r\n", "should_keep_alive": true, "message_complete_on_eof": false, "http_major": 1, "http_minor": 1, "method": "GET", "query_string": "", "fragment": "", "request_path": "/get_no_headers_no_body/world", "request_url": "/get_no_headers_no_body/world", "num_headers": 0, "body": "", "strict": true }, { "name": "get one header no body", "type": "HTTP_REQUEST", "raw": "GET /get_one_header_no_body HTTP/1.1\r\nAccept: */*\r\n\r\n", "should_keep_alive": true, "message_complete_on_eof": false, "http_major": 1, "http_minor": 1, "method": "GET", "query_string": "", "fragment": "", "request_path": "/get_one_header_no_body", "request_url": "/get_one_header_no_body", "num_headers": 1, "headers": { "Accept": "*/*" }, "body": "", "strict": true }, { "name": "get funky content length body hello", "type": "HTTP_REQUEST", "raw": "GET /get_funky_content_length_body_hello HTTP/1.0\r\nconTENT-Length: 5\r\n\r\nHELLO", "should_keep_alive": false, "message_complete_on_eof": false, "http_major": 1, "http_minor": 0, "method": "GET", "query_string": "", "fragment": "", "request_path": "/get_funky_content_length_body_hello", "request_url": "/get_funky_content_length_body_hello", "num_headers": 1, "headers": { "conTENT-Length": "5" }, "body": "HELLO", "strict": true }, { "name": "post identity body world", "type": "HTTP_REQUEST", "raw": "POST /post_identity_body_world?q=search#hey HTTP/1.1\r\nAccept: */*\r\nTransfer-Encoding: identity\r\nContent-Length: 5\r\n\r\nWorld", "should_keep_alive": true, "message_complete_on_eof": false, "http_major": 1, "http_minor": 1, "method": "POST", "query_string": "q=search", "fragment": "hey", "request_path": "/post_identity_body_world", "request_url": "/post_identity_body_world?q=search#hey", "num_headers": 3, "headers": { "Accept": "*/*", "Transfer-Encoding": "identity", "Content-Length": "5" }, "body": "World", "strict": true }, { "name": "post - chunked body: all your base are belong to us", "type": "HTTP_REQUEST", "raw": "POST /post_chunked_all_your_base HTTP/1.1\r\nTransfer-Encoding: chunked\r\n\r\n1e\r\nall your base are belong to us\r\n0\r\n\r\n", "should_keep_alive": true, "message_complete_on_eof": false, "http_major": 1, "http_minor": 1, "method": "POST", "query_string": "", "fragment": "", "request_path": "/post_chunked_all_your_base", "request_url": "/post_chunked_all_your_base", "num_headers": 1, "headers": { "Transfer-Encoding": "chunked" }, "body": "all your base are belong to us", "strict": true }, { "name": "two chunks ; triple zero ending", "type": "HTTP_REQUEST", "raw": "POST /two_chunks_mult_zero_end HTTP/1.1\r\nTransfer-Encoding: chunked\r\n\r\n5\r\nhello\r\n6\r\n world\r\n000\r\n\r\n", "should_keep_alive": true, "message_complete_on_eof": false, "http_major": 1, "http_minor": 1, "method": "POST", "query_string": "", "fragment": "", "request_path": "/two_chunks_mult_zero_end", "request_url": "/two_chunks_mult_zero_end", "num_headers": 1, "headers": { "Transfer-Encoding": "chunked" }, "body": "hello world", "strict": true }, { "name": "chunked with trailing headers. blech.", "type": "HTTP_REQUEST", "raw": "POST /chunked_w_trailing_headers HTTP/1.1\r\nTransfer-Encoding: chunked\r\n\r\n5\r\nhello\r\n6\r\n world\r\n0\r\nVary: *\r\nContent-Type: text/plain\r\n\r\n", "should_keep_alive": true, "message_complete_on_eof": false, "http_major": 1, "http_minor": 1, "method": "POST", "query_string": "", "fragment": "", "request_path": "/chunked_w_trailing_headers", "request_url": "/chunked_w_trailing_headers", "num_headers": 3, "headers": { "Transfer-Encoding": "chunked", "Vary": "*", "Content-Type": "text/plain" }, "body": "hello world", "strict": true }, { "name": "with bullshit after the length", "type": "HTTP_REQUEST", "raw": "POST /chunked_w_bullshit_after_length HTTP/1.1\r\nTransfer-Encoding: chunked\r\n\r\n5; ihatew3;whatthefuck=aretheseparametersfor\r\nhello\r\n6; blahblah; blah\r\n world\r\n0\r\n\r\n", "should_keep_alive": true, "message_complete_on_eof": false, "http_major": 1, "http_minor": 1, "method": "POST", "query_string": "", "fragment": "", "request_path": "/chunked_w_bullshit_after_length", "request_url": "/chunked_w_bullshit_after_length", "num_headers": 1, "headers": { "Transfer-Encoding": "chunked" }, "body": "hello world", "strict": true }, { "name": "with quotes", "type": "HTTP_REQUEST", "raw": "GET /with_\"stupid\"_quotes?foo=\"bar\" HTTP/1.1\r\n\r\n", "should_keep_alive": true, "message_complete_on_eof": false, "http_major": 1, "http_minor": 1, "method": "GET", "query_string": "foo=\"bar\"", "fragment": "", "request_path": "/with_\"stupid\"_quotes", "request_url": "/with_\"stupid\"_quotes?foo=\"bar\"", "num_headers": 0, "headers": { }, "body": "", "strict": true }, { "name": "apachebench get", "type": "HTTP_REQUEST", "raw": "GET /test HTTP/1.0\r\nHost: 0.0.0.0:5000\r\nUser-Agent: ApacheBench/2.3\r\nAccept: */*\r\n\r\n", "should_keep_alive": false, "message_complete_on_eof": false, "http_major": 1, "http_minor": 0, "method": "GET", "query_string": "", "fragment": "", "request_path": "/test", "request_url": "/test", "num_headers": 3, "headers": { "Host": "0.0.0.0:5000", "User-Agent": "ApacheBench/2.3", "Accept": "*/*" }, "body": "", "strict": true }, { "name": "query url with question mark", "type": "HTTP_REQUEST", "raw": "GET /test.cgi?foo=bar?baz HTTP/1.1\r\n\r\n", "should_keep_alive": true, "message_complete_on_eof": false, "http_major": 1, "http_minor": 1, "method": "GET", "query_string": "foo=bar?baz", "fragment": "", "request_path": "/test.cgi", "request_url": "/test.cgi?foo=bar?baz", "num_headers": 0, "headers": { }, "body": "", "strict": true }, { "name": "newline prefix get", "type": "HTTP_REQUEST", "raw": "\r\nGET /test HTTP/1.1\r\n\r\n", "should_keep_alive": true, "message_complete_on_eof": false, "http_major": 1, "http_minor": 1, "method": "GET", "query_string": "", "fragment": "", "request_path": "/test", "request_url": "/test", "num_headers": 0, "headers": { }, "body": "", "strict": true }, { "name": "upgrade request", "type": "HTTP_REQUEST", "raw": "GET /demo HTTP/1.1\r\nHost: example.com\r\nConnection: Upgrade\r\nSec-WebSocket-Key2: 12998 5 Y3 1 .P00\r\nSec-WebSocket-Protocol: sample\r\nUpgrade: WebSocket\r\nSec-WebSocket-Key1: 4 @1 46546xW%0l 1 5\r\nOrigin: http://example.com\r\n\r\nHot diggity dogg", "should_keep_alive": true, "message_complete_on_eof": false, "http_major": 1, "http_minor": 1, "method": "GET", "query_string": "", "fragment": "", "request_path": "/demo", "request_url": "/demo", "num_headers": 7, "upgrade": "Hot diggity dogg", "headers": { "Host": "example.com", "Connection": "Upgrade", "Sec-WebSocket-Key2": "12998 5 Y3 1 .P00", "Sec-WebSocket-Protocol": "sample", "Upgrade": "WebSocket", "Sec-WebSocket-Key1": "4 @1 46546xW%0l 1 5", "Origin": "http://example.com" }, "body": "", "strict": true }, { "name": "connect request", "type": "HTTP_REQUEST", "raw": "CONNECT 0-home0.netscape.com:443 HTTP/1.0\r\nUser-agent: Mozilla/1.1N\r\nProxy-authorization: basic aGVsbG86d29ybGQ=\r\n\r\nsome data\r\nand yet even more data", "should_keep_alive": false, "message_complete_on_eof": false, "http_major": 1, "http_minor": 0, "method": "CONNECT", "query_string": "", "fragment": "", "request_path": "", "request_url": "0-home0.netscape.com:443", "num_headers": 2, "upgrade": "some data\r\nand yet even more data", "headers": { "User-agent": "Mozilla/1.1N", "Proxy-authorization": "basic aGVsbG86d29ybGQ=" }, "body": "", "strict": true }, { "name": "report request", "type": "HTTP_REQUEST", "raw": "REPORT /test HTTP/1.1\r\n\r\n", "should_keep_alive": true, "message_complete_on_eof": false, "http_major": 1, "http_minor": 1, "method": "REPORT", "query_string": "", "fragment": "", "request_path": "/test", "request_url": "/test", "num_headers": 0, "headers": { }, "body": "", "strict": true }, { "name": "request with no http version", "type": "HTTP_REQUEST", "raw": "GET /\r\n\r\n", "should_keep_alive": false, "message_complete_on_eof": false, "http_major": 0, "http_minor": 9, "method": "GET", "query_string": "", "fragment": "", "request_path": "/", "request_url": "/", "num_headers": 0, "headers": { }, "body": "", "strict": true }, { "name": "m-search request", "type": "HTTP_REQUEST", "raw": "M-SEARCH * HTTP/1.1\r\nHOST: 239.255.255.250:1900\r\nMAN: \"ssdp:discover\"\r\nST: \"ssdp:all\"\r\n\r\n", "should_keep_alive": true, "message_complete_on_eof": false, "http_major": 1, "http_minor": 1, "method": "M-SEARCH", "query_string": "", "fragment": "", "request_path": "*", "request_url": "*", "num_headers": 3, "headers": { "HOST": "239.255.255.250:1900", "MAN": "\"ssdp:discover\"", "ST": "\"ssdp:all\"" }, "body": "", "strict": true }, { "name": "line folding in header value", "type": "HTTP_REQUEST", "raw": "GET / HTTP/1.1\r\nLine1: abc\r\n\tdef\r\n ghi\r\n\t\tjkl\r\n mno \r\n\t \tqrs\r\nLine2: \t line2\t\r\n\r\n", "should_keep_alive": true, "message_complete_on_eof": false, "http_major": 1, "http_minor": 1, "method": "GET", "query_string": "", "fragment": "", "request_path": "/", "request_url": "/", "num_headers": 2, "headers": { "Line1": "abcdefghijklmno qrs", "Line2": "line2\t" }, "body": "", "strict": true }, { "name": "host terminated by a query string", "type": "HTTP_REQUEST", "raw": "GET http://hypnotoad.org?hail=all HTTP/1.1\r\n\r\n", "should_keep_alive": true, "message_complete_on_eof": false, "http_major": 1, "http_minor": 1, "method": "GET", "query_string": "hail=all", "fragment": "", "request_path": "", "request_url": "http://hypnotoad.org?hail=all", "num_headers": 0, "headers": { }, "body": "", "strict": true }, { "name": "host:port terminated by a query string", "type": "HTTP_REQUEST", "raw": "GET http://hypnotoad.org:1234?hail=all HTTP/1.1\r\n\r\n", "should_keep_alive": true, "message_complete_on_eof": false, "http_major": 1, "http_minor": 1, "method": "GET", "query_string": "hail=all", "fragment": "", "request_path": "", "request_url": "http://hypnotoad.org:1234?hail=all", "port": 1234, "num_headers": 0, "headers": { }, "body": "", "strict": true }, { "name": "host:port terminated by a space", "type": "HTTP_REQUEST", "raw": "GET http://hypnotoad.org:1234 HTTP/1.1\r\n\r\n", "should_keep_alive": true, "message_complete_on_eof": false, "http_major": 1, "http_minor": 1, "method": "GET", "query_string": "", "fragment": "", "request_path": "", "request_url": "http://hypnotoad.org:1234", "port": 1234, "num_headers": 0, "headers": { }, "body": "", "strict": true }, { "name": "PATCH request", "type": "HTTP_REQUEST", "raw": "PATCH /file.txt HTTP/1.1\r\nHost: www.example.com\r\nContent-Type: application/example\r\nIf-Match: \"e0023aa4e\"\r\nContent-Length: 10\r\n\r\ncccccccccc", "should_keep_alive": true, "message_complete_on_eof": false, "http_major": 1, "http_minor": 1, "method": "PATCH", "query_string": "", "fragment": "", "request_path": "/file.txt", "request_url": "/file.txt", "num_headers": 4, "headers": { "Host": "www.example.com", "Content-Type": "application/example", "If-Match": "\"e0023aa4e\"", "Content-Length": "10" }, "body": "cccccccccc", "strict": true }, { "name": "connect caps request", "type": "HTTP_REQUEST", "raw": "CONNECT HOME0.NETSCAPE.COM:443 HTTP/1.0\r\nUser-agent: Mozilla/1.1N\r\nProxy-authorization: basic aGVsbG86d29ybGQ=\r\n\r\n", "should_keep_alive": false, "message_complete_on_eof": false, "http_major": 1, "http_minor": 0, "method": "CONNECT", "query_string": "", "fragment": "", "request_path": "", "request_url": "HOME0.NETSCAPE.COM:443", "num_headers": 2, "upgrade": "", "headers": { "User-agent": "Mozilla/1.1N", "Proxy-authorization": "basic aGVsbG86d29ybGQ=" }, "body": "", "strict": true }, { "name": "utf-8 path request", "type": "HTTP_REQUEST", "strict": false, "raw": "GET /δ¶/δt/pope?q=1#narf HTTP/1.1\r\nHost: github.com\r\n\r\n", "should_keep_alive": true, "message_complete_on_eof": false, "http_major": 1, "http_minor": 1, "method": "GET", "query_string": "q=1", "fragment": "narf", "request_path": "/δ¶/δt/pope", "request_url": "/δ¶/δt/pope?q=1#narf", "num_headers": 1, "headers": { "Host": "github.com" }, "body": "" }, { "name": "hostname underscore", "type": "HTTP_REQUEST", "strict": false, "raw": "CONNECT home_0.netscape.com:443 HTTP/1.0\r\nUser-agent: Mozilla/1.1N\r\nProxy-authorization: basic aGVsbG86d29ybGQ=\r\n\r\n", "should_keep_alive": false, "message_complete_on_eof": false, "http_major": 1, "http_minor": 0, "method": "CONNECT", "query_string": "", "fragment": "", "request_path": "", "request_url": "home_0.netscape.com:443", "num_headers": 2, "upgrade": "", "headers": { "User-agent": "Mozilla/1.1N", "Proxy-authorization": "basic aGVsbG86d29ybGQ=" }, "body": "" } ]http_parser.rb-0.6.0/spec/support/responses.json000066400000000000000000000306401225173000200220270ustar00rootroot00000000000000[ { "name": "google 301", "type": "HTTP_RESPONSE", "raw": "HTTP/1.1 301 Moved Permanently\r\nLocation: http://www.google.com/\r\nContent-Type: text/html; charset=UTF-8\r\nDate: Sun, 26 Apr 2009 11:11:49 GMT\r\nExpires: Tue, 26 May 2009 11:11:49 GMT\r\nX-$PrototypeBI-Version: 1.6.0.3\r\nCache-Control: public, max-age=2592000\r\nServer: gws\r\nContent-Length: 219 \r\n\r\n\n301 Moved\n

301 Moved

\nThe document has moved\nhere.\r\n\r\n", "should_keep_alive": true, "message_complete_on_eof": false, "http_major": 1, "http_minor": 1, "status_code": 301, "num_headers": 8, "headers": { "Location": "http://www.google.com/", "Content-Type": "text/html; charset=UTF-8", "Date": "Sun, 26 Apr 2009 11:11:49 GMT", "Expires": "Tue, 26 May 2009 11:11:49 GMT", "X-$PrototypeBI-Version": "1.6.0.3", "Cache-Control": "public, max-age=2592000", "Server": "gws", "Content-Length": "219 " }, "body": "\n301 Moved\n

301 Moved

\nThe document has moved\nhere.\r\n\r\n", "strict": true }, { "name": "no content-length response", "type": "HTTP_RESPONSE", "raw": "HTTP/1.1 200 OK\r\nDate: Tue, 04 Aug 2009 07:59:32 GMT\r\nServer: Apache\r\nX-Powered-By: Servlet/2.5 JSP/2.1\r\nContent-Type: text/xml; charset=utf-8\r\nConnection: close\r\n\r\n\n\n \n \n SOAP-ENV:Client\n Client Error\n \n \n", "should_keep_alive": false, "message_complete_on_eof": true, "http_major": 1, "http_minor": 1, "status_code": 200, "num_headers": 5, "headers": { "Date": "Tue, 04 Aug 2009 07:59:32 GMT", "Server": "Apache", "X-Powered-By": "Servlet/2.5 JSP/2.1", "Content-Type": "text/xml; charset=utf-8", "Connection": "close" }, "body": "\n\n \n \n SOAP-ENV:Client\n Client Error\n \n \n", "strict": true }, { "name": "404 no headers no body", "type": "HTTP_RESPONSE", "raw": "HTTP/1.1 404 Not Found\r\n\r\n", "should_keep_alive": false, "message_complete_on_eof": true, "http_major": 1, "http_minor": 1, "status_code": 404, "num_headers": 0, "headers": { }, "body_size": 0, "body": "", "strict": true }, { "name": "301 no response phrase", "type": "HTTP_RESPONSE", "raw": "HTTP/1.1 301\r\n\r\n", "should_keep_alive": false, "message_complete_on_eof": true, "http_major": 1, "http_minor": 1, "status_code": 301, "num_headers": 0, "headers": { }, "body": "", "strict": true }, { "name": "200 trailing space on chunked body", "type": "HTTP_RESPONSE", "raw": "HTTP/1.1 200 OK\r\nContent-Type: text/plain\r\nTransfer-Encoding: chunked\r\n\r\n25 \r\nThis is the data in the first chunk\r\n\r\n1C\r\nand this is the second one\r\n\r\n0 \r\n\r\n", "should_keep_alive": true, "message_complete_on_eof": false, "http_major": 1, "http_minor": 1, "status_code": 200, "num_headers": 2, "headers": { "Content-Type": "text/plain", "Transfer-Encoding": "chunked" }, "body_size": 65, "body": "This is the data in the first chunk\r\nand this is the second one\r\n", "strict": true }, { "name": "no carriage ret", "type": "HTTP_RESPONSE", "raw": "HTTP/1.1 200 OK\nContent-Type: text/html; charset=utf-8\nConnection: close\n\nthese headers are from http://news.ycombinator.com/", "should_keep_alive": false, "message_complete_on_eof": true, "http_major": 1, "http_minor": 1, "status_code": 200, "num_headers": 2, "headers": { "Content-Type": "text/html; charset=utf-8", "Connection": "close" }, "body": "these headers are from http://news.ycombinator.com/", "strict": true }, { "name": "proxy connection", "type": "HTTP_RESPONSE", "raw": "HTTP/1.1 200 OK\r\nContent-Type: text/html; charset=UTF-8\r\nContent-Length: 11\r\nProxy-Connection: close\r\nDate: Thu, 31 Dec 2009 20:55:48 +0000\r\n\r\nhello world", "should_keep_alive": false, "message_complete_on_eof": false, "http_major": 1, "http_minor": 1, "status_code": 200, "num_headers": 4, "headers": { "Content-Type": "text/html; charset=UTF-8", "Content-Length": "11", "Proxy-Connection": "close", "Date": "Thu, 31 Dec 2009 20:55:48 +0000" }, "body": "hello world", "strict": true }, { "name": "underscore header key", "type": "HTTP_RESPONSE", "raw": "HTTP/1.1 200 OK\r\nServer: DCLK-AdSvr\r\nContent-Type: text/xml\r\nContent-Length: 0\r\nDCLK_imp: v7;x;114750856;0-0;0;17820020;0/0;21603567/21621457/1;;~okv=;dcmt=text/xml;;~cs=o\r\n\r\n", "should_keep_alive": true, "message_complete_on_eof": false, "http_major": 1, "http_minor": 1, "status_code": 200, "num_headers": 4, "headers": { "Server": "DCLK-AdSvr", "Content-Type": "text/xml", "Content-Length": "0", "DCLK_imp": "v7;x;114750856;0-0;0;17820020;0/0;21603567/21621457/1;;~okv=;dcmt=text/xml;;~cs=o" }, "body": "", "strict": true }, { "name": "bonjourmadame.fr", "type": "HTTP_RESPONSE", "raw": "HTTP/1.0 301 Moved Permanently\r\nDate: Thu, 03 Jun 2010 09:56:32 GMT\r\nServer: Apache/2.2.3 (Red Hat)\r\nCache-Control: public\r\nPragma: \r\nLocation: http://www.bonjourmadame.fr/\r\nVary: Accept-Encoding\r\nContent-Length: 0\r\nContent-Type: text/html; charset=UTF-8\r\nConnection: keep-alive\r\n\r\n", "should_keep_alive": true, "message_complete_on_eof": false, "http_major": 1, "http_minor": 0, "status_code": 301, "num_headers": 9, "headers": { "Date": "Thu, 03 Jun 2010 09:56:32 GMT", "Server": "Apache/2.2.3 (Red Hat)", "Cache-Control": "public", "Pragma": "", "Location": "http://www.bonjourmadame.fr/", "Vary": "Accept-Encoding", "Content-Length": "0", "Content-Type": "text/html; charset=UTF-8", "Connection": "keep-alive" }, "body": "", "strict": true }, { "name": "field underscore", "type": "HTTP_RESPONSE", "raw": "HTTP/1.1 200 OK\r\nDate: Tue, 28 Sep 2010 01:14:13 GMT\r\nServer: Apache\r\nCache-Control: no-cache, must-revalidate\r\nExpires: Mon, 26 Jul 1997 05:00:00 GMT\r\n.et-Cookie: PlaxoCS=1274804622353690521; path=/; domain=.plaxo.com\r\nVary: Accept-Encoding\r\n_eep-Alive: timeout=45\r\n_onnection: Keep-Alive\r\nTransfer-Encoding: chunked\r\nContent-Type: text/html\r\nConnection: close\r\n\r\n0\r\n\r\n", "should_keep_alive": false, "message_complete_on_eof": false, "http_major": 1, "http_minor": 1, "status_code": 200, "num_headers": 11, "headers": { "Date": "Tue, 28 Sep 2010 01:14:13 GMT", "Server": "Apache", "Cache-Control": "no-cache, must-revalidate", "Expires": "Mon, 26 Jul 1997 05:00:00 GMT", ".et-Cookie": "PlaxoCS=1274804622353690521; path=/; domain=.plaxo.com", "Vary": "Accept-Encoding", "_eep-Alive": "timeout=45", "_onnection": "Keep-Alive", "Transfer-Encoding": "chunked", "Content-Type": "text/html", "Connection": "close" }, "body": "", "strict": true }, { "name": "non-ASCII in status line", "type": "HTTP_RESPONSE", "raw": "HTTP/1.1 500 Oriëntatieprobleem\r\nDate: Fri, 5 Nov 2010 23:07:12 GMT+2\r\nContent-Length: 0\r\nConnection: close\r\n\r\n", "should_keep_alive": false, "message_complete_on_eof": false, "http_major": 1, "http_minor": 1, "status_code": 500, "num_headers": 3, "headers": { "Date": "Fri, 5 Nov 2010 23:07:12 GMT+2", "Content-Length": "0", "Connection": "close" }, "body": "", "strict": true }, { "name": "http version 0.9", "type": "HTTP_RESPONSE", "raw": "HTTP/0.9 200 OK\r\n\r\n", "should_keep_alive": false, "message_complete_on_eof": true, "http_major": 0, "http_minor": 9, "status_code": 200, "num_headers": 0, "headers": { }, "body": "", "strict": true }, { "name": "neither content-length nor transfer-encoding response", "type": "HTTP_RESPONSE", "raw": "HTTP/1.1 200 OK\r\nContent-Type: text/plain\r\n\r\nhello world", "should_keep_alive": false, "message_complete_on_eof": true, "http_major": 1, "http_minor": 1, "status_code": 200, "num_headers": 1, "headers": { "Content-Type": "text/plain" }, "body": "hello world", "strict": true }, { "name": "HTTP/1.0 with keep-alive and EOF-terminated 200 status", "type": "HTTP_RESPONSE", "raw": "HTTP/1.0 200 OK\r\nConnection: keep-alive\r\n\r\n", "should_keep_alive": false, "message_complete_on_eof": true, "http_major": 1, "http_minor": 0, "status_code": 200, "num_headers": 1, "headers": { "Connection": "keep-alive" }, "body_size": 0, "body": "", "strict": true }, { "name": "HTTP/1.0 with keep-alive and a 204 status", "type": "HTTP_RESPONSE", "raw": "HTTP/1.0 204 No content\r\nConnection: keep-alive\r\n\r\n", "should_keep_alive": true, "message_complete_on_eof": false, "http_major": 1, "http_minor": 0, "status_code": 204, "num_headers": 1, "headers": { "Connection": "keep-alive" }, "body_size": 0, "body": "", "strict": true }, { "name": "HTTP/1.1 with an EOF-terminated 200 status", "type": "HTTP_RESPONSE", "raw": "HTTP/1.1 200 OK\r\n\r\n", "should_keep_alive": false, "message_complete_on_eof": true, "http_major": 1, "http_minor": 1, "status_code": 200, "num_headers": 0, "headers": { }, "body_size": 0, "body": "", "strict": true }, { "name": "HTTP/1.1 with a 204 status", "type": "HTTP_RESPONSE", "raw": "HTTP/1.1 204 No content\r\n\r\n", "should_keep_alive": true, "message_complete_on_eof": false, "http_major": 1, "http_minor": 1, "status_code": 204, "num_headers": 0, "headers": { }, "body_size": 0, "body": "", "strict": true }, { "name": "HTTP/1.1 with a 204 status and keep-alive disabled", "type": "HTTP_RESPONSE", "raw": "HTTP/1.1 204 No content\r\nConnection: close\r\n\r\n", "should_keep_alive": false, "message_complete_on_eof": false, "http_major": 1, "http_minor": 1, "status_code": 204, "num_headers": 1, "headers": { "Connection": "close" }, "body_size": 0, "body": "", "strict": true }, { "name": "HTTP/1.1 with chunked endocing and a 200 response", "type": "HTTP_RESPONSE", "raw": "HTTP/1.1 200 OK\r\nTransfer-Encoding: chunked\r\n\r\n0\r\n\r\n", "should_keep_alive": true, "message_complete_on_eof": false, "http_major": 1, "http_minor": 1, "status_code": 200, "num_headers": 1, "headers": { "Transfer-Encoding": "chunked" }, "body_size": 0, "body": "", "strict": true }, { "name": "field space", "type": "HTTP_RESPONSE", "strict": false, "raw": "HTTP/1.1 200 OK\r\nServer: Microsoft-IIS/6.0\r\nX-Powered-By: ASP.NET\r\nen-US Content-Type: text/xml\r\nContent-Type: text/xml\r\nContent-Length: 16\r\nDate: Fri, 23 Jul 2010 18:45:38 GMT\r\nConnection: keep-alive\r\n\r\nhello", "should_keep_alive": true, "message_complete_on_eof": false, "http_major": 1, "http_minor": 1, "status_code": 200, "num_headers": 7, "headers": { "Server": "Microsoft-IIS/6.0", "X-Powered-By": "ASP.NET", "en-US Content-Type": "text/xml", "Content-Type": "text/xml", "Content-Length": "16", "Date": "Fri, 23 Jul 2010 18:45:38 GMT", "Connection": "keep-alive" }, "body": "hello" } ]http_parser.rb-0.6.0/tasks/000077500000000000000000000000001225173000200155675ustar00rootroot00000000000000http_parser.rb-0.6.0/tasks/compile.rake000066400000000000000000000022051225173000200200620ustar00rootroot00000000000000require 'rubygems/package_task' require 'rake/extensiontask' require 'rake/javaextensiontask' def gemspec @clean_gemspec ||= eval(File.read(File.expand_path('../../http_parser.rb.gemspec', __FILE__))) end Gem::PackageTask.new(gemspec) do |pkg| end if RUBY_PLATFORM =~ /java/ Rake::JavaExtensionTask.new("ruby_http_parser", gemspec) do |ext| ext.classpath = File.expand_path('../../ext/ruby_http_parser/vendor/http-parser-java/ext/primitives.jar', __FILE__) end else Rake::ExtensionTask.new("ruby_http_parser", gemspec) do |ext| unless RUBY_PLATFORM =~ /mswin|mingw/ ext.cross_compile = true ext.cross_platform = ['x86-mingw32', 'x86-mswin32-60'] # inject 1.8/1.9 pure-ruby entry point ext.cross_compiling do |spec| spec.files += ['lib/ruby_http_parser.rb'] end end end end file 'lib/ruby_http_parser.rb' do |t| File.open(t.name, 'wb') do |f| f.write <<-eoruby RUBY_VERSION =~ /(\\d+.\\d+)/ require "\#{$1}/ruby_http_parser" eoruby end at_exit{ FileUtils.rm t.name if File.exists?(t.name) } end if Rake::Task.task_defined?(:cross) task :cross => 'lib/ruby_http_parser.rb' end http_parser.rb-0.6.0/tasks/fixtures.rake000066400000000000000000000040721225173000200203070ustar00rootroot00000000000000desc "Generate test fixtures" task :fixtures => :submodules do require 'yajl' data = File.read File.expand_path('../../ext/ruby_http_parser/vendor/http-parser/test.c', __FILE__) %w[ requests responses ].each do |type| # find test definitions in between requests/responses[]= and .name=NULL tmp = data[/#{type}\[\]\s*=(.+?),\s*\{\s*\.name=\s*NULL/m, 1] # replace first { with a [ (parsing an array of test cases) tmp.sub!('{','[') # replace booleans tmp.gsub!('TRUE', 'true') tmp.gsub!('FALSE', 'false') # mark strict mode tests tmp.gsub!(%r|#if\s+!HTTP_PARSER_STRICT(.+?)#endif\s*/\*\s*!HTTP_PARSER_STRICT.+\n|m){ $1.gsub(/^(.+,\.type= .+)$/, "\\1\n, .strict= false") } # remove macros and comments tmp.gsub!(/^#(if|elif|endif|define).+$/,'') tmp.gsub!(/\/\*(.+?)\*\/$/,'') # HTTP_* enums become strings tmp.gsub!(/(= )(HTTP_\w+)/){ "#{$1}#{$2.sub('MSEARCH','M-SEARCH').dump}" } # join multiline strings for body and raw data tmp.gsub!(/((body|raw)\s*=)(.+?)(\n\s+[\},])/m){ before, after = $1, $4 raw = $3.split("\n").map{ |l| l.strip[1..-2] }.join('') "#{before} \"#{raw}\" #{after}" } # make headers an array of array tuples tmp.gsub!(/(\.headers\s*=)(.+?)(\s*,\.)/m){ before, after = $1, $3 raw = $2.gsub('{', '[').gsub('}', ']') "#{before} #{raw} #{after}" } # .name= becomes "name": tmp.gsub!(/^(.{2,5})\.(\w+)\s*=/){ "#{$1}#{$2.dump}: " } # evaluate addition expressions tmp.gsub!(/(body_size\":\s*)(\d+)\+(\d+)/){ "#{$1}#{$2.to_i+$3.to_i}" } # end result array tmp << ']' # normalize data results = Yajl.load(tmp, :symbolize_keys => true) results.map{ |res| res[:headers] and res[:headers] = Hash[*res[:headers].flatten] res[:method] and res[:method].gsub!(/^HTTP_/, '') res[:strict] = true unless res.has_key?(:strict) } # write to a file File.open("spec/support/#{type}.json", 'w'){ |f| f.write Yajl.dump(results, :pretty => true) } end end http_parser.rb-0.6.0/tasks/spec.rake000066400000000000000000000001411225173000200173610ustar00rootroot00000000000000require "rspec/core/rake_task" RSpec::Core::RakeTask.new do |t| t.rspec_opts = %w(-fs -c) end http_parser.rb-0.6.0/tasks/submodules.rake000066400000000000000000000002661225173000200206210ustar00rootroot00000000000000desc "Fetch upstream submodules" task :submodules do if Dir['ext/ruby_http_parser/vendor/http-parser/*'].empty? sh 'git submodule init' sh 'git submodule update' end end