mongrel 0.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
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
+