ruby_http_parser 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
@@ -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,7 @@
1
+ require 'mkmf'
2
+
3
+ http_parser_dir = File.expand_path(File.dirname(__FILE__) + '/../http-parser')
4
+ $CFLAGS << " -I#{http_parser_dir} "
5
+
6
+ dir_config("ruby_http_parser")
7
+ create_makefile("ruby_http_parser")
@@ -0,0 +1,275 @@
1
+ #include "ruby.h"
2
+ #include "ext_help.h"
3
+ #include "http_parser.c"
4
+
5
+ #define HEADER_PREFIX "HTTP_"
6
+ #define DEF_CONST(N, val) N = rb_obj_freeze(rb_str_new2(val)); rb_global_variable(&N)
7
+ #define GET_WRAPPER(N, from) ParserWrapper *N = (ParserWrapper *)(from)->data;
8
+
9
+ #define HASH_CAT(h, k, ptr, len) \
10
+ do { \
11
+ VALUE __v = rb_hash_aref(h, k); \
12
+ if (__v != Qnil) { \
13
+ rb_str_cat(__v, ptr, len); \
14
+ } else { \
15
+ rb_hash_aset(h, k, rb_str_new(ptr, len)); \
16
+ } \
17
+ } while(0)
18
+
19
+
20
+ // Stolen from Ebb
21
+ static char upcase[] =
22
+ "\0______________________________"
23
+ "_________________0123456789_____"
24
+ "__ABCDEFGHIJKLMNOPQRSTUVWXYZ____"
25
+ "__ABCDEFGHIJKLMNOPQRSTUVWXYZ____"
26
+ "________________________________"
27
+ "________________________________"
28
+ "________________________________"
29
+ "________________________________";
30
+
31
+ typedef struct ParserWrapper {
32
+ http_parser parser;
33
+ VALUE env;
34
+ VALUE on_message_complete;
35
+ VALUE on_headers_complete;
36
+ VALUE on_body;
37
+ VALUE last_field_name;
38
+ const char *last_field_name_at;
39
+ size_t last_field_name_length;
40
+ } ParserWrapper;
41
+
42
+ static VALUE eParserError;
43
+ static VALUE sCall;
44
+ static VALUE sPathInfo;
45
+ static VALUE sQueryString;
46
+ static VALUE sURL;
47
+ static VALUE sFragment;
48
+
49
+ /** Callbacks **/
50
+
51
+ int on_message_begin(http_parser *parser) {
52
+ GET_WRAPPER(wrapper, parser);
53
+ // Init env Rack hash
54
+ if (wrapper->env != Qnil)
55
+ rb_gc_unregister_address(&wrapper->env);
56
+ wrapper->env = rb_hash_new();
57
+ rb_gc_register_address(&wrapper->env);
58
+
59
+ return 0;
60
+ }
61
+
62
+ int on_path(http_parser *parser, const char *at, size_t length) {
63
+ GET_WRAPPER(wrapper, parser);
64
+ HASH_CAT(wrapper->env, sPathInfo, at, length);
65
+ return 0;
66
+ }
67
+
68
+ int on_query_string(http_parser *parser, const char *at, size_t length) {
69
+ GET_WRAPPER(wrapper, parser);
70
+ HASH_CAT(wrapper->env, sQueryString, at, length);
71
+ return 0;
72
+ }
73
+
74
+ int on_url(http_parser *parser, const char *at, size_t length) {
75
+ GET_WRAPPER(wrapper, parser);
76
+ HASH_CAT(wrapper->env, sURL, at, length);
77
+ return 0;
78
+ }
79
+
80
+ int on_fragment(http_parser *parser, const char *at, size_t length) {
81
+ GET_WRAPPER(wrapper, parser);
82
+ HASH_CAT(wrapper->env, sFragment, at, length);
83
+ return 0;
84
+ }
85
+
86
+ int on_header_field(http_parser *parser, const char *at, size_t length) {
87
+ GET_WRAPPER(wrapper, parser);
88
+
89
+ wrapper->last_field_name = Qnil;
90
+
91
+ if (wrapper->last_field_name_at == NULL) {
92
+ wrapper->last_field_name_at = at;
93
+ wrapper->last_field_name_length = length;
94
+ } else {
95
+ wrapper->last_field_name_length += length;
96
+ }
97
+
98
+ return 0;
99
+ }
100
+
101
+ int on_header_value(http_parser *parser, const char *at, size_t length) {
102
+ GET_WRAPPER(wrapper, parser);
103
+
104
+ VALUE name = Qnil;
105
+
106
+ if (wrapper->last_field_name == Qnil) {
107
+ wrapper->last_field_name = rb_str_new(HEADER_PREFIX, sizeof(HEADER_PREFIX) - 1 + wrapper->last_field_name_length);
108
+
109
+ // normalize header name
110
+ size_t name_length = wrapper->last_field_name_length;
111
+ const char *name_at = wrapper->last_field_name_at;
112
+ char *name_ptr = RSTRING_PTR(wrapper->last_field_name) + sizeof(HEADER_PREFIX) - 1;
113
+ int i;
114
+ for(i = 0; i < name_length; i++) {
115
+ char *ch = name_ptr + i;
116
+ *ch = upcase[(int)name_at[i]];
117
+ }
118
+
119
+ wrapper->last_field_name_at = NULL;
120
+ wrapper->last_field_name_length = 0;
121
+ }
122
+
123
+ HASH_CAT(wrapper->env, wrapper->last_field_name, at, length);
124
+
125
+ return 0;
126
+ }
127
+
128
+ int on_headers_complete(http_parser *parser) {
129
+ GET_WRAPPER(wrapper, parser);
130
+
131
+ if (wrapper->on_headers_complete != Qnil) {
132
+ rb_funcall(wrapper->on_headers_complete, sCall, 1, wrapper->env);
133
+ }
134
+
135
+ return 0;
136
+ }
137
+
138
+ int on_body(http_parser *parser, const char *at, size_t length) {
139
+ GET_WRAPPER(wrapper, parser);
140
+
141
+ if (wrapper->on_body != Qnil) {
142
+ rb_funcall(wrapper->on_body, sCall, 1, rb_str_new(at, length));
143
+ }
144
+
145
+ return 0;
146
+ }
147
+
148
+ int on_message_complete(http_parser *parser) {
149
+ GET_WRAPPER(wrapper, parser);
150
+
151
+ if (wrapper->on_message_complete != Qnil) {
152
+ rb_funcall(wrapper->on_message_complete, sCall, 1, wrapper->env);
153
+ }
154
+
155
+ return 0;
156
+ }
157
+
158
+
159
+ /** Usual ruby C stuff **/
160
+
161
+ void Parser_free(void *data) {
162
+ if(data) {
163
+ ParserWrapper *wrapper = (ParserWrapper *) data;
164
+ if (wrapper->env != Qnil)
165
+ rb_gc_unregister_address(&wrapper->env);
166
+ free(data);
167
+ }
168
+ }
169
+
170
+ VALUE Parser_alloc(VALUE klass) {
171
+ ParserWrapper *wrapper = ALLOC_N(ParserWrapper, 1);
172
+ http_parser_init(&wrapper->parser);
173
+
174
+ wrapper->env = Qnil;
175
+ wrapper->on_message_complete = Qnil;
176
+ wrapper->on_headers_complete = Qnil;
177
+ wrapper->on_body = Qnil;
178
+
179
+ wrapper->last_field_name = Qnil;
180
+ wrapper->last_field_name_at = NULL;
181
+ wrapper->last_field_name_length = 0;
182
+
183
+ // Init callbacks
184
+ wrapper->parser.on_message_begin = on_message_begin;
185
+ wrapper->parser.on_path = on_path;
186
+ wrapper->parser.on_query_string = on_query_string;
187
+ wrapper->parser.on_url = on_url;
188
+ wrapper->parser.on_fragment = on_fragment;
189
+ wrapper->parser.on_header_field = on_header_field;
190
+ wrapper->parser.on_header_value = on_header_value;
191
+ wrapper->parser.on_headers_complete = on_headers_complete;
192
+ wrapper->parser.on_body = on_body;
193
+ wrapper->parser.on_message_complete = on_message_complete;
194
+
195
+ wrapper->parser.data = wrapper;
196
+
197
+ return Data_Wrap_Struct(klass, NULL, Parser_free, wrapper);
198
+ }
199
+
200
+ VALUE Parser_parse_requests(VALUE self, VALUE data) {
201
+ ParserWrapper *wrapper = NULL;
202
+ char *ptr = RSTRING_PTR(data);
203
+ long len = RSTRING_LEN(data);
204
+
205
+ DATA_GET(self, ParserWrapper, wrapper);
206
+
207
+ size_t nparsed = http_parse_requests(&wrapper->parser, ptr, len);
208
+
209
+ if (nparsed != len) {
210
+ rb_raise(eParserError, "Invalid request");
211
+ }
212
+ }
213
+
214
+ VALUE Parser_parse_responses(VALUE self, VALUE data) {
215
+ ParserWrapper *wrapper = NULL;
216
+ char *ptr = RSTRING_PTR(data);
217
+ long len = RSTRING_LEN(data);
218
+
219
+ DATA_GET(self, ParserWrapper, wrapper);
220
+
221
+ size_t nparsed = http_parse_responses(&wrapper->parser, ptr, len);
222
+
223
+ if (nparsed != len) {
224
+ rb_raise(eParserError, "Invalid response");
225
+ }
226
+ }
227
+
228
+ VALUE Parser_set_on_headers_complete(VALUE self, VALUE callback) {
229
+ ParserWrapper *wrapper = NULL;
230
+ DATA_GET(self, ParserWrapper, wrapper);
231
+
232
+ wrapper->on_headers_complete = callback;
233
+ return callback;
234
+ }
235
+
236
+ VALUE Parser_set_on_message_complete(VALUE self, VALUE callback) {
237
+ ParserWrapper *wrapper = NULL;
238
+ DATA_GET(self, ParserWrapper, wrapper);
239
+
240
+ wrapper->on_message_complete = callback;
241
+ return callback;
242
+ }
243
+
244
+ VALUE Parser_set_on_body(VALUE self, VALUE callback) {
245
+ ParserWrapper *wrapper = NULL;
246
+ DATA_GET(self, ParserWrapper, wrapper);
247
+
248
+ wrapper->on_body = callback;
249
+ return callback;
250
+ }
251
+
252
+
253
+ void Init_ruby_http_parser() {
254
+ VALUE mHTTP = rb_define_module_under(rb_define_module("Net"), "HTTP");
255
+ VALUE cParser = rb_define_class_under(mHTTP, "Parser", rb_cObject);
256
+ VALUE cRequestParser = rb_define_class_under(mHTTP, "RequestParser", cParser);
257
+ VALUE cResponseParser = rb_define_class_under(mHTTP, "ResponseParser", cParser);
258
+
259
+ eParserError = rb_define_class_under(mHTTP, "ParseError", rb_eIOError);
260
+ sCall = rb_intern("call");
261
+
262
+ // String constants
263
+ DEF_CONST(sPathInfo, "PATH_INFO");
264
+ DEF_CONST(sQueryString, "QUERY_STRING");
265
+ DEF_CONST(sURL, "REQUEST_URI");
266
+ DEF_CONST(sFragment, "FRAGMENT");
267
+
268
+ rb_define_alloc_func(cParser, Parser_alloc);
269
+ rb_define_method(cParser, "on_message_complete=", Parser_set_on_message_complete, 1);
270
+ rb_define_method(cParser, "on_headers_complete=", Parser_set_on_headers_complete, 1);
271
+ rb_define_method(cParser, "on_body=", Parser_set_on_body, 1);
272
+
273
+ rb_define_method(cRequestParser, "<<", Parser_parse_requests, 1);
274
+ rb_define_method(cResponseParser, "<<", Parser_parse_responses, 1);
275
+ }
@@ -0,0 +1 @@
1
+ require File.dirname(__FILE__) + "/../../ruby_http_parser"
@@ -0,0 +1,13 @@
1
+ Gem::Specification.new do |s|
2
+ s.name = "ruby_http_parser"
3
+ s.version = "0.0.1"
4
+ s.summary = "Ruby bindings to http://github.com/ry/http-parser"
5
+
6
+ s.author = "Marc-Andre Cournoyer"
7
+ s.email = "macournoyer@gmail.com"
8
+ s.files = Dir["**/*"]
9
+ s.homepage = "http://github.com/macournoyer/ruby_http_parser"
10
+
11
+ s.require_paths = ["lib"]
12
+ s.extensions = ["ext/ruby_http_parser/extconf.rb"]
13
+ end
@@ -0,0 +1,27 @@
1
+ require File.dirname(__FILE__) + "/spec_helper"
2
+
3
+ describe Net::HTTP::RequestParser do
4
+ before do
5
+ @parser = Net::HTTP::RequestParser.new
6
+ end
7
+
8
+ it "should parse GET" do
9
+ env = nil
10
+ body = ""
11
+
12
+ @parser.on_headers_complete = proc { |e| env = e }
13
+ @parser.on_body = proc { |chunk| body << chunk }
14
+
15
+ @parser << "GET /test?ok=1 HTTP/1.1\r\n" +
16
+ "User-Agent: curl/7.18.0\r\n" +
17
+ "Host: 0.0.0.0:5000\r\n" +
18
+ "Accept: */*\r\n" +
19
+ "Content-Length: 5\r\n" +
20
+ "\r\n" +
21
+ "World"
22
+
23
+ env["PATH_INFO"].should == "/test"
24
+ env["HTTP_HOST"].should == "0.0.0.0:5000"
25
+ body.should == "World"
26
+ end
27
+ end
@@ -0,0 +1,3 @@
1
+ require "rubygems"
2
+ require "spec"
3
+ require File.dirname(__FILE__) + "/../lib/net/http/parser"
@@ -0,0 +1,157 @@
1
+
2
+ SHELL = /bin/sh
3
+
4
+ #### Start of system configuration section. ####
5
+
6
+ srcdir = /Users/ma/projects/ruby_http_parser/ext/ruby_http_parser
7
+ topdir = /System/Library/Frameworks/Ruby.framework/Versions/1.8/usr/lib/ruby/1.8/universal-darwin10.0
8
+ hdrdir = $(topdir)
9
+ VPATH = $(srcdir):$(topdir):$(hdrdir)
10
+ exec_prefix = $(prefix)
11
+ prefix = $(DESTDIR)/System/Library/Frameworks/Ruby.framework/Versions/1.8/usr
12
+ sharedstatedir = $(prefix)/com
13
+ mandir = $(DESTDIR)/usr/share/man
14
+ psdir = $(docdir)
15
+ oldincludedir = $(DESTDIR)/usr/include
16
+ localedir = $(datarootdir)/locale
17
+ bindir = $(exec_prefix)/bin
18
+ libexecdir = $(exec_prefix)/libexec
19
+ sitedir = $(DESTDIR)/Library/Ruby/Site
20
+ htmldir = $(docdir)
21
+ vendorarchdir = $(vendorlibdir)/$(sitearch)
22
+ includedir = $(prefix)/include
23
+ infodir = $(DESTDIR)/usr/share/info
24
+ vendorlibdir = $(vendordir)/$(ruby_version)
25
+ sysconfdir = $(prefix)/etc
26
+ libdir = $(exec_prefix)/lib
27
+ sbindir = $(exec_prefix)/sbin
28
+ rubylibdir = $(libdir)/ruby/$(ruby_version)
29
+ docdir = $(datarootdir)/doc/$(PACKAGE)
30
+ dvidir = $(docdir)
31
+ vendordir = $(libdir)/ruby/vendor_ruby
32
+ datarootdir = $(prefix)/share
33
+ pdfdir = $(docdir)
34
+ archdir = $(rubylibdir)/$(arch)
35
+ sitearchdir = $(sitelibdir)/$(sitearch)
36
+ datadir = $(datarootdir)
37
+ localstatedir = $(prefix)/var
38
+ sitelibdir = $(sitedir)/$(ruby_version)
39
+
40
+ CC = gcc
41
+ LIBRUBY = $(LIBRUBY_SO)
42
+ LIBRUBY_A = lib$(RUBY_SO_NAME)-static.a
43
+ LIBRUBYARG_SHARED = -l$(RUBY_SO_NAME)
44
+ LIBRUBYARG_STATIC = -l$(RUBY_SO_NAME)
45
+
46
+ RUBY_EXTCONF_H =
47
+ CFLAGS = -fno-common -arch i386 -arch x86_64 -g -Os -pipe -fno-common -DENABLE_DTRACE -fno-common -pipe -fno-common $(cflags) -I/Users/ma/projects/ruby_http_parser/ext/http-parser
48
+ INCFLAGS = -I. -I$(topdir) -I$(hdrdir) -I$(srcdir)
49
+ DEFS =
50
+ CPPFLAGS = -D_XOPEN_SOURCE -D_DARWIN_C_SOURCE $(DEFS) $(cppflags)
51
+ CXXFLAGS = $(CFLAGS)
52
+ ldflags = -L. -arch i386 -arch x86_64
53
+ dldflags =
54
+ archflag =
55
+ DLDFLAGS = $(ldflags) $(dldflags) $(archflag)
56
+ LDSHARED = cc -arch i386 -arch x86_64 -pipe -bundle -undefined dynamic_lookup
57
+ AR = ar
58
+ EXEEXT =
59
+
60
+ RUBY_INSTALL_NAME = ruby
61
+ RUBY_SO_NAME = ruby
62
+ arch = universal-darwin10.0
63
+ sitearch = universal-darwin10.0
64
+ ruby_version = 1.8
65
+ ruby = /System/Library/Frameworks/Ruby.framework/Versions/1.8/usr/bin/ruby
66
+ RUBY = $(ruby)
67
+ RM = rm -f
68
+ MAKEDIRS = mkdir -p
69
+ INSTALL = /usr/bin/install -c
70
+ INSTALL_PROG = $(INSTALL) -m 0755
71
+ INSTALL_DATA = $(INSTALL) -m 644
72
+ COPY = cp
73
+
74
+ #### End of system configuration section. ####
75
+
76
+ preload =
77
+
78
+ libpath = . $(libdir)
79
+ LIBPATH = -L. -L$(libdir)
80
+ DEFFILE =
81
+
82
+ CLEANFILES = mkmf.log
83
+ DISTCLEANFILES =
84
+
85
+ extout =
86
+ extout_prefix =
87
+ target_prefix =
88
+ LOCAL_LIBS =
89
+ LIBS = $(LIBRUBYARG_SHARED) -lpthread -ldl
90
+ SRCS = ruby_http_parser.c
91
+ OBJS = ruby_http_parser.o
92
+ TARGET = ruby_http_parser
93
+ DLLIB = $(TARGET).bundle
94
+ EXTSTATIC =
95
+ STATIC_LIB =
96
+
97
+ BINDIR = $(bindir)
98
+ RUBYCOMMONDIR = $(sitedir)$(target_prefix)
99
+ RUBYLIBDIR = $(sitelibdir)$(target_prefix)
100
+ RUBYARCHDIR = $(sitearchdir)$(target_prefix)
101
+
102
+ TARGET_SO = $(DLLIB)
103
+ CLEANLIBS = $(TARGET).bundle $(TARGET).il? $(TARGET).tds $(TARGET).map
104
+ CLEANOBJS = *.o *.a *.s[ol] *.pdb *.exp *.bak
105
+
106
+ all: $(DLLIB)
107
+ static: $(STATIC_LIB)
108
+
109
+ clean:
110
+ @-$(RM) $(CLEANLIBS) $(CLEANOBJS) $(CLEANFILES)
111
+
112
+ distclean: clean
113
+ @-$(RM) Makefile $(RUBY_EXTCONF_H) conftest.* mkmf.log
114
+ @-$(RM) core ruby$(EXEEXT) *~ $(DISTCLEANFILES)
115
+
116
+ realclean: distclean
117
+ install: install-so install-rb
118
+
119
+ install-so: $(RUBYARCHDIR)
120
+ install-so: $(RUBYARCHDIR)/$(DLLIB)
121
+ $(RUBYARCHDIR)/$(DLLIB): $(DLLIB)
122
+ $(INSTALL_PROG) $(DLLIB) $(RUBYARCHDIR)
123
+ install-rb: pre-install-rb install-rb-default
124
+ install-rb-default: pre-install-rb-default
125
+ pre-install-rb: Makefile
126
+ pre-install-rb-default: Makefile
127
+ $(RUBYARCHDIR):
128
+ $(MAKEDIRS) $@
129
+
130
+ site-install: site-install-so site-install-rb
131
+ site-install-so: install-so
132
+ site-install-rb: install-rb
133
+
134
+ .SUFFIXES: .c .m .cc .cxx .cpp .C .o
135
+
136
+ .cc.o:
137
+ $(CXX) $(INCFLAGS) $(CPPFLAGS) $(CXXFLAGS) -c $<
138
+
139
+ .cxx.o:
140
+ $(CXX) $(INCFLAGS) $(CPPFLAGS) $(CXXFLAGS) -c $<
141
+
142
+ .cpp.o:
143
+ $(CXX) $(INCFLAGS) $(CPPFLAGS) $(CXXFLAGS) -c $<
144
+
145
+ .C.o:
146
+ $(CXX) $(INCFLAGS) $(CPPFLAGS) $(CXXFLAGS) -c $<
147
+
148
+ .c.o:
149
+ $(CC) $(INCFLAGS) $(CPPFLAGS) $(CFLAGS) -c $<
150
+
151
+ $(DLLIB): $(OBJS)
152
+ @-$(RM) $@
153
+ $(LDSHARED) -o $@ $(OBJS) $(LIBPATH) $(DLDFLAGS) $(LOCAL_LIBS) $(LIBS)
154
+
155
+
156
+
157
+ $(OBJS): ruby.h defines.h