mongrel 0.2.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (74) hide show
  1. data/COPYING +504 -0
  2. data/LICENSE +504 -0
  3. data/README +117 -0
  4. data/Rakefile +30 -0
  5. data/doc/rdoc/classes/Mongrel.html +144 -0
  6. data/doc/rdoc/classes/Mongrel/Error404Handler.html +171 -0
  7. data/doc/rdoc/classes/Mongrel/Error404Handler.src/M000023.html +18 -0
  8. data/doc/rdoc/classes/Mongrel/Error404Handler.src/M000024.html +18 -0
  9. data/doc/rdoc/classes/Mongrel/HeaderOut.html +167 -0
  10. data/doc/rdoc/classes/Mongrel/HeaderOut.src/M000013.html +18 -0
  11. data/doc/rdoc/classes/Mongrel/HeaderOut.src/M000014.html +21 -0
  12. data/doc/rdoc/classes/Mongrel/HttpHandler.html +159 -0
  13. data/doc/rdoc/classes/Mongrel/HttpHandler.src/M000019.html +17 -0
  14. data/doc/rdoc/classes/Mongrel/HttpParser.html +271 -0
  15. data/doc/rdoc/classes/Mongrel/HttpParser.src/M000001.html +28 -0
  16. data/doc/rdoc/classes/Mongrel/HttpParser.src/M000002.html +29 -0
  17. data/doc/rdoc/classes/Mongrel/HttpParser.src/M000003.html +29 -0
  18. data/doc/rdoc/classes/Mongrel/HttpParser.src/M000004.html +41 -0
  19. data/doc/rdoc/classes/Mongrel/HttpParser.src/M000005.html +27 -0
  20. data/doc/rdoc/classes/Mongrel/HttpParser.src/M000006.html +27 -0
  21. data/doc/rdoc/classes/Mongrel/HttpParser.src/M000007.html +28 -0
  22. data/doc/rdoc/classes/Mongrel/HttpRequest.html +177 -0
  23. data/doc/rdoc/classes/Mongrel/HttpRequest.src/M000025.html +30 -0
  24. data/doc/rdoc/classes/Mongrel/HttpResponse.html +202 -0
  25. data/doc/rdoc/classes/Mongrel/HttpResponse.src/M000020.html +21 -0
  26. data/doc/rdoc/classes/Mongrel/HttpResponse.src/M000021.html +20 -0
  27. data/doc/rdoc/classes/Mongrel/HttpResponse.src/M000022.html +25 -0
  28. data/doc/rdoc/classes/Mongrel/HttpServer.html +336 -0
  29. data/doc/rdoc/classes/Mongrel/HttpServer.src/M000008.html +26 -0
  30. data/doc/rdoc/classes/Mongrel/HttpServer.src/M000009.html +58 -0
  31. data/doc/rdoc/classes/Mongrel/HttpServer.src/M000010.html +22 -0
  32. data/doc/rdoc/classes/Mongrel/HttpServer.src/M000011.html +18 -0
  33. data/doc/rdoc/classes/Mongrel/HttpServer.src/M000012.html +18 -0
  34. data/doc/rdoc/classes/Mongrel/URIClassifier.html +257 -0
  35. data/doc/rdoc/classes/Mongrel/URIClassifier.src/M000015.html +54 -0
  36. data/doc/rdoc/classes/Mongrel/URIClassifier.src/M000016.html +50 -0
  37. data/doc/rdoc/classes/Mongrel/URIClassifier.src/M000017.html +36 -0
  38. data/doc/rdoc/classes/Mongrel/URIClassifier.src/M000018.html +73 -0
  39. data/doc/rdoc/created.rid +1 -0
  40. data/doc/rdoc/files/COPYING.html +756 -0
  41. data/doc/rdoc/files/LICENSE.html +756 -0
  42. data/doc/rdoc/files/README.html +273 -0
  43. data/doc/rdoc/files/ext/http11/http11_c.html +101 -0
  44. data/doc/rdoc/files/lib/mongrel_rb.html +111 -0
  45. data/doc/rdoc/fr_class_index.html +35 -0
  46. data/doc/rdoc/fr_file_index.html +31 -0
  47. data/doc/rdoc/fr_method_index.html +51 -0
  48. data/doc/rdoc/index.html +24 -0
  49. data/doc/rdoc/rdoc-style.css +208 -0
  50. data/examples/camping/blog.rb +300 -0
  51. data/examples/camping/tepee.rb +168 -0
  52. data/examples/simpletest.rb +16 -0
  53. data/examples/webrick_compare.rb +20 -0
  54. data/ext/http11/MANIFEST +0 -0
  55. data/ext/http11/ext_help.h +14 -0
  56. data/ext/http11/extconf.rb +6 -0
  57. data/ext/http11/http11.c +436 -0
  58. data/ext/http11/http11_parser.c +918 -0
  59. data/ext/http11/http11_parser.h +37 -0
  60. data/ext/http11/tst.h +40 -0
  61. data/ext/http11/tst_cleanup.c +24 -0
  62. data/ext/http11/tst_delete.c +146 -0
  63. data/ext/http11/tst_grow_node_free_list.c +38 -0
  64. data/ext/http11/tst_init.c +41 -0
  65. data/ext/http11/tst_insert.c +192 -0
  66. data/ext/http11/tst_search.c +54 -0
  67. data/lib/mongrel.rb +298 -0
  68. data/setup.rb +1360 -0
  69. data/test/test_http11.rb +38 -0
  70. data/test/test_response.rb +44 -0
  71. data/test/test_uriclassifier.rb +104 -0
  72. data/test/test_ws.rb +33 -0
  73. data/tools/rakehelp.rb +99 -0
  74. metadata +132 -0
@@ -0,0 +1,168 @@
1
+ #!/usr/bin/ruby
2
+ $:.unshift File.dirname(__FILE__) + "/../../lib"
3
+ %w(rubygems redcloth camping acts_as_versioned).each { |lib| require lib }
4
+
5
+ Camping.goes :Tepee
6
+
7
+ module Tepee::Models
8
+ def self.schema(&block)
9
+ @@schema = block if block_given?
10
+ @@schema
11
+ end
12
+
13
+ class Page < Base
14
+ PAGE_LINK = /\[\[([^\]|]*)[|]?([^\]]*)\]\]/
15
+ validates_uniqueness_of :title
16
+ before_save { |r| r.title = r.title.underscore }
17
+ acts_as_versioned
18
+ end
19
+ end
20
+
21
+ Tepee::Models.schema do
22
+ create_table :tepee_pages, :force => true do |t|
23
+ t.column :title, :string, :limit => 255
24
+ t.column :body, :text
25
+ end
26
+ Tepee::Models::Page.create_versioned_table
27
+ end
28
+
29
+ module Tepee::Controllers
30
+ class Index < R '/'
31
+ def get
32
+ redirect Show, 'home_page'
33
+ end
34
+ end
35
+
36
+ class List < R '/list'
37
+ def get
38
+ @pages = Page.find :all, :order => 'title'
39
+ render :list
40
+ end
41
+ end
42
+
43
+ class Show < R '/s/(\w+)', '/s/(\w+)/(\d+)'
44
+ def get page_name, version = nil
45
+ redirect(Edit, page_name, 1) and return unless @page = Page.find_by_title(page_name)
46
+ @version = (version.nil? or version == @page.version.to_s) ? @page : @page.versions.find_by_version(version)
47
+ render :show
48
+ end
49
+ end
50
+
51
+ class Edit < R '/e/(\w+)/(\d+)', '/e/(\w+)'
52
+ def get page_name, version = nil
53
+ @page = Page.find_or_create_by_title(page_name)
54
+ @page = @page.versions.find_by_version(version) unless version.nil? or version == @page.version.to_s
55
+ render :edit
56
+ end
57
+
58
+ def post page_name
59
+ Page.find_or_create_by_title(page_name).update_attributes :body => input.post_body and redirect Show, page_name
60
+ end
61
+ end
62
+ end
63
+
64
+ module Tepee::Views
65
+ def layout
66
+ html do
67
+ head do
68
+ title 'test'
69
+ end
70
+ body do
71
+ p do
72
+ small do
73
+ span "welcome to " ; a 'tepee', :href => "http://code.whytheluckystiff.net/svn/camping/trunk/examples/tepee/"
74
+ span '. go ' ; a 'home', :href => R(Show, 'home_page')
75
+ span '. list all ' ; a 'pages', :href => R(List)
76
+ end
77
+ end
78
+ div.content do
79
+ self << yield
80
+ end
81
+ end
82
+ end
83
+ end
84
+
85
+ def show
86
+ h1 @page.title
87
+ div { _markup @version.body }
88
+ p do
89
+ a 'edit', :href => R(Edit, @version.title, @version.version)
90
+ a 'back', :href => R(Show, @version.title, @version.version-1) unless @version.version == 1
91
+ a 'next', :href => R(Show, @version.title, @version.version+1) unless @version.version == @page.version
92
+ a 'current', :href => R(Show, @version.title) unless @version.version == @page.version
93
+ end
94
+ end
95
+
96
+ def edit
97
+ form :method => 'post', :action => R(Edit, @page.title) do
98
+ p do
99
+ label 'Body' ; br
100
+ textarea @page.body, :name => 'post_body', :rows => 50, :cols => 100
101
+ end
102
+
103
+ p do
104
+ input :type => 'submit'
105
+ a 'cancel', :href => R(Show, @page.title, @page.version)
106
+ end
107
+ end
108
+ end
109
+
110
+ def list
111
+ h1 'all pages'
112
+ ul { @pages.each { |p| li { a p.title, :href => R(Show, p.title) } } }
113
+ end
114
+
115
+ def _markup body
116
+ return '' if body.blank?
117
+ body.gsub!(Tepee::Models::Page::PAGE_LINK) do
118
+ page = title = $1.underscore
119
+ title = $2 unless $2.empty?
120
+ if Tepee::Models::Page.find(:all, :select => 'title').collect { |p| p.title }.include?(page)
121
+ %Q{<a href="#{R Show, page}">#{title}</a>}
122
+ else
123
+ %Q{<span>#{title}<a href="#{R Edit, page, 1}">?</a></span>}
124
+ end
125
+ end
126
+ RedCloth.new(body, [ :hard_breaks ]).to_html
127
+ end
128
+ end
129
+
130
+ def Tepee.create
131
+ unless Tepee::Models::Page.table_exists?
132
+ ActiveRecord::Schema.define(&Tepee::Models.schema)
133
+ Tepee::Models::Page.reset_column_information
134
+ end
135
+ end
136
+
137
+ if __FILE__ == $0
138
+ require 'thread'
139
+
140
+ class CampingHandler < Mongrel::HttpHandler
141
+ def initialize(klass)
142
+ @klass = klass
143
+ end
144
+ def process(request, response)
145
+ req = StringIO.new(request.body)
146
+ controller = @klass.run(req, request.params)
147
+ response.start(controller.status) do |head,out|
148
+ controller.headers.each do |k, v|
149
+ [*v].each do |vi|
150
+ head[k] = vi
151
+ end
152
+ end
153
+ out << controller.body
154
+ end
155
+ end
156
+ end
157
+
158
+ Tepee::Models::Base.establish_connection :adapter => 'sqlite3', :database => 'tepee.db'
159
+ Tepee::Models::Base.logger = Logger.new('camping.log')
160
+ Tepee::Models::Base.threaded_connections=false
161
+ Tepee.create
162
+
163
+ h = Mongrel::HttpServer.new("0.0.0.0", "3000")
164
+ puts "** Tepee example is running at http://localhost:3000/tepee"
165
+ h.register("/tepee", CampingHandler.new(Tepee))
166
+ h.register("/favicon.ico", Mongrel::Error404Handler.new(""))
167
+ h.run.join
168
+ end
@@ -0,0 +1,16 @@
1
+ require 'mongrel'
2
+ require 'yaml'
3
+
4
+ class SimpleHandler < Mongrel::HttpHandler
5
+ def process(request, response)
6
+ response.start do |head,out|
7
+ head["Content-Type"] = "text/plain"
8
+ out.write("hello!\n")
9
+ end
10
+ end
11
+ end
12
+
13
+ h = Mongrel::HttpServer.new("0.0.0.0", "3000")
14
+ h.register("/test", SimpleHandler.new)
15
+ h.run.join
16
+
@@ -0,0 +1,20 @@
1
+ #!/usr/local/bin/ruby
2
+ require 'webrick'
3
+ include WEBrick
4
+
5
+ s = HTTPServer.new( :Port => 4000 )
6
+
7
+ # HTTPServer#mount(path, servletclass)
8
+ # When a request referring "/hello" is received,
9
+ # the HTTPServer get an instance of servletclass
10
+ # and then call a method named do_"a HTTP method".
11
+
12
+ class HelloServlet < HTTPServlet::AbstractServlet
13
+ def do_GET(req, res)
14
+ res.body = "hello!"
15
+ res['Content-Type'] = "text/html"
16
+ end
17
+ end
18
+ s.mount("/test", HelloServlet)
19
+
20
+ s.start
File without changes
@@ -0,0 +1,14 @@
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
+ #ifdef DEBUG
9
+ #define TRACE() fprintf(stderr, "> %s:%d:%s\n", __FILE__, __LINE__, __FUNCTION__)
10
+ #else
11
+ #define TRACE()
12
+ #endif
13
+
14
+ #endif
@@ -0,0 +1,6 @@
1
+ require 'mkmf'
2
+
3
+ dir_config("http11")
4
+ have_library("c", "main")
5
+
6
+ create_makefile("http11")
@@ -0,0 +1,436 @@
1
+ #include "ruby.h"
2
+ #include "ext_help.h"
3
+ #include <assert.h>
4
+ #include <string.h>
5
+ #include "http11_parser.h"
6
+ #include <ctype.h>
7
+ #include "tst.h"
8
+
9
+ static VALUE mMongrel;
10
+ static VALUE cHttpParser;
11
+ static VALUE cURIClassifier;
12
+ static int id_handler_map;
13
+
14
+
15
+ void http_field(void *data, const char *field, size_t flen, const char *value, size_t vlen)
16
+ {
17
+ char *ch, *end;
18
+ VALUE req = (VALUE)data;
19
+ VALUE f = rb_str_new2("HTTP_");
20
+ VALUE v = rb_str_new(value, vlen);
21
+
22
+ rb_str_buf_cat(f, field, flen);
23
+
24
+ for(ch = RSTRING(f)->ptr, end = ch + RSTRING(f)->len; ch < end; ch++) {
25
+ if(*ch == '-') {
26
+ *ch = '_';
27
+ } else {
28
+ *ch = toupper(*ch);
29
+ }
30
+ }
31
+
32
+ rb_hash_aset(req, f, v);
33
+ }
34
+
35
+ void request_method(void *data, const char *at, size_t length)
36
+ {
37
+ VALUE req = (VALUE)data;
38
+ VALUE val = rb_str_new(at, length);
39
+ VALUE id = rb_str_new2("REQUEST_METHOD");
40
+ rb_hash_aset(req, id, val);
41
+ }
42
+
43
+ void path_info(void *data, const char *at, size_t length)
44
+ {
45
+ VALUE req = (VALUE)data;
46
+ VALUE val = rb_str_new(at, length);
47
+ VALUE id = rb_str_new2("PATH_INFO");
48
+ rb_hash_aset(req, id, val);
49
+ }
50
+
51
+
52
+ void query_string(void *data, const char *at, size_t length)
53
+ {
54
+ VALUE req = (VALUE)data;
55
+ VALUE val = rb_str_new(at, length);
56
+ VALUE id = rb_str_new2("QUERY_STRING");
57
+ rb_hash_aset(req, id, val);
58
+ }
59
+
60
+ void http_version(void *data, const char *at, size_t length)
61
+ {
62
+ VALUE req = (VALUE)data;
63
+ VALUE val = rb_str_new(at, length);
64
+ VALUE id = rb_str_new2("HTTP_VERSION");
65
+ rb_hash_aset(req, id, val);
66
+ }
67
+
68
+
69
+
70
+
71
+
72
+ void HttpParser_free(void *data) {
73
+ TRACE();
74
+
75
+ if(data) {
76
+ free(data);
77
+ }
78
+ }
79
+
80
+
81
+ VALUE HttpParser_alloc(VALUE klass)
82
+ {
83
+ VALUE obj;
84
+ http_parser *hp = ALLOC_N(http_parser, 1);
85
+ TRACE();
86
+ hp->http_field = http_field;
87
+ hp->request_method = request_method;
88
+ hp->path_info = path_info;
89
+ hp->query_string = query_string;
90
+ hp->http_version = http_version;
91
+
92
+ obj = Data_Wrap_Struct(klass, NULL, HttpParser_free, hp);
93
+
94
+ return obj;
95
+ }
96
+
97
+
98
+ /**
99
+ * call-seq:
100
+ * parser.new -> parser
101
+ *
102
+ * Creates a new parser.
103
+ */
104
+ VALUE HttpParser_init(VALUE self)
105
+ {
106
+ http_parser *http = NULL;
107
+ DATA_GET(self, http_parser, http);
108
+ http_parser_init(http);
109
+
110
+ return self;
111
+ }
112
+
113
+
114
+ /**
115
+ * call-seq:
116
+ * parser.reset -> nil
117
+ *
118
+ * Resets the parser to it's initial state so that you can reuse it
119
+ * rather than making new ones.
120
+ */
121
+ VALUE HttpParser_reset(VALUE self)
122
+ {
123
+ http_parser *http = NULL;
124
+ DATA_GET(self, http_parser, http);
125
+ http_parser_init(http);
126
+
127
+ return Qnil;
128
+ }
129
+
130
+
131
+ /**
132
+ * call-seq:
133
+ * parser.finish -> true/false
134
+ *
135
+ * Finishes a parser early which could put in a "good" or bad state.
136
+ * You should call reset after finish it or bad things will happen.
137
+ */
138
+ VALUE HttpParser_finish(VALUE self)
139
+ {
140
+ http_parser *http = NULL;
141
+ DATA_GET(self, http_parser, http);
142
+ http_parser_finish(http);
143
+
144
+ return http_parser_is_finished(http) ? Qtrue : Qfalse;
145
+ }
146
+
147
+
148
+ /**
149
+ * call-seq:
150
+ * parser.execute(req_hash, data) -> Integer
151
+ *
152
+ * Takes a Hash and a String of data, parses the String of data filling in the Hash
153
+ * returning an Integer to indicate how much of the data has been read. No matter
154
+ * what the return value, you should call HttpParser#finished? and HttpParser#error?
155
+ * to figure out if it's done parsing or there was an error.
156
+ *
157
+ * This function now throws an exception when there is a parsing error. This makes
158
+ * the logic for working with the parser much easier. You can still test for an
159
+ * error, but now you need to wrap the parser with an exception handling block.
160
+ */
161
+ VALUE HttpParser_execute(VALUE self, VALUE req_hash, VALUE data)
162
+ {
163
+ http_parser *http = NULL;
164
+ DATA_GET(self, http_parser, http);
165
+
166
+ http->data = (void *)req_hash;
167
+ http_parser_execute(http, RSTRING(data)->ptr, RSTRING(data)->len);
168
+
169
+ if(http_parser_has_error(http)) {
170
+ rb_raise(rb_eStandardError, "HTTP Parsing failure");
171
+ } else {
172
+ return INT2FIX(http_parser_nread(http));
173
+ }
174
+ }
175
+
176
+
177
+ /**
178
+ * call-seq:
179
+ * parser.error? -> true/false
180
+ *
181
+ * Tells you whether the parser is in an error state.
182
+ */
183
+ VALUE HttpParser_has_error(VALUE self)
184
+ {
185
+ http_parser *http = NULL;
186
+ DATA_GET(self, http_parser, http);
187
+
188
+ return http_parser_has_error(http) ? Qtrue : Qfalse;
189
+ }
190
+
191
+
192
+ /**
193
+ * call-seq:
194
+ * parser.finished? -> true/false
195
+ *
196
+ * Tells you whether the parser is finished or not and in a good state.
197
+ */
198
+ VALUE HttpParser_is_finished(VALUE self)
199
+ {
200
+ http_parser *http = NULL;
201
+ DATA_GET(self, http_parser, http);
202
+
203
+ return http_parser_is_finished(http) ? Qtrue : Qfalse;
204
+ }
205
+
206
+
207
+ /**
208
+ * call-seq:
209
+ * parser.nread -> Integer
210
+ *
211
+ * Returns the amount of data processed so far during this processing cycle. It is
212
+ * set to 0 on initialize or reset calls and is incremented each time execute is called.
213
+ */
214
+ VALUE HttpParser_nread(VALUE self)
215
+ {
216
+ http_parser *http = NULL;
217
+ DATA_GET(self, http_parser, http);
218
+
219
+ return INT2FIX(http->nread);
220
+ }
221
+
222
+
223
+ void URIClassifier_free(void *data)
224
+ {
225
+ TRACE();
226
+
227
+ if(data) {
228
+ tst_cleanup((struct tst *)data);
229
+ }
230
+ }
231
+
232
+
233
+ #define TRIE_INCREASE 30
234
+
235
+ VALUE URIClassifier_alloc(VALUE klass)
236
+ {
237
+ VALUE obj;
238
+ struct tst *tst = tst_init(TRIE_INCREASE);
239
+ TRACE();
240
+ assert(tst && "failed to initialize trie structure");
241
+
242
+ obj = Data_Wrap_Struct(klass, NULL, URIClassifier_free, tst);
243
+
244
+ return obj;
245
+ }
246
+
247
+ /**
248
+ * call-seq:
249
+ * URIClassifier.new -> URIClassifier
250
+ *
251
+ * Initializes a new URIClassifier object that you can use to associate URI sequences
252
+ * with objects. You can actually use it with any string sequence and any objects,
253
+ * but it's mostly used with URIs.
254
+ *
255
+ * It uses TST from http://www.octavian.org/cs/software.html to build an ternary search
256
+ * trie to hold all of the URIs. It uses this to do an initial search for the a URI
257
+ * prefix, and then to break the URI into SCRIPT_NAME and PATH_INFO portions. It actually
258
+ * will do two searches most of the time in order to find the right handler for the
259
+ * registered prefix portion.
260
+ *
261
+ * Here's how it all works. Let's say you register "/blog" with a BlogHandler. Great.
262
+ * Now, someone goes to "/blog/zedsucks/ass". You want SCRIPT_NAME to be "/blog" and
263
+ * PATH_INFO to be "/zedsucks/ass". URIClassifier first does a TST search and comes
264
+ * up with a failure, but knows that the failure ended at the "/blog" part. So, that's
265
+ * the SCRIPT_NAME. It then tries a second search for just "/blog". If that comes back
266
+ * good then it sets the rest ("/zedsucks/ass") to the PATH_INFO and returns the BlogHandler.
267
+ *
268
+ * The optimal approach would be to not do the search twice, but the TST lib doesn't
269
+ * really support returning prefixes. Might not be hard to add later.
270
+ *
271
+ * The key though is that it will try to match the *longest* match it can. If you
272
+ * also register "/blog/zed" then the above URI will give SCRIPT_NAME="/blog/zed",
273
+ * PATH_INFO="sucks/ass". Probably not what you want, so your handler will need to
274
+ * do the 404 thing.
275
+ *
276
+ * Take a look at the postamble of example/tepee.rb to see how this is handled for
277
+ * Camping.
278
+ */
279
+ VALUE URIClassifier_init(VALUE self)
280
+ {
281
+ VALUE hash;
282
+
283
+ // we create an internal hash to protect stuff from the GC
284
+ hash = rb_hash_new();
285
+ rb_ivar_set(self, id_handler_map, hash);
286
+ }
287
+
288
+
289
+ /**
290
+ * call-seq:
291
+ * uc.register("/someuri", SampleHandler.new) -> nil
292
+ *
293
+ * Registers the SampleHandler (one for all requests) with the "/someuri".
294
+ * When URIClassifier::resolve is called with "/someuri" it'll return
295
+ * SampleHandler immediately. When "/someuri/pathhere" is called it'll
296
+ * find SomeHandler after a second search, and setup PATH_INFO="/pathhere".
297
+ *
298
+ * You actually can reuse this class to register nearly anything and
299
+ * quickly resolve it. This could be used for caching, fast mapping, etc.
300
+ * The downside is it uses much more memory than a Hash, but it can be
301
+ * a lot faster. It's main advantage is that it works on prefixes, which
302
+ * is damn hard to get right with a Hash.
303
+ */
304
+ VALUE URIClassifier_register(VALUE self, VALUE uri, VALUE handler)
305
+ {
306
+ int rc = 0;
307
+ void *ptr = NULL;
308
+ struct tst *tst = NULL;
309
+ DATA_GET(self, struct tst, tst);
310
+
311
+ rc = tst_insert((unsigned char *)StringValueCStr(uri), (void *)handler , tst, 0, &ptr);
312
+
313
+ if(rc == TST_DUPLICATE_KEY) {
314
+ rb_raise(rb_eStandardError, "Handler already registered with that name");
315
+ } else if(rc == TST_ERROR) {
316
+ rb_raise(rb_eStandardError, "Memory error registering handler");
317
+ } else if(rc == TST_NULL_KEY) {
318
+ rb_raise(rb_eStandardError, "URI was empty");
319
+ }
320
+
321
+ rb_hash_aset(rb_ivar_get(self, id_handler_map), uri, handler);
322
+
323
+ return Qnil;
324
+ }
325
+
326
+
327
+ /**
328
+ * call-seq:
329
+ * uc.unregister("/someuri")
330
+ *
331
+ * Yep, just removes this uri and it's handler from the trie.
332
+ */
333
+ VALUE URIClassifier_unregister(VALUE self, VALUE uri)
334
+ {
335
+ void *handler = NULL;
336
+ struct tst *tst = NULL;
337
+ DATA_GET(self, struct tst, tst);
338
+
339
+ handler = tst_delete((unsigned char *)StringValueCStr(uri), tst);
340
+
341
+ if(handler) {
342
+ rb_hash_delete(rb_ivar_get(self, id_handler_map), uri);
343
+
344
+ return (VALUE)handler;
345
+ } else {
346
+ return Qnil;
347
+ }
348
+ }
349
+
350
+
351
+ /**
352
+ * call-seq:
353
+ * uc.resolve("/someuri") -> "/someuri", "", handler
354
+ * uc.resolve("/someuri/pathinfo") -> "/someuri", "/pathinfo", handler
355
+ * uc.resolve("/notfound/orhere") -> nil, nil, nil
356
+ *
357
+ * Attempts to resolve either the whole URI or at the longest prefix, returning
358
+ * the prefix (as script_info), path (as path_info), and registered handler
359
+ * (usually an HttpHandler).
360
+ *
361
+ * It expects strings. Don't try other string-line stuff yet.
362
+ */
363
+ VALUE URIClassifier_resolve(VALUE self, VALUE uri)
364
+ {
365
+ void *handler = NULL;
366
+ int pref_len = 0;
367
+ struct tst *tst = NULL;
368
+ VALUE result;
369
+ VALUE script_name;
370
+ VALUE path_info;
371
+ unsigned char *uri_str = NULL;
372
+ unsigned char *script_name_str = NULL;
373
+
374
+ DATA_GET(self, struct tst, tst);
375
+ uri_str = (unsigned char *)StringValueCStr(uri);
376
+
377
+ handler = tst_search(uri_str, tst, &pref_len);
378
+
379
+ // setup for multiple return values
380
+ result = rb_ary_new();
381
+
382
+
383
+ if(handler == NULL) {
384
+ script_name = rb_str_substr (uri, 0, pref_len);
385
+ script_name_str = (unsigned char *)StringValueCStr(script_name);
386
+
387
+ handler = tst_search(script_name_str, tst, NULL);
388
+
389
+ if(handler == NULL) {
390
+ // didn't find the script name at all
391
+ rb_ary_push(result, Qnil);
392
+ rb_ary_push(result, Qnil);
393
+ rb_ary_push(result, Qnil);
394
+ return result;
395
+ } else {
396
+ // found a handler, setup the path info and we're good
397
+ path_info = rb_str_substr(uri, pref_len, RSTRING(uri)->len);
398
+ }
399
+ } else {
400
+ // whole thing was found, so uri is the script name, path info empty
401
+ script_name = uri;
402
+ path_info = rb_str_new2("");
403
+ }
404
+
405
+ rb_ary_push(result, script_name);
406
+ rb_ary_push(result, path_info);
407
+ rb_ary_push(result, (VALUE)handler);
408
+ return result;
409
+ }
410
+
411
+
412
+ void Init_http11()
413
+ {
414
+
415
+ mMongrel = rb_define_module("Mongrel");
416
+ id_handler_map = rb_intern("@handler_map");
417
+
418
+ cHttpParser = rb_define_class_under(mMongrel, "HttpParser", rb_cObject);
419
+ rb_define_alloc_func(cHttpParser, HttpParser_alloc);
420
+ rb_define_method(cHttpParser, "initialize", HttpParser_init,0);
421
+ rb_define_method(cHttpParser, "reset", HttpParser_reset,0);
422
+ rb_define_method(cHttpParser, "finish", HttpParser_finish,0);
423
+ rb_define_method(cHttpParser, "execute", HttpParser_execute,2);
424
+ rb_define_method(cHttpParser, "error?", HttpParser_has_error,0);
425
+ rb_define_method(cHttpParser, "finished?", HttpParser_is_finished,0);
426
+ rb_define_method(cHttpParser, "nread", HttpParser_nread,0);
427
+
428
+ cURIClassifier = rb_define_class_under(mMongrel, "URIClassifier", rb_cObject);
429
+ rb_define_alloc_func(cURIClassifier, URIClassifier_alloc);
430
+ rb_define_method(cURIClassifier, "initialize", URIClassifier_init, 0);
431
+ rb_define_method(cURIClassifier, "register", URIClassifier_register, 2);
432
+ rb_define_method(cURIClassifier, "unregister", URIClassifier_unregister, 1);
433
+ rb_define_method(cURIClassifier, "resolve", URIClassifier_resolve, 1);
434
+ }
435
+
436
+