unicorn 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
data/SIGNALS ADDED
@@ -0,0 +1,34 @@
1
+ == Signal handling
2
+
3
+ In general, signals need only be sent to the master process. However,
4
+ the signals unicorn uses internally to communicate with the worker
5
+ processes are documented here as well.
6
+
7
+ === Master Process
8
+
9
+ * HUP - reload config file and gracefully restart all workers
10
+
11
+ * INT/TERM - quick shutdown, kills all workers immediately
12
+
13
+ * QUIT - graceful shutdown, waits for workers to finish their
14
+ current request before finishing.
15
+
16
+ * USR1 - reopen all logs owned by the master and all workers
17
+ See Unicorn::Util.reopen_logs for what is considered a log.
18
+
19
+ * USR2 - reexecute the running binary. A separate QUIT
20
+ should be sent to the original process once the child is verified to
21
+ be up and running.
22
+
23
+ === Worker Processes
24
+
25
+ Sending signals directly to the worker processes should not normally be
26
+ needed. If the master process is running, any exited worker will be
27
+ automatically respawned.
28
+
29
+ * INT/TERM - quick shutdown, immediately exit
30
+
31
+ * QUIT - gracefully exit after finishing the current request
32
+
33
+ * USR1 - reopen all logs owned by the worker process
34
+ See Unicorn::Util.reopen_logs for what is considered a log.
data/TODO ADDED
@@ -0,0 +1,5 @@
1
+ tests for timeouts
2
+ Performance
3
+ QA behaviour on 1.9
4
+ X Make sure Echoe doesn't activate itself in packaged gems
5
+ Optimize Rack's dispatcher http://github.com/chneukirchen/rack/blob/cf040ea68a6a60a11f484a2145d2e62c34e7487e/lib/rack/urlmap.rb
data/bin/unicorn ADDED
@@ -0,0 +1,189 @@
1
+ #!/home/ew/bin/ruby
2
+ $stdin.sync = $stdout.sync = $stderr.sync = true
3
+ require 'unicorn' # require this first to populate Unicorn::DEFAULT_START_CTX
4
+ require 'optparse'
5
+
6
+ env = "development"
7
+ daemonize = false
8
+ listeners = []
9
+ options = { :listeners => listeners }
10
+ host = Unicorn::Const::DEFAULT_HOST
11
+ port = Unicorn::Const::DEFAULT_PORT
12
+
13
+ opts = OptionParser.new("", 24, ' ') do |opts|
14
+ opts.banner = "Usage: #{File.basename($0)} " \
15
+ "[ruby options] [unicorn options] [rackup config file]"
16
+
17
+ opts.separator "Ruby options:"
18
+
19
+ lineno = 1
20
+ opts.on("-e", "--eval LINE", "evaluate a LINE of code") do |line|
21
+ eval line, TOPLEVEL_BINDING, "-e", lineno
22
+ lineno += 1
23
+ end
24
+
25
+ opts.on("-d", "--debug", "set debugging flags (set $DEBUG to true)") do
26
+ $DEBUG = true
27
+ end
28
+
29
+ opts.on("-w", "--warn", "turn warnings on for your script") do
30
+ $-w = true
31
+ end
32
+
33
+ opts.on("-I", "--include PATH",
34
+ "specify $LOAD_PATH (may be used more than once)") do |path|
35
+ $LOAD_PATH.unshift(*path.split(/:/))
36
+ end
37
+
38
+ opts.on("-r", "--require LIBRARY",
39
+ "require the library, before executing your script") do |library|
40
+ require library
41
+ end
42
+
43
+ opts.separator "Unicorn options:"
44
+
45
+ # some of these switches exist for rackup command-line compatibility,
46
+
47
+ opts.on("-o", "--host HOST",
48
+ "listen on HOST (default: #{Unicorn::Const::DEFAULT_HOST})") do |h|
49
+ host = h
50
+ end
51
+
52
+ opts.on("-p", "--port PORT",
53
+ "use PORT (default: #{Unicorn::Const::DEFAULT_PORT})") do |p|
54
+ port = p.to_i
55
+ end
56
+
57
+ opts.on("-E", "--env ENVIRONMENT",
58
+ "use ENVIRONMENT for defaults (default: development)") do |e|
59
+ env = e
60
+ end
61
+
62
+ opts.on("-D", "--daemonize", "run daemonized in the background") do |d|
63
+ daemonize = d ? true : false
64
+ end
65
+
66
+ opts.on("-P", "--pid FILE", "file to store PID (default: none)") do |f|
67
+ options[:pid] = File.expand_path(f)
68
+ end
69
+
70
+ opts.on("-s", "--server SERVER",
71
+ "this flag only exists for compatibility") do |s|
72
+ warn "-s/--server only exists for compatibility with rackup"
73
+ end
74
+
75
+ # Unicorn-specific stuff
76
+ opts.on("-l", "--listen {HOST:PORT|PATH}",
77
+ "listen on HOST:PORT or PATH",
78
+ "this may be specified multiple times",
79
+ "(default: #{Unicorn::Const::DEFAULT_LISTEN})") do |address|
80
+ listeners << address
81
+ end
82
+
83
+ opts.on("-c", "--config-file FILE", "Unicorn-specific config file") do |f|
84
+ options[:config_file] = File.expand_path(f)
85
+ end
86
+
87
+ # I'm avoiding Unicorn-specific config options on the command-line.
88
+ # IMNSHO, config options on the command-line are redundant given
89
+ # config files and make things unnecessarily complicated with multiple
90
+ # places to look for a config option.
91
+
92
+ opts.separator "Common options:"
93
+
94
+ opts.on_tail("-h", "--help", "Show this message") do
95
+ puts opts
96
+ exit
97
+ end
98
+
99
+ opts.on_tail("-v", "--version", "Show version") do
100
+ puts "unicorn v#{Unicorn::Const::UNICORN_VERSION}"
101
+ exit
102
+ end
103
+
104
+ opts.parse! ARGV
105
+ end
106
+
107
+ require 'pp' if $DEBUG
108
+
109
+ # require Rack as late as possible in case $LOAD_PATH is modified
110
+ # in config.ru or command-line
111
+ require 'rack'
112
+
113
+ config = ARGV[0] || "config.ru"
114
+ abort "configuration file #{config} not found" unless File.exist?(config)
115
+
116
+ inner_app = case config
117
+ when /\.ru$/
118
+ raw = File.open(config, "rb") { |fp| fp.sysread(fp.stat.size) }
119
+ # parse embedded command-line options in config.ru comments
120
+ if raw[/^#\\(.*)/]
121
+ opts.parse! $1.split(/\s+/)
122
+ end
123
+ lambda { || eval("Rack::Builder.new {(#{raw}\n)}.to_app", nil, config) }
124
+ else
125
+ lambda do ||
126
+ require config
127
+ Object.const_get(File.basename(config, '.rb').capitalize)
128
+ end
129
+ end
130
+
131
+ app = case env
132
+ when "development"
133
+ lambda do ||
134
+ Rack::Builder.new do
135
+ use Rack::CommonLogger, $stderr
136
+ use Rack::ShowExceptions
137
+ use Rack::Lint
138
+ run inner_app.call
139
+ end.to_app
140
+ end
141
+ when "deployment"
142
+ lambda do ||
143
+ Rack::Builder.new do
144
+ use Rack::CommonLogger, $stderr
145
+ run inner_app.call
146
+ end.to_app
147
+ end
148
+ else
149
+ inner_app
150
+ end
151
+
152
+ if listeners.empty?
153
+ listener = "#{host}:#{port}"
154
+ listeners << listener
155
+ end
156
+
157
+ if $DEBUG
158
+ pp({
159
+ :unicorn_options => options,
160
+ :app => app,
161
+ :inner_app => inner_app,
162
+ :daemonize => daemonize,
163
+ })
164
+ end
165
+
166
+ # only daemonize if we're not inheriting file descriptors from our parent
167
+ if daemonize
168
+
169
+ $stdin.reopen("/dev/null")
170
+ unless ENV['UNICORN_FD']
171
+ exit if fork
172
+ Process.setsid
173
+ exit if fork
174
+ end
175
+
176
+ # We don't do a lot of standard daemonization stuff:
177
+ # * $stderr/$stderr can/will be redirected separately
178
+ # * umask is whatever was set by the parent process at startup
179
+ # and can be set in config.ru and config_file, so making it
180
+ # 0000 and potentially exposing sensitive log data can be bad
181
+ # policy.
182
+ # * Don't bother to chdir here since Unicorn is designed to
183
+ # run inside APP_ROOT. Unicorn will also re-chdir() to
184
+ # the directory it was started in when being re-executed
185
+ # to pickup code changes if the original deployment directory
186
+ # is a symlink or otherwise got replaced.
187
+ end
188
+
189
+ Unicorn.run(app, options)
@@ -0,0 +1,15 @@
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
+ #define ARRAY_SIZE(x) (sizeof(x)/sizeof(x[0]))
8
+
9
+ #ifdef DEBUG
10
+ #define TRACE() fprintf(stderr, "> %s:%d:%s\n", __FILE__, __LINE__, __FUNCTION__)
11
+ #else
12
+ #define TRACE()
13
+ #endif
14
+
15
+ #endif
@@ -0,0 +1,5 @@
1
+ require 'mkmf'
2
+
3
+ dir_config("unicorn/http11")
4
+ have_library("c", "main")
5
+ create_makefile("unicorn/http11")
@@ -0,0 +1,526 @@
1
+ /**
2
+ * Copyright (c) 2005 Zed A. Shaw
3
+ * You can redistribute it and/or modify it under the same terms as Ruby.
4
+ */
5
+ #include "ruby.h"
6
+ #include "ext_help.h"
7
+ #include <assert.h>
8
+ #include <string.h>
9
+ #include "http11_parser.h"
10
+
11
+ #ifndef RSTRING_PTR
12
+ #define RSTRING_PTR(s) (RSTRING(s)->ptr)
13
+ #endif
14
+ #ifndef RSTRING_LEN
15
+ #define RSTRING_LEN(s) (RSTRING(s)->len)
16
+ #endif
17
+
18
+ static VALUE mUnicorn;
19
+ static VALUE cHttpParser;
20
+ static VALUE eHttpParserError;
21
+
22
+ #define HTTP_PREFIX "HTTP_"
23
+ #define HTTP_PREFIX_LEN (sizeof(HTTP_PREFIX) - 1)
24
+
25
+ static VALUE global_request_method;
26
+ static VALUE global_request_uri;
27
+ static VALUE global_fragment;
28
+ static VALUE global_query_string;
29
+ static VALUE global_http_version;
30
+ static VALUE global_content_length;
31
+ static VALUE global_http_content_length;
32
+ static VALUE global_request_path;
33
+ static VALUE global_content_type;
34
+ static VALUE global_http_content_type;
35
+ static VALUE global_http_body;
36
+ static VALUE global_gateway_interface;
37
+ static VALUE global_gateway_interface_value;
38
+ static VALUE global_server_name;
39
+ static VALUE global_server_port;
40
+ static VALUE global_server_protocol;
41
+ static VALUE global_server_protocol_value;
42
+ static VALUE global_http_host;
43
+ static VALUE global_port_80;
44
+ static VALUE global_localhost;
45
+
46
+ /** Defines common length and error messages for input length validation. */
47
+ #define DEF_MAX_LENGTH(N,length) const size_t MAX_##N##_LENGTH = length; const char *MAX_##N##_LENGTH_ERR = "HTTP element " # N " is longer than the " # length " allowed length."
48
+
49
+ /** Validates the max length of given input and throws an HttpParserError exception if over. */
50
+ #define VALIDATE_MAX_LENGTH(len, N) if(len > MAX_##N##_LENGTH) { rb_raise(eHttpParserError, MAX_##N##_LENGTH_ERR); }
51
+
52
+ /** Defines global strings in the init method. */
53
+ #define DEF_GLOBAL(N, val) global_##N = rb_obj_freeze(rb_str_new2(val)); rb_global_variable(&global_##N)
54
+
55
+
56
+ /* Defines the maximum allowed lengths for various input elements.*/
57
+ DEF_MAX_LENGTH(FIELD_NAME, 256);
58
+ DEF_MAX_LENGTH(FIELD_VALUE, 80 * 1024);
59
+ DEF_MAX_LENGTH(REQUEST_URI, 1024 * 12);
60
+ DEF_MAX_LENGTH(FRAGMENT, 1024); /* Don't know if this length is specified somewhere or not */
61
+ DEF_MAX_LENGTH(REQUEST_PATH, 1024);
62
+ DEF_MAX_LENGTH(QUERY_STRING, (1024 * 10));
63
+ DEF_MAX_LENGTH(HEADER, (1024 * (80 + 32)));
64
+
65
+ struct common_field {
66
+ const signed long len;
67
+ const char *name;
68
+ VALUE value;
69
+ };
70
+
71
+ /*
72
+ * A list of common HTTP headers we expect to receive.
73
+ * This allows us to avoid repeatedly creating identical string
74
+ * objects to be used with rb_hash_aset().
75
+ */
76
+ static struct common_field common_http_fields[] = {
77
+ # define f(N) { (sizeof(N) - 1), N, Qnil }
78
+ f("ACCEPT"),
79
+ f("ACCEPT_CHARSET"),
80
+ f("ACCEPT_ENCODING"),
81
+ f("ACCEPT_LANGUAGE"),
82
+ f("ALLOW"),
83
+ f("AUTHORIZATION"),
84
+ f("CACHE_CONTROL"),
85
+ f("CONNECTION"),
86
+ f("CONTENT_ENCODING"),
87
+ f("CONTENT_LENGTH"),
88
+ f("CONTENT_TYPE"),
89
+ f("COOKIE"),
90
+ f("DATE"),
91
+ f("EXPECT"),
92
+ f("FROM"),
93
+ f("HOST"),
94
+ f("IF_MATCH"),
95
+ f("IF_MODIFIED_SINCE"),
96
+ f("IF_NONE_MATCH"),
97
+ f("IF_RANGE"),
98
+ f("IF_UNMODIFIED_SINCE"),
99
+ f("KEEP_ALIVE"), /* Firefox sends this */
100
+ f("MAX_FORWARDS"),
101
+ f("PRAGMA"),
102
+ f("PROXY_AUTHORIZATION"),
103
+ f("RANGE"),
104
+ f("REFERER"),
105
+ f("TE"),
106
+ f("TRAILER"),
107
+ f("TRANSFER_ENCODING"),
108
+ f("UPGRADE"),
109
+ f("USER_AGENT"),
110
+ f("VIA"),
111
+ f("X_FORWARDED_FOR"), /* common for proxies */
112
+ f("X_REAL_IP"), /* common for proxies */
113
+ f("WARNING")
114
+ # undef f
115
+ };
116
+
117
+ /*
118
+ * qsort(3) and bsearch(3) improve average performance slightly, but may
119
+ * not be worth it for lack of portability to certain platforms...
120
+ */
121
+ #if defined(HAVE_QSORT_BSEARCH)
122
+ /* sort by length, then by name if there's a tie */
123
+ static int common_field_cmp(const void *a, const void *b)
124
+ {
125
+ struct common_field *cfa = (struct common_field *)a;
126
+ struct common_field *cfb = (struct common_field *)b;
127
+ signed long diff = cfa->len - cfb->len;
128
+ return diff ? diff : memcmp(cfa->name, cfb->name, cfa->len);
129
+ }
130
+ #endif /* HAVE_QSORT_BSEARCH */
131
+
132
+ static void init_common_fields(void)
133
+ {
134
+ int i;
135
+ struct common_field *cf = common_http_fields;
136
+ char tmp[256]; /* MAX_FIELD_NAME_LENGTH */
137
+ memcpy(tmp, HTTP_PREFIX, HTTP_PREFIX_LEN);
138
+
139
+ for(i = 0; i < ARRAY_SIZE(common_http_fields); cf++, i++) {
140
+ memcpy(tmp + HTTP_PREFIX_LEN, cf->name, cf->len + 1);
141
+ cf->value = rb_obj_freeze(rb_str_new(tmp, HTTP_PREFIX_LEN + cf->len));
142
+ rb_global_variable(&cf->value);
143
+ }
144
+
145
+ #if defined(HAVE_QSORT_BSEARCH)
146
+ qsort(common_http_fields,
147
+ ARRAY_SIZE(common_http_fields),
148
+ sizeof(struct common_field),
149
+ common_field_cmp);
150
+ #endif /* HAVE_QSORT_BSEARCH */
151
+ }
152
+
153
+ static VALUE find_common_field_value(const char *field, size_t flen)
154
+ {
155
+ #if defined(HAVE_QSORT_BSEARCH)
156
+ struct common_field key;
157
+ struct common_field *found;
158
+ key.name = field;
159
+ key.len = (signed long)flen;
160
+ found = (struct common_field *)bsearch(&key, common_http_fields,
161
+ ARRAY_SIZE(common_http_fields),
162
+ sizeof(struct common_field),
163
+ common_field_cmp);
164
+ return found ? found->value : Qnil;
165
+ #else /* !HAVE_QSORT_BSEARCH */
166
+ int i;
167
+ struct common_field *cf = common_http_fields;
168
+ for(i = 0; i < ARRAY_SIZE(common_http_fields); i++, cf++) {
169
+ if (cf->len == flen && !memcmp(cf->name, field, flen))
170
+ return cf->value;
171
+ }
172
+ return Qnil;
173
+ #endif /* !HAVE_QSORT_BSEARCH */
174
+ }
175
+
176
+ static void http_field(void *data, const char *field,
177
+ size_t flen, const char *value, size_t vlen)
178
+ {
179
+ VALUE req = (VALUE)data;
180
+ VALUE v = Qnil;
181
+ VALUE f = Qnil;
182
+
183
+ VALIDATE_MAX_LENGTH(flen, FIELD_NAME);
184
+ VALIDATE_MAX_LENGTH(vlen, FIELD_VALUE);
185
+
186
+ v = rb_str_new(value, vlen);
187
+
188
+ f = find_common_field_value(field, flen);
189
+
190
+ if (f == Qnil) {
191
+ /*
192
+ * We got a strange header that we don't have a memoized value for.
193
+ * Fallback to creating a new string to use as a hash key.
194
+ *
195
+ * using rb_str_new(NULL, len) here is faster than rb_str_buf_new(len)
196
+ * in my testing, because: there's no minimum allocation length (and
197
+ * no check for it, either), RSTRING_LEN(f) does not need to be
198
+ * written twice, and and RSTRING_PTR(f) will already be
199
+ * null-terminated for us.
200
+ */
201
+ f = rb_str_new(NULL, HTTP_PREFIX_LEN + flen);
202
+ memcpy(RSTRING_PTR(f), HTTP_PREFIX, HTTP_PREFIX_LEN);
203
+ memcpy(RSTRING_PTR(f) + HTTP_PREFIX_LEN, field, flen);
204
+ assert(*(RSTRING_PTR(f) + RSTRING_LEN(f)) == '\0'); /* paranoia */
205
+ /* fprintf(stderr, "UNKNOWN HEADER <%s>\n", RSTRING_PTR(f)); */
206
+ }
207
+
208
+ rb_hash_aset(req, f, v);
209
+ }
210
+
211
+ static void request_method(void *data, const char *at, size_t length)
212
+ {
213
+ VALUE req = (VALUE)data;
214
+ VALUE val = Qnil;
215
+
216
+ val = rb_str_new(at, length);
217
+ rb_hash_aset(req, global_request_method, val);
218
+ }
219
+
220
+ static void request_uri(void *data, const char *at, size_t length)
221
+ {
222
+ VALUE req = (VALUE)data;
223
+ VALUE val = Qnil;
224
+
225
+ VALIDATE_MAX_LENGTH(length, REQUEST_URI);
226
+
227
+ val = rb_str_new(at, length);
228
+ rb_hash_aset(req, global_request_uri, val);
229
+ }
230
+
231
+ static void fragment(void *data, const char *at, size_t length)
232
+ {
233
+ VALUE req = (VALUE)data;
234
+ VALUE val = Qnil;
235
+
236
+ VALIDATE_MAX_LENGTH(length, FRAGMENT);
237
+
238
+ val = rb_str_new(at, length);
239
+ rb_hash_aset(req, global_fragment, val);
240
+ }
241
+
242
+ static void request_path(void *data, const char *at, size_t length)
243
+ {
244
+ VALUE req = (VALUE)data;
245
+ VALUE val = Qnil;
246
+
247
+ VALIDATE_MAX_LENGTH(length, REQUEST_PATH);
248
+
249
+ val = rb_str_new(at, length);
250
+ rb_hash_aset(req, global_request_path, val);
251
+ }
252
+
253
+ static void query_string(void *data, const char *at, size_t length)
254
+ {
255
+ VALUE req = (VALUE)data;
256
+ VALUE val = Qnil;
257
+
258
+ VALIDATE_MAX_LENGTH(length, QUERY_STRING);
259
+
260
+ val = rb_str_new(at, length);
261
+ rb_hash_aset(req, global_query_string, val);
262
+ }
263
+
264
+ static void http_version(void *data, const char *at, size_t length)
265
+ {
266
+ VALUE req = (VALUE)data;
267
+ VALUE val = rb_str_new(at, length);
268
+ rb_hash_aset(req, global_http_version, val);
269
+ }
270
+
271
+ /** Finalizes the request header to have a bunch of stuff that's
272
+ needed. */
273
+
274
+ static void header_done(void *data, const char *at, size_t length)
275
+ {
276
+ VALUE req = (VALUE)data;
277
+ VALUE temp = Qnil;
278
+ VALUE ctype = Qnil;
279
+ VALUE clen = Qnil;
280
+ char *colon = NULL;
281
+
282
+ clen = rb_hash_aref(req, global_http_content_length);
283
+ if(clen != Qnil) {
284
+ rb_hash_aset(req, global_content_length, clen);
285
+ }
286
+
287
+ ctype = rb_hash_aref(req, global_http_content_type);
288
+ if(ctype != Qnil) {
289
+ rb_hash_aset(req, global_content_type, ctype);
290
+ }
291
+
292
+ rb_hash_aset(req, global_gateway_interface, global_gateway_interface_value);
293
+ if((temp = rb_hash_aref(req, global_http_host)) != Qnil) {
294
+ colon = memchr(RSTRING_PTR(temp), ':', RSTRING_LEN(temp));
295
+ if(colon != NULL) {
296
+ rb_hash_aset(req, global_server_name, rb_str_substr(temp, 0, colon - RSTRING_PTR(temp)));
297
+ rb_hash_aset(req, global_server_port,
298
+ rb_str_substr(temp, colon - RSTRING_PTR(temp)+1,
299
+ RSTRING_LEN(temp)));
300
+ } else {
301
+ rb_hash_aset(req, global_server_name, temp);
302
+ rb_hash_aset(req, global_server_port, global_port_80);
303
+ }
304
+ } else {
305
+ rb_hash_aset(req, global_server_name, global_localhost);
306
+ rb_hash_aset(req, global_server_port, global_port_80);
307
+ }
308
+
309
+ /* grab the initial body and stuff it into the hash */
310
+ rb_hash_aset(req, global_http_body, rb_str_new(at, length));
311
+ rb_hash_aset(req, global_server_protocol, global_server_protocol_value);
312
+ }
313
+
314
+ static void HttpParser_free(void *data) {
315
+ TRACE();
316
+
317
+ if(data) {
318
+ free(data);
319
+ }
320
+ }
321
+
322
+
323
+ static VALUE HttpParser_alloc(VALUE klass)
324
+ {
325
+ VALUE obj;
326
+ http_parser *hp = ALLOC_N(http_parser, 1);
327
+ TRACE();
328
+ hp->http_field = http_field;
329
+ hp->request_method = request_method;
330
+ hp->request_uri = request_uri;
331
+ hp->fragment = fragment;
332
+ hp->request_path = request_path;
333
+ hp->query_string = query_string;
334
+ hp->http_version = http_version;
335
+ hp->header_done = header_done;
336
+ http_parser_init(hp);
337
+
338
+ obj = Data_Wrap_Struct(klass, NULL, HttpParser_free, hp);
339
+
340
+ return obj;
341
+ }
342
+
343
+
344
+ /**
345
+ * call-seq:
346
+ * parser.new -> parser
347
+ *
348
+ * Creates a new parser.
349
+ */
350
+ static VALUE HttpParser_init(VALUE self)
351
+ {
352
+ http_parser *http = NULL;
353
+ DATA_GET(self, http_parser, http);
354
+ http_parser_init(http);
355
+
356
+ return self;
357
+ }
358
+
359
+
360
+ /**
361
+ * call-seq:
362
+ * parser.reset -> nil
363
+ *
364
+ * Resets the parser to it's initial state so that you can reuse it
365
+ * rather than making new ones.
366
+ */
367
+ static VALUE HttpParser_reset(VALUE self)
368
+ {
369
+ http_parser *http = NULL;
370
+ DATA_GET(self, http_parser, http);
371
+ http_parser_init(http);
372
+
373
+ return Qnil;
374
+ }
375
+
376
+
377
+ /**
378
+ * call-seq:
379
+ * parser.finish -> true/false
380
+ *
381
+ * Finishes a parser early which could put in a "good" or bad state.
382
+ * You should call reset after finish it or bad things will happen.
383
+ */
384
+ static VALUE HttpParser_finish(VALUE self)
385
+ {
386
+ http_parser *http = NULL;
387
+ DATA_GET(self, http_parser, http);
388
+ http_parser_finish(http);
389
+
390
+ return http_parser_is_finished(http) ? Qtrue : Qfalse;
391
+ }
392
+
393
+
394
+ /**
395
+ * call-seq:
396
+ * parser.execute(req_hash, data, start) -> Integer
397
+ *
398
+ * Takes a Hash and a String of data, parses the String of data filling in the Hash
399
+ * returning an Integer to indicate how much of the data has been read. No matter
400
+ * what the return value, you should call HttpParser#finished? and HttpParser#error?
401
+ * to figure out if it's done parsing or there was an error.
402
+ *
403
+ * This function now throws an exception when there is a parsing error. This makes
404
+ * the logic for working with the parser much easier. You can still test for an
405
+ * error, but now you need to wrap the parser with an exception handling block.
406
+ *
407
+ * The third argument allows for parsing a partial request and then continuing
408
+ * the parsing from that position. It needs all of the original data as well
409
+ * so you have to append to the data buffer as you read.
410
+ */
411
+ static VALUE HttpParser_execute(VALUE self, VALUE req_hash,
412
+ VALUE data, VALUE start)
413
+ {
414
+ http_parser *http = NULL;
415
+ int from = 0;
416
+ char *dptr = NULL;
417
+ long dlen = 0;
418
+
419
+ DATA_GET(self, http_parser, http);
420
+
421
+ from = FIX2INT(start);
422
+ dptr = RSTRING_PTR(data);
423
+ dlen = RSTRING_LEN(data);
424
+
425
+ if(from >= dlen) {
426
+ rb_raise(eHttpParserError, "Requested start is after data buffer end.");
427
+ } else {
428
+ http->data = (void *)req_hash;
429
+ http_parser_execute(http, dptr, dlen, from);
430
+
431
+ VALIDATE_MAX_LENGTH(http_parser_nread(http), HEADER);
432
+
433
+ if(http_parser_has_error(http)) {
434
+ rb_raise(eHttpParserError, "Invalid HTTP format, parsing fails.");
435
+ } else {
436
+ return INT2FIX(http_parser_nread(http));
437
+ }
438
+ }
439
+ }
440
+
441
+
442
+
443
+ /**
444
+ * call-seq:
445
+ * parser.error? -> true/false
446
+ *
447
+ * Tells you whether the parser is in an error state.
448
+ */
449
+ static VALUE HttpParser_has_error(VALUE self)
450
+ {
451
+ http_parser *http = NULL;
452
+ DATA_GET(self, http_parser, http);
453
+
454
+ return http_parser_has_error(http) ? Qtrue : Qfalse;
455
+ }
456
+
457
+
458
+ /**
459
+ * call-seq:
460
+ * parser.finished? -> true/false
461
+ *
462
+ * Tells you whether the parser is finished or not and in a good state.
463
+ */
464
+ static VALUE HttpParser_is_finished(VALUE self)
465
+ {
466
+ http_parser *http = NULL;
467
+ DATA_GET(self, http_parser, http);
468
+
469
+ return http_parser_is_finished(http) ? Qtrue : Qfalse;
470
+ }
471
+
472
+
473
+ /**
474
+ * call-seq:
475
+ * parser.nread -> Integer
476
+ *
477
+ * Returns the amount of data processed so far during this processing cycle. It is
478
+ * set to 0 on initialize or reset calls and is incremented each time execute is called.
479
+ */
480
+ static VALUE HttpParser_nread(VALUE self)
481
+ {
482
+ http_parser *http = NULL;
483
+ DATA_GET(self, http_parser, http);
484
+
485
+ return INT2FIX(http->nread);
486
+ }
487
+
488
+ void Init_http11()
489
+ {
490
+
491
+ mUnicorn = rb_define_module("Unicorn");
492
+
493
+ DEF_GLOBAL(request_method, "REQUEST_METHOD");
494
+ DEF_GLOBAL(request_uri, "REQUEST_URI");
495
+ DEF_GLOBAL(fragment, "FRAGMENT");
496
+ DEF_GLOBAL(query_string, "QUERY_STRING");
497
+ DEF_GLOBAL(http_version, "HTTP_VERSION");
498
+ DEF_GLOBAL(request_path, "REQUEST_PATH");
499
+ DEF_GLOBAL(content_length, "CONTENT_LENGTH");
500
+ DEF_GLOBAL(http_content_length, "HTTP_CONTENT_LENGTH");
501
+ DEF_GLOBAL(http_body, "HTTP_BODY");
502
+ DEF_GLOBAL(content_type, "CONTENT_TYPE");
503
+ DEF_GLOBAL(http_content_type, "HTTP_CONTENT_TYPE");
504
+ DEF_GLOBAL(gateway_interface, "GATEWAY_INTERFACE");
505
+ DEF_GLOBAL(gateway_interface_value, "CGI/1.2");
506
+ DEF_GLOBAL(server_name, "SERVER_NAME");
507
+ DEF_GLOBAL(server_port, "SERVER_PORT");
508
+ DEF_GLOBAL(server_protocol, "SERVER_PROTOCOL");
509
+ DEF_GLOBAL(server_protocol_value, "HTTP/1.1");
510
+ DEF_GLOBAL(http_host, "HTTP_HOST");
511
+ DEF_GLOBAL(port_80, "80");
512
+ DEF_GLOBAL(localhost, "localhost");
513
+
514
+ eHttpParserError = rb_define_class_under(mUnicorn, "HttpParserError", rb_eIOError);
515
+
516
+ cHttpParser = rb_define_class_under(mUnicorn, "HttpParser", rb_cObject);
517
+ rb_define_alloc_func(cHttpParser, HttpParser_alloc);
518
+ rb_define_method(cHttpParser, "initialize", HttpParser_init,0);
519
+ rb_define_method(cHttpParser, "reset", HttpParser_reset,0);
520
+ rb_define_method(cHttpParser, "finish", HttpParser_finish,0);
521
+ rb_define_method(cHttpParser, "execute", HttpParser_execute,3);
522
+ rb_define_method(cHttpParser, "error?", HttpParser_has_error,0);
523
+ rb_define_method(cHttpParser, "finished?", HttpParser_is_finished,0);
524
+ rb_define_method(cHttpParser, "nread", HttpParser_nread,0);
525
+ init_common_fields();
526
+ }