| 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 |
#include <ctype.h> |
|---|
| 11 |
#include "tst.h" |
|---|
| 12 |
|
|---|
| 13 |
static VALUE mMongrel; |
|---|
| 14 |
static VALUE cHttpParser; |
|---|
| 15 |
static VALUE cURIClassifier; |
|---|
| 16 |
static VALUE eHttpParserError; |
|---|
| 17 |
|
|---|
| 18 |
#define id_handler_map rb_intern("@handler_map") |
|---|
| 19 |
#define id_http_body rb_intern("@http_body") |
|---|
| 20 |
|
|---|
| 21 |
static VALUE global_http_prefix; |
|---|
| 22 |
static VALUE global_request_method; |
|---|
| 23 |
static VALUE global_request_uri; |
|---|
| 24 |
static VALUE global_query_string; |
|---|
| 25 |
static VALUE global_http_version; |
|---|
| 26 |
static VALUE global_content_length; |
|---|
| 27 |
static VALUE global_http_content_length; |
|---|
| 28 |
static VALUE global_request_path; |
|---|
| 29 |
static VALUE global_content_type; |
|---|
| 30 |
static VALUE global_http_content_type; |
|---|
| 31 |
static VALUE global_gateway_interface; |
|---|
| 32 |
static VALUE global_gateway_interface_value; |
|---|
| 33 |
static VALUE global_server_name; |
|---|
| 34 |
static VALUE global_server_port; |
|---|
| 35 |
static VALUE global_server_protocol; |
|---|
| 36 |
static VALUE global_server_protocol_value; |
|---|
| 37 |
static VALUE global_http_host; |
|---|
| 38 |
static VALUE global_mongrel_version; |
|---|
| 39 |
static VALUE global_server_software; |
|---|
| 40 |
static VALUE global_port_80; |
|---|
| 41 |
|
|---|
| 42 |
#define TRIE_INCREASE 30 |
|---|
| 43 |
|
|---|
| 44 |
/** Defines common length and error messages for input length validation. */ |
|---|
| 45 |
#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." |
|---|
| 46 |
|
|---|
| 47 |
/** Validates the max length of given input and throws an HttpParserError exception if over. */ |
|---|
| 48 |
#define VALIDATE_MAX_LENGTH(len, N) if(len > MAX_##N##_LENGTH) { rb_raise(eHttpParserError, MAX_##N##_LENGTH_ERR); } |
|---|
| 49 |
|
|---|
| 50 |
/** Defines global strings in the init method. */ |
|---|
| 51 |
#define DEF_GLOBAL(N, val) global_##N = rb_obj_freeze(rb_str_new2(val)); rb_global_variable(&global_##N) |
|---|
| 52 |
|
|---|
| 53 |
|
|---|
| 54 |
/* Defines the maximum allowed lengths for various input elements.*/ |
|---|
| 55 |
DEF_MAX_LENGTH(FIELD_NAME, 256); |
|---|
| 56 |
DEF_MAX_LENGTH(FIELD_VALUE, 80 * 1024); |
|---|
| 57 |
DEF_MAX_LENGTH(REQUEST_URI, 1024 * 12); |
|---|
| 58 |
DEF_MAX_LENGTH(REQUEST_PATH, 1024); |
|---|
| 59 |
DEF_MAX_LENGTH(QUERY_STRING, (1024 * 10)); |
|---|
| 60 |
DEF_MAX_LENGTH(HEADER, (1024 * (80 + 32))); |
|---|
| 61 |
|
|---|
| 62 |
|
|---|
| 63 |
void http_field(void *data, const char *field, size_t flen, const char *value, size_t vlen) |
|---|
| 64 |
{ |
|---|
| 65 |
char *ch, *end; |
|---|
| 66 |
VALUE req = (VALUE)data; |
|---|
| 67 |
VALUE v = Qnil; |
|---|
| 68 |
VALUE f = Qnil; |
|---|
| 69 |
|
|---|
| 70 |
VALIDATE_MAX_LENGTH(flen, FIELD_NAME); |
|---|
| 71 |
VALIDATE_MAX_LENGTH(vlen, FIELD_VALUE); |
|---|
| 72 |
|
|---|
| 73 |
v = rb_str_new(value, vlen); |
|---|
| 74 |
f = rb_str_dup(global_http_prefix); |
|---|
| 75 |
f = rb_str_buf_cat(f, field, flen); |
|---|
| 76 |
|
|---|
| 77 |
for(ch = RSTRING(f)->ptr, end = ch + RSTRING(f)->len; ch < end; ch++) { |
|---|
| 78 |
if(*ch == '-') { |
|---|
| 79 |
*ch = '_'; |
|---|
| 80 |
} else { |
|---|
| 81 |
*ch = toupper(*ch); |
|---|
| 82 |
} |
|---|
| 83 |
} |
|---|
| 84 |
|
|---|
| 85 |
rb_hash_aset(req, f, v); |
|---|
| 86 |
} |
|---|
| 87 |
|
|---|
| 88 |
void request_method(void *data, const char *at, size_t length) |
|---|
| 89 |
{ |
|---|
| 90 |
VALUE req = (VALUE)data; |
|---|
| 91 |
VALUE val = Qnil; |
|---|
| 92 |
|
|---|
| 93 |
val = rb_str_new(at, length); |
|---|
| 94 |
rb_hash_aset(req, global_request_method, val); |
|---|
| 95 |
} |
|---|
| 96 |
|
|---|
| 97 |
void request_uri(void *data, const char *at, size_t length) |
|---|
| 98 |
{ |
|---|
| 99 |
VALUE req = (VALUE)data; |
|---|
| 100 |
VALUE val = Qnil; |
|---|
| 101 |
|
|---|
| 102 |
VALIDATE_MAX_LENGTH(length, REQUEST_URI); |
|---|
| 103 |
|
|---|
| 104 |
val = rb_str_new(at, length); |
|---|
| 105 |
rb_hash_aset(req, global_request_uri, val); |
|---|
| 106 |
} |
|---|
| 107 |
|
|---|
| 108 |
void request_path(void *data, const char *at, size_t length) |
|---|
| 109 |
{ |
|---|
| 110 |
VALUE req = (VALUE)data; |
|---|
| 111 |
VALUE val = Qnil; |
|---|
| 112 |
|
|---|
| 113 |
VALIDATE_MAX_LENGTH(length, REQUEST_PATH); |
|---|
| 114 |
|
|---|
| 115 |
val = rb_str_new(at, length); |
|---|
| 116 |
rb_hash_aset(req, global_request_path, val); |
|---|
| 117 |
} |
|---|
| 118 |
|
|---|
| 119 |
void query_string(void *data, const char *at, size_t length) |
|---|
| 120 |
{ |
|---|
| 121 |
VALUE req = (VALUE)data; |
|---|
| 122 |
VALUE val = Qnil; |
|---|
| 123 |
|
|---|
| 124 |
VALIDATE_MAX_LENGTH(length, QUERY_STRING); |
|---|
| 125 |
|
|---|
| 126 |
val = rb_str_new(at, length); |
|---|
| 127 |
rb_hash_aset(req, global_query_string, val); |
|---|
| 128 |
} |
|---|
| 129 |
|
|---|
| 130 |
void http_version(void *data, const char *at, size_t length) |
|---|
| 131 |
{ |
|---|
| 132 |
VALUE req = (VALUE)data; |
|---|
| 133 |
VALUE val = rb_str_new(at, length); |
|---|
| 134 |
rb_hash_aset(req, global_http_version, val); |
|---|
| 135 |
} |
|---|
| 136 |
|
|---|
| 137 |
/** Finalizes the request header to have a bunch of stuff that's |
|---|
| 138 |
needed. */ |
|---|
| 139 |
|
|---|
| 140 |
void header_done(void *data, const char *at, size_t length) |
|---|
| 141 |
{ |
|---|
| 142 |
VALUE req = (VALUE)data; |
|---|
| 143 |
VALUE temp = Qnil; |
|---|
| 144 |
VALUE ctype = Qnil; |
|---|
| 145 |
VALUE clen = Qnil; |
|---|
| 146 |
char *colon = NULL; |
|---|
| 147 |
|
|---|
| 148 |
clen = rb_hash_aref(req, global_http_content_length); |
|---|
| 149 |
if(clen != Qnil) { |
|---|
| 150 |
rb_hash_aset(req, global_content_length, clen); |
|---|
| 151 |
} |
|---|
| 152 |
|
|---|
| 153 |
ctype = rb_hash_aref(req, global_http_content_type); |
|---|
| 154 |
if(ctype != Qnil) { |
|---|
| 155 |
rb_hash_aset(req, global_content_type, ctype); |
|---|
| 156 |
} |
|---|
| 157 |
|
|---|
| 158 |
rb_hash_aset(req, global_gateway_interface, global_gateway_interface_value); |
|---|
| 159 |
if((temp = rb_hash_aref(req, global_http_host)) != Qnil) { |
|---|
| 160 |
// ruby better close strings off with a '\0' dammit |
|---|
| 161 |
colon = strchr(RSTRING(temp)->ptr, ':'); |
|---|
| 162 |
if(colon != NULL) { |
|---|
| 163 |
rb_hash_aset(req, global_server_name, rb_str_substr(temp, 0, colon - RSTRING(temp)->ptr)); |
|---|
| 164 |
rb_hash_aset(req, global_server_port, |
|---|
| 165 |
rb_str_substr(temp, colon - RSTRING(temp)->ptr+1, |
|---|
| 166 |
RSTRING(temp)->len)); |
|---|
| 167 |
} else { |
|---|
| 168 |
rb_hash_aset(req, global_server_name, temp); |
|---|
| 169 |
rb_hash_aset(req, global_server_port, global_port_80); |
|---|
| 170 |
} |
|---|
| 171 |
} |
|---|
| 172 |
|
|---|
| 173 |
// grab the initial body and stuff it into an ivar |
|---|
| 174 |
rb_ivar_set(req, id_http_body, rb_str_new(at, length)); |
|---|
| 175 |
rb_hash_aset(req, global_server_protocol, global_server_protocol_value); |
|---|
| 176 |
rb_hash_aset(req, global_server_software, global_mongrel_version); |
|---|
| 177 |
} |
|---|
| 178 |
|
|---|
| 179 |
|
|---|
| 180 |
void HttpParser_free(void *data) { |
|---|
| 181 |
TRACE(); |
|---|
| 182 |
|
|---|
| 183 |
if(data) { |
|---|
| 184 |
free(data); |
|---|
| 185 |
} |
|---|
| 186 |
} |
|---|
| 187 |
|
|---|
| 188 |
|
|---|
| 189 |
VALUE HttpParser_alloc(VALUE klass) |
|---|
| 190 |
{ |
|---|
| 191 |
VALUE obj; |
|---|
| 192 |
http_parser *hp = ALLOC_N(http_parser, 1); |
|---|
| 193 |
TRACE(); |
|---|
| 194 |
hp->http_field = http_field; |
|---|
| 195 |
hp->request_method = request_method; |
|---|
| 196 |
hp->request_uri = request_uri; |
|---|
| 197 |
hp->request_path = request_path; |
|---|
| 198 |
hp->query_string = query_string; |
|---|
| 199 |
hp->http_version = http_version; |
|---|
| 200 |
hp->header_done = header_done; |
|---|
| 201 |
http_parser_init(hp); |
|---|
| 202 |
|
|---|
| 203 |
obj = Data_Wrap_Struct(klass, NULL, HttpParser_free, hp); |
|---|
| 204 |
|
|---|
| 205 |
return obj; |
|---|
| 206 |
} |
|---|
| 207 |
|
|---|
| 208 |
|
|---|
| 209 |
/** |
|---|
| 210 |
* call-seq: |
|---|
| 211 |
* parser.new -> parser |
|---|
| 212 |
* |
|---|
| 213 |
* Creates a new parser. |
|---|
| 214 |
*/ |
|---|
| 215 |
VALUE HttpParser_init(VALUE self) |
|---|
| 216 |
{ |
|---|
| 217 |
http_parser *http = NULL; |
|---|
| 218 |
DATA_GET(self, http_parser, http); |
|---|
| 219 |
http_parser_init(http); |
|---|
| 220 |
|
|---|
| 221 |
return self; |
|---|
| 222 |
} |
|---|
| 223 |
|
|---|
| 224 |
|
|---|
| 225 |
/** |
|---|
| 226 |
* call-seq: |
|---|
| 227 |
* parser.reset -> nil |
|---|
| 228 |
* |
|---|
| 229 |
* Resets the parser to it's initial state so that you can reuse it |
|---|
| 230 |
* rather than making new ones. |
|---|
| 231 |
*/ |
|---|
| 232 |
VALUE HttpParser_reset(VALUE self) |
|---|
| 233 |
{ |
|---|
| 234 |
http_parser *http = NULL; |
|---|
| 235 |
DATA_GET(self, http_parser, http); |
|---|
| 236 |
http_parser_init(http); |
|---|
| 237 |
|
|---|
| 238 |
return Qnil; |
|---|
| 239 |
} |
|---|
| 240 |
|
|---|
| 241 |
|
|---|
| 242 |
/** |
|---|
| 243 |
* call-seq: |
|---|
| 244 |
* parser.finish -> true/false |
|---|
| 245 |
* |
|---|
| 246 |
* Finishes a parser early which could put in a "good" or bad state. |
|---|
| 247 |
* You should call reset after finish it or bad things will happen. |
|---|
| 248 |
*/ |
|---|
| 249 |
VALUE HttpParser_finish(VALUE self) |
|---|
| 250 |
{ |
|---|
| 251 |
http_parser *http = NULL; |
|---|
| 252 |
DATA_GET(self, http_parser, http); |
|---|
| 253 |
http_parser_finish(http); |
|---|
| 254 |
|
|---|
| 255 |
return http_parser_is_finished(http) ? Qtrue : Qfalse; |
|---|
| 256 |
} |
|---|
| 257 |
|
|---|
| 258 |
|
|---|
| 259 |
/** |
|---|
| 260 |
* call-seq: |
|---|
| 261 |
* parser.execute(req_hash, data, start) -> Integer |
|---|
| 262 |
* |
|---|
| 263 |
* Takes a Hash and a String of data, parses the String of data filling in the Hash |
|---|
| 264 |
* returning an Integer to indicate how much of the data has been read. No matter |
|---|
| 265 |
* what the return value, you should call HttpParser#finished? and HttpParser#error? |
|---|
| 266 |
* to figure out if it's done parsing or there was an error. |
|---|
| 267 |
* |
|---|
| 268 |
* This function now throws an exception when there is a parsing error. This makes |
|---|
| 269 |
* the logic for working with the parser much easier. You can still test for an |
|---|
| 270 |
* error, but now you need to wrap the parser with an exception handling block. |
|---|
| 271 |
* |
|---|
| 272 |
* The third argument allows for parsing a partial request and then continuing |
|---|
| 273 |
* the parsing from that position. It needs all of the original data as well |
|---|
| 274 |
* so you have to append to the data buffer as you read. |
|---|
| 275 |
*/ |
|---|
| 276 |
VALUE HttpParser_execute(VALUE self, VALUE req_hash, VALUE data, VALUE start) |
|---|
| 277 |
{ |
|---|
| 278 |
http_parser *http = NULL; |
|---|
| 279 |
int from = 0; |
|---|
| 280 |
char *dptr = NULL; |
|---|
| 281 |
long dlen = 0; |
|---|
| 282 |
|
|---|
| 283 |
DATA_GET(self, http_parser, http); |
|---|
| 284 |
|
|---|
| 285 |
from = FIX2INT(start); |
|---|
| 286 |
dptr = RSTRING(data)->ptr; |
|---|
| 287 |
dlen = RSTRING(data)->len; |
|---|
| 288 |
|
|---|
| 289 |
if(from >= dlen) { |
|---|
| 290 |
rb_raise(eHttpParserError, "Requested start is after data buffer end."); |
|---|
| 291 |
} else { |
|---|
| 292 |
http->data = (void *)req_hash; |
|---|
| 293 |
http_parser_execute(http, dptr, dlen, from); |
|---|
| 294 |
|
|---|
| 295 |
VALIDATE_MAX_LENGTH(http_parser_nread(http), HEADER); |
|---|
| 296 |
|
|---|
| 297 |
if(http_parser_has_error(http)) { |
|---|
| 298 |
rb_raise(eHttpParserError, "Invalid HTTP format, parsing fails."); |
|---|
| 299 |
} else { |
|---|
| 300 |
return INT2FIX(http_parser_nread(http)); |
|---|
| 301 |
} |
|---|
| 302 |
} |
|---|
| 303 |
} |
|---|
| 304 |
|
|---|
| 305 |
|
|---|
| 306 |
|
|---|
| 307 |
/** |
|---|
| 308 |
* call-seq: |
|---|
| 309 |
* parser.error? -> true/false |
|---|
| 310 |
* |
|---|
| 311 |
* Tells you whether the parser is in an error state. |
|---|
| 312 |
*/ |
|---|
| 313 |
VALUE HttpParser_has_error(VALUE self) |
|---|
| 314 |
{ |
|---|
| 315 |
http_parser *http = NULL; |
|---|
| 316 |
DATA_GET(self, http_parser, http); |
|---|
| 317 |
|
|---|
| 318 |
return http_parser_has_error(http) ? Qtrue : Qfalse; |
|---|
| 319 |
} |
|---|
| 320 |
|
|---|
| 321 |
|
|---|
| 322 |
/** |
|---|
| 323 |
* call-seq: |
|---|
| 324 |
* parser.finished? -> true/false |
|---|
| 325 |
* |
|---|
| 326 |
* Tells you whether the parser is finished or not and in a good state. |
|---|
| 327 |
*/ |
|---|
| 328 |
VALUE HttpParser_is_finished(VALUE self) |
|---|
| 329 |
{ |
|---|
| 330 |
http_parser *http = NULL; |
|---|
| 331 |
DATA_GET(self, http_parser, http); |
|---|
| 332 |
|
|---|
| 333 |
return http_parser_is_finished(http) ? Qtrue : Qfalse; |
|---|
| 334 |
} |
|---|
| 335 |
|
|---|
| 336 |
|
|---|
| 337 |
/** |
|---|
| 338 |
* call-seq: |
|---|
| 339 |
* parser.nread -> Integer |
|---|
| 340 |
* |
|---|
| 341 |
* Returns the amount of data processed so far during this processing cycle. It is |
|---|
| 342 |
* set to 0 on initialize or reset calls and is incremented each time execute is called. |
|---|
| 343 |
*/ |
|---|
| 344 |
VALUE HttpParser_nread(VALUE self) |
|---|
| 345 |
{ |
|---|
| 346 |
http_parser *http = NULL; |
|---|
| 347 |
DATA_GET(self, http_parser, http); |
|---|
| 348 |
|
|---|
| 349 |
return INT2FIX(http->nread); |
|---|
| 350 |
} |
|---|
| 351 |
|
|---|
| 352 |
|
|---|
| 353 |
void URIClassifier_free(void *data) |
|---|
| 354 |
{ |
|---|
| 355 |
TRACE(); |
|---|
| 356 |
|
|---|
| 357 |
if(data) { |
|---|
| 358 |
tst_cleanup((struct tst *)data); |
|---|
| 359 |
} |
|---|
| 360 |
} |
|---|
| 361 |
|
|---|
| 362 |
|
|---|
| 363 |
|
|---|
| 364 |
VALUE URIClassifier_alloc(VALUE klass) |
|---|
| 365 |
{ |
|---|
| 366 |
VALUE obj; |
|---|
| 367 |
struct tst *tst = tst_init(TRIE_INCREASE); |
|---|
| 368 |
TRACE(); |
|---|
| 369 |
assert(tst && "failed to initialize trie structure"); |
|---|
| 370 |
|
|---|
| 371 |
obj = Data_Wrap_Struct(klass, NULL, URIClassifier_free, tst); |
|---|
| 372 |
|
|---|
| 373 |
return obj; |
|---|
| 374 |
} |
|---|
| 375 |
|
|---|
| 376 |
/** |
|---|
| 377 |
* call-seq: |
|---|
| 378 |
* URIClassifier.new -> URIClassifier |
|---|
| 379 |
* |
|---|
| 380 |
* Initializes a new URIClassifier object that you can use to associate URI sequences |
|---|
| 381 |
* with objects. You can actually use it with any string sequence and any objects, |
|---|
| 382 |
* but it's mostly used with URIs. |
|---|
| 383 |
* |
|---|
| 384 |
* It uses TST from http://www.octavian.org/cs/software.html to build an ternary search |
|---|
| 385 |
* trie to hold all of the URIs. It uses this to do an initial search for the a URI |
|---|
| 386 |
* prefix, and then to break the URI into SCRIPT_NAME and PATH_INFO portions. It actually |
|---|
| 387 |
* will do two searches most of the time in order to find the right handler for the |
|---|
| 388 |
* registered prefix portion. |
|---|
| 389 |
* |
|---|
| 390 |
*/ |
|---|
| 391 |
VALUE URIClassifier_init(VALUE self) |
|---|
| 392 |
{ |
|---|
| 393 |
VALUE hash; |
|---|
| 394 |
|
|---|
| 395 |
// we create an internal hash to protect stuff from the GC |
|---|
| 396 |
hash = rb_hash_new(); |
|---|
| 397 |
rb_ivar_set(self, id_handler_map, hash); |
|---|
| 398 |
|
|---|
| 399 |
return self; |
|---|
| 400 |
} |
|---|
| 401 |
|
|---|
| 402 |
|
|---|
| 403 |
/** |
|---|
| 404 |
* call-seq: |
|---|
| 405 |
* uc.register("/someuri", SampleHandler.new) -> nil |
|---|
| 406 |
* |
|---|
| 407 |
* Registers the SampleHandler (one for all requests) with the "/someuri". |
|---|
| 408 |
* When URIClassifier::resolve is called with "/someuri" it'll return |
|---|
| 409 |
* SampleHandler immediately. When called with "/someuri/iwant" it'll also |
|---|
| 410 |
* return SomeHandler immediatly, with no additional searches, but it will |
|---|
| 411 |
* return path info with "/iwant". |
|---|
| 412 |
* |
|---|
| 413 |
* You actually can reuse this class to register nearly anything and |
|---|
| 414 |
* quickly resolve it. This could be used for caching, fast mapping, etc. |
|---|
| 415 |
* The downside is it uses much more memory than a Hash, but it can be |
|---|
| 416 |
* a lot faster. It's main advantage is that it works on prefixes, which |
|---|
| 417 |
* is damn hard to get right with a Hash. |
|---|
| 418 |
*/ |
|---|
| 419 |
VALUE URIClassifier_register(VALUE self, VALUE uri, VALUE handler) |
|---|
| 420 |
{ |
|---|
| 421 |
int rc = 0; |
|---|
| 422 |
void *ptr = NULL; |
|---|
| 423 |
struct tst *tst = NULL; |
|---|
| 424 |
DATA_GET(self, struct tst, tst); |
|---|
| 425 |
|
|---|
| 426 |
rc = tst_insert((unsigned char *)StringValueCStr(uri), (void *)handler , tst, 0, &ptr); |
|---|
| 427 |
|
|---|
| 428 |
if(rc == TST_DUPLICATE_KEY) { |
|---|
| 429 |
rb_raise(rb_eStandardError, "Handler already registered with that name"); |
|---|
| 430 |
} else if(rc == TST_ERROR) { |
|---|
| 431 |
rb_raise(rb_eStandardError, "Memory error registering handler"); |
|---|
| 432 |
} else if(rc == TST_NULL_KEY) { |
|---|
| 433 |
rb_raise(rb_eStandardError, "URI was empty"); |
|---|
| 434 |
} |
|---|
| 435 |
|
|---|
| 436 |
rb_hash_aset(rb_ivar_get(self, id_handler_map), uri, handler); |
|---|
| 437 |
|
|---|
| 438 |
return Qnil; |
|---|
| 439 |
} |
|---|
| 440 |
|
|---|
| 441 |
|
|---|
| 442 |
/** |
|---|
| 443 |
* call-seq: |
|---|
| 444 |
* uc.unregister("/someuri") |
|---|
| 445 |
* |
|---|
| 446 |
* Yep, just removes this uri and it's handler from the trie. |
|---|
| 447 |
*/ |
|---|
| 448 |
VALUE URIClassifier_unregister(VALUE self, VALUE uri) |
|---|
| 449 |
{ |
|---|
| 450 |
void *handler = NULL; |
|---|
| 451 |
struct tst *tst = NULL; |
|---|
| 452 |
DATA_GET(self, struct tst, tst); |
|---|
| 453 |
|
|---|
| 454 |
handler = tst_delete((unsigned char *)StringValueCStr(uri), tst); |
|---|
| 455 |
|
|---|
| 456 |
if(handler) { |
|---|
| 457 |
rb_hash_delete(rb_ivar_get(self, id_handler_map), uri); |
|---|
| 458 |
|
|---|
| 459 |
return (VALUE)handler; |
|---|
| 460 |
} else { |
|---|
| 461 |
return Qnil; |
|---|
| 462 |
} |
|---|
| 463 |
} |
|---|
| 464 |
|
|---|
| 465 |
|
|---|
| 466 |
/** |
|---|
| 467 |
* call-seq: |
|---|
| 468 |
* uc.resolve("/someuri") -> "/someuri", "", handler |
|---|
| 469 |
* uc.resolve("/someuri/pathinfo") -> "/someuri", "/pathinfo", handler |
|---|
| 470 |
* uc.resolve("/notfound/orhere") -> nil, nil, nil |
|---|
| 471 |
* uc.resolve("/") -> "/", "/", handler # if uc.register("/", handler) |
|---|
| 472 |
* uc.resolve("/path/from/root") -> "/", "/path/from/root", handler # if uc.register("/", handler) |
|---|
| 473 |
* |
|---|
| 474 |
* Attempts to resolve either the whole URI or at the longest prefix, returning |
|---|
| 475 |
* the prefix (as script_info), path (as path_info), and registered handler |
|---|
| 476 |
* (usually an HttpHandler). If it doesn't find a handler registered at the longest |
|---|
| 477 |
* match then it returns nil,nil,nil. |
|---|
| 478 |
* |
|---|
| 479 |
* Because the resolver uses a trie you are able to register a handler at *any* character |
|---|
| 480 |
* in the URI and it will be handled as long as it's the longest prefix. So, if you |
|---|
| 481 |
* registered handler #1 at "/something/lik", and #2 at "/something/like/that", then a |
|---|
| 482 |
* a search for "/something/like" would give you #1. A search for "/something/like/that/too" |
|---|
| 483 |
* would give you #2. |
|---|
| 484 |
* |
|---|
| 485 |
* This is very powerful since it means you can also attach handlers to parts of the ; |
|---|
| 486 |
* (semi-colon) separated path params, any part of the path, use off chars, anything really. |
|---|
| 487 |
* It also means that it's very efficient to do this only taking as long as the URI has |
|---|
| 488 |
* characters. |
|---|
| 489 |
* |
|---|
| 490 |
* A slight modification to the CGI 1.2 standard is given for handlers registered to "/". |
|---|
| 491 |
* CGI expects all CGI scripts to be at some script path, so it doesn't really say anything |
|---|
| 492 |
* about a script that handles the root. To make this work, the resolver will detect that |
|---|
| 493 |
* the requested handler is at "/", and return that for script_name, and then simply return |
|---|
| 494 |
* the full URI back as path_info. |
|---|
| 495 |
* |
|---|
| 496 |
* It expects strings with no embedded '\0' characters. Don't try other string-like stuff yet. |
|---|
| 497 |
*/ |
|---|
| 498 |
VALUE URIClassifier_resolve(VALUE self, VALUE uri) |
|---|
| 499 |
{ |
|---|
| 500 |
void *handler = NULL; |
|---|
| 501 |
int pref_len = 0; |
|---|
| 502 |
struct tst *tst = NULL; |
|---|
| 503 |
VALUE result; |
|---|
| 504 |
unsigned char *uri_str = NULL; |
|---|
| 505 |
|
|---|
| 506 |
DATA_GET(self, struct tst, tst); |
|---|
| 507 |
uri_str = (unsigned char *)StringValueCStr(uri); |
|---|
| 508 |
|
|---|
| 509 |
handler = tst_search(uri_str, tst, &pref_len); |
|---|
| 510 |
|
|---|
| 511 |
// setup for multiple return values |
|---|
| 512 |
result = rb_ary_new(); |
|---|
| 513 |
|
|---|
| 514 |
if(handler) { |
|---|
| 515 |
rb_ary_push(result, rb_str_substr (uri, 0, pref_len)); |
|---|
| 516 |
// compensate for a script_name="/" where we need to add the "/" to path_info to keep it consistent |
|---|
| 517 |
if(pref_len == 1 && uri_str[0] == '/') { |
|---|
| 518 |
// matches the root URI so we have to use the whole URI as the path_info |
|---|
| 519 |
rb_ary_push(result, uri); |
|---|
| 520 |
} else { |
|---|
| 521 |
// matches a script so process like normal |
|---|
| 522 |
rb_ary_push(result, rb_str_substr(uri, pref_len, RSTRING(uri)->len)); |
|---|
| 523 |
} |
|---|
| 524 |
|
|---|
| 525 |
rb_ary_push(result, (VALUE)handler); |
|---|
| 526 |
} else { |
|---|
| 527 |
// not found so push back nothing |
|---|
| 528 |
rb_ary_push(result, Qnil); |
|---|
| 529 |
rb_ary_push(result, Qnil); |
|---|
| 530 |
rb_ary_push(result, Qnil); |
|---|
| 531 |
} |
|---|
| 532 |
|
|---|
| 533 |
return result; |
|---|
| 534 |
} |
|---|
| 535 |
|
|---|
| 536 |
|
|---|
| 537 |
void Init_http11() |
|---|
| 538 |
{ |
|---|
| 539 |
|
|---|
| 540 |
mMongrel = rb_define_module("Mongrel"); |
|---|
| 541 |
|
|---|
| 542 |
DEF_GLOBAL(http_prefix, "HTTP_"); |
|---|
| 543 |
DEF_GLOBAL(request_method, "REQUEST_METHOD"); |
|---|
| 544 |
DEF_GLOBAL(request_uri, "REQUEST_URI"); |
|---|
| 545 |
DEF_GLOBAL(query_string, "QUERY_STRING"); |
|---|
| 546 |
DEF_GLOBAL(http_version, "HTTP_VERSION"); |
|---|
| 547 |
DEF_GLOBAL(request_path, "REQUEST_PATH"); |
|---|
| 548 |
DEF_GLOBAL(content_length, "CONTENT_LENGTH"); |
|---|
| 549 |
DEF_GLOBAL(http_content_length, "HTTP_CONTENT_LENGTH"); |
|---|
| 550 |
DEF_GLOBAL(content_type, "CONTENT_TYPE"); |
|---|
| 551 |
DEF_GLOBAL(http_content_type, "HTTP_CONTENT_TYPE"); |
|---|
| 552 |
DEF_GLOBAL(gateway_interface, "GATEWAY_INTERFACE"); |
|---|
| 553 |
DEF_GLOBAL(gateway_interface_value, "CGI/1.2"); |
|---|
| 554 |
DEF_GLOBAL(server_name, "SERVER_NAME"); |
|---|
| 555 |
DEF_GLOBAL(server_port, "SERVER_PORT"); |
|---|
| 556 |
DEF_GLOBAL(server_protocol, "SERVER_PROTOCOL"); |
|---|
| 557 |
DEF_GLOBAL(server_protocol_value, "HTTP/1.1"); |
|---|
| 558 |
DEF_GLOBAL(http_host, "HTTP_HOST"); |
|---|
| 559 |
DEF_GLOBAL(mongrel_version, "Mongrel 1.0.1"); |
|---|
| 560 |
DEF_GLOBAL(server_software, "SERVER_SOFTWARE"); |
|---|
| 561 |
DEF_GLOBAL(port_80, "80"); |
|---|
| 562 |
|
|---|
| 563 |
eHttpParserError = rb_define_class_under(mMongrel, "HttpParserError", rb_eIOError); |
|---|
| 564 |
|
|---|
| 565 |
cHttpParser = rb_define_class_under(mMongrel, "HttpParser", rb_cObject); |
|---|
| 566 |
rb_define_alloc_func(cHttpParser, HttpParser_alloc); |
|---|
| 567 |
rb_define_method(cHttpParser, "initialize", HttpParser_init,0); |
|---|
| 568 |
rb_define_method(cHttpParser, "reset", HttpParser_reset,0); |
|---|
| 569 |
rb_define_method(cHttpParser, "finish", HttpParser_finish,0); |
|---|
| 570 |
rb_define_method(cHttpParser, "execute", HttpParser_execute,3); |
|---|
| 571 |
rb_define_method(cHttpParser, "error?", HttpParser_has_error,0); |
|---|
| 572 |
rb_define_method(cHttpParser, "finished?", HttpParser_is_finished,0); |
|---|
| 573 |
rb_define_method(cHttpParser, "nread", HttpParser_nread,0); |
|---|
| 574 |
|
|---|
| 575 |
cURIClassifier = rb_define_class_under(mMongrel, "URIClassifier", rb_cObject); |
|---|
| 576 |
rb_define_alloc_func(cURIClassifier, URIClassifier_alloc); |
|---|
| 577 |
rb_define_method(cURIClassifier, "initialize", URIClassifier_init, 0); |
|---|
| 578 |
rb_define_method(cURIClassifier, "register", URIClassifier_register, 2); |
|---|
| 579 |
rb_define_method(cURIClassifier, "unregister", URIClassifier_unregister, 1); |
|---|
| 580 |
rb_define_method(cURIClassifier, "resolve", URIClassifier_resolve, 1); |
|---|
| 581 |
} |
|---|
| 582 |
|
|---|
| 583 |
|
|---|