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.
- data/COPYING +504 -0
- data/LICENSE +504 -0
- data/README +117 -0
- data/Rakefile +30 -0
- data/doc/rdoc/classes/Mongrel.html +144 -0
- data/doc/rdoc/classes/Mongrel/Error404Handler.html +171 -0
- data/doc/rdoc/classes/Mongrel/Error404Handler.src/M000023.html +18 -0
- data/doc/rdoc/classes/Mongrel/Error404Handler.src/M000024.html +18 -0
- data/doc/rdoc/classes/Mongrel/HeaderOut.html +167 -0
- data/doc/rdoc/classes/Mongrel/HeaderOut.src/M000013.html +18 -0
- data/doc/rdoc/classes/Mongrel/HeaderOut.src/M000014.html +21 -0
- data/doc/rdoc/classes/Mongrel/HttpHandler.html +159 -0
- data/doc/rdoc/classes/Mongrel/HttpHandler.src/M000019.html +17 -0
- data/doc/rdoc/classes/Mongrel/HttpParser.html +271 -0
- data/doc/rdoc/classes/Mongrel/HttpParser.src/M000001.html +28 -0
- data/doc/rdoc/classes/Mongrel/HttpParser.src/M000002.html +29 -0
- data/doc/rdoc/classes/Mongrel/HttpParser.src/M000003.html +29 -0
- data/doc/rdoc/classes/Mongrel/HttpParser.src/M000004.html +41 -0
- data/doc/rdoc/classes/Mongrel/HttpParser.src/M000005.html +27 -0
- data/doc/rdoc/classes/Mongrel/HttpParser.src/M000006.html +27 -0
- data/doc/rdoc/classes/Mongrel/HttpParser.src/M000007.html +28 -0
- data/doc/rdoc/classes/Mongrel/HttpRequest.html +177 -0
- data/doc/rdoc/classes/Mongrel/HttpRequest.src/M000025.html +30 -0
- data/doc/rdoc/classes/Mongrel/HttpResponse.html +202 -0
- data/doc/rdoc/classes/Mongrel/HttpResponse.src/M000020.html +21 -0
- data/doc/rdoc/classes/Mongrel/HttpResponse.src/M000021.html +20 -0
- data/doc/rdoc/classes/Mongrel/HttpResponse.src/M000022.html +25 -0
- data/doc/rdoc/classes/Mongrel/HttpServer.html +336 -0
- data/doc/rdoc/classes/Mongrel/HttpServer.src/M000008.html +26 -0
- data/doc/rdoc/classes/Mongrel/HttpServer.src/M000009.html +58 -0
- data/doc/rdoc/classes/Mongrel/HttpServer.src/M000010.html +22 -0
- data/doc/rdoc/classes/Mongrel/HttpServer.src/M000011.html +18 -0
- data/doc/rdoc/classes/Mongrel/HttpServer.src/M000012.html +18 -0
- data/doc/rdoc/classes/Mongrel/URIClassifier.html +257 -0
- data/doc/rdoc/classes/Mongrel/URIClassifier.src/M000015.html +54 -0
- data/doc/rdoc/classes/Mongrel/URIClassifier.src/M000016.html +50 -0
- data/doc/rdoc/classes/Mongrel/URIClassifier.src/M000017.html +36 -0
- data/doc/rdoc/classes/Mongrel/URIClassifier.src/M000018.html +73 -0
- data/doc/rdoc/created.rid +1 -0
- data/doc/rdoc/files/COPYING.html +756 -0
- data/doc/rdoc/files/LICENSE.html +756 -0
- data/doc/rdoc/files/README.html +273 -0
- data/doc/rdoc/files/ext/http11/http11_c.html +101 -0
- data/doc/rdoc/files/lib/mongrel_rb.html +111 -0
- data/doc/rdoc/fr_class_index.html +35 -0
- data/doc/rdoc/fr_file_index.html +31 -0
- data/doc/rdoc/fr_method_index.html +51 -0
- data/doc/rdoc/index.html +24 -0
- data/doc/rdoc/rdoc-style.css +208 -0
- data/examples/camping/blog.rb +300 -0
- data/examples/camping/tepee.rb +168 -0
- data/examples/simpletest.rb +16 -0
- data/examples/webrick_compare.rb +20 -0
- data/ext/http11/MANIFEST +0 -0
- data/ext/http11/ext_help.h +14 -0
- data/ext/http11/extconf.rb +6 -0
- data/ext/http11/http11.c +436 -0
- data/ext/http11/http11_parser.c +918 -0
- data/ext/http11/http11_parser.h +37 -0
- data/ext/http11/tst.h +40 -0
- data/ext/http11/tst_cleanup.c +24 -0
- data/ext/http11/tst_delete.c +146 -0
- data/ext/http11/tst_grow_node_free_list.c +38 -0
- data/ext/http11/tst_init.c +41 -0
- data/ext/http11/tst_insert.c +192 -0
- data/ext/http11/tst_search.c +54 -0
- data/lib/mongrel.rb +298 -0
- data/setup.rb +1360 -0
- data/test/test_http11.rb +38 -0
- data/test/test_response.rb +44 -0
- data/test/test_uriclassifier.rb +104 -0
- data/test/test_ws.rb +33 -0
- data/tools/rakehelp.rb +99 -0
- 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
|
data/ext/http11/MANIFEST
ADDED
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
|
data/ext/http11/http11.c
ADDED
@@ -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
|
+
|