mongrel 1.1.5-x86-mingw32

Sign up to get free protection for your applications and to get access to all the features.
Files changed (73) hide show
  1. data.tar.gz.sig +4 -0
  2. data/CHANGELOG +18 -0
  3. data/COPYING +55 -0
  4. data/LICENSE +55 -0
  5. data/Manifest +69 -0
  6. data/README +74 -0
  7. data/TODO +5 -0
  8. data/bin/mongrel_rails +283 -0
  9. data/examples/builder.rb +29 -0
  10. data/examples/camping/README +3 -0
  11. data/examples/camping/blog.rb +294 -0
  12. data/examples/camping/tepee.rb +149 -0
  13. data/examples/httpd.conf +474 -0
  14. data/examples/mime.yaml +3 -0
  15. data/examples/mongrel.conf +9 -0
  16. data/examples/mongrel_simple_ctrl.rb +92 -0
  17. data/examples/mongrel_simple_service.rb +116 -0
  18. data/examples/monitrc +57 -0
  19. data/examples/random_thrash.rb +19 -0
  20. data/examples/simpletest.rb +52 -0
  21. data/examples/webrick_compare.rb +20 -0
  22. data/ext/http11/ext_help.h +14 -0
  23. data/ext/http11/extconf.rb +6 -0
  24. data/ext/http11/http11.c +402 -0
  25. data/ext/http11/http11_parser.c +1221 -0
  26. data/ext/http11/http11_parser.h +49 -0
  27. data/ext/http11/http11_parser.java.rl +170 -0
  28. data/ext/http11/http11_parser.rl +152 -0
  29. data/ext/http11/http11_parser_common.rl +54 -0
  30. data/ext/http11_java/Http11Service.java +13 -0
  31. data/ext/http11_java/org/jruby/mongrel/Http11.java +266 -0
  32. data/ext/http11_java/org/jruby/mongrel/Http11Parser.java +572 -0
  33. data/lib/http11.so +0 -0
  34. data/lib/mongrel.rb +355 -0
  35. data/lib/mongrel/camping.rb +107 -0
  36. data/lib/mongrel/cgi.rb +181 -0
  37. data/lib/mongrel/command.rb +222 -0
  38. data/lib/mongrel/configurator.rb +388 -0
  39. data/lib/mongrel/const.rb +110 -0
  40. data/lib/mongrel/debug.rb +203 -0
  41. data/lib/mongrel/gems.rb +22 -0
  42. data/lib/mongrel/handlers.rb +468 -0
  43. data/lib/mongrel/header_out.rb +28 -0
  44. data/lib/mongrel/http_request.rb +155 -0
  45. data/lib/mongrel/http_response.rb +163 -0
  46. data/lib/mongrel/init.rb +10 -0
  47. data/lib/mongrel/mime_types.yml +616 -0
  48. data/lib/mongrel/rails.rb +185 -0
  49. data/lib/mongrel/stats.rb +89 -0
  50. data/lib/mongrel/tcphack.rb +18 -0
  51. data/lib/mongrel/uri_classifier.rb +76 -0
  52. data/mongrel-public_cert.pem +20 -0
  53. data/mongrel.gemspec +242 -0
  54. data/setup.rb +1585 -0
  55. data/test/mime.yaml +3 -0
  56. data/test/mongrel.conf +1 -0
  57. data/test/test_cgi_wrapper.rb +26 -0
  58. data/test/test_command.rb +86 -0
  59. data/test/test_conditional.rb +107 -0
  60. data/test/test_configurator.rb +88 -0
  61. data/test/test_debug.rb +25 -0
  62. data/test/test_handlers.rb +126 -0
  63. data/test/test_http11.rb +156 -0
  64. data/test/test_redirect_handler.rb +45 -0
  65. data/test/test_request_progress.rb +100 -0
  66. data/test/test_response.rb +127 -0
  67. data/test/test_stats.rb +35 -0
  68. data/test/test_uriclassifier.rb +261 -0
  69. data/test/test_ws.rb +115 -0
  70. data/test/testhelp.rb +79 -0
  71. data/tools/trickletest.rb +45 -0
  72. metadata +197 -0
  73. metadata.gz.sig +1 -0
@@ -0,0 +1,3 @@
1
+ ---
2
+ .jpeg: image/jpeg
3
+ .png: image/test
@@ -0,0 +1,9 @@
1
+ ---
2
+ :environment: production
3
+ :daemon: "true"
4
+ :host: 0.0.0.0
5
+ :log_file: log/mongrel.log
6
+ :docroot: public
7
+ :debug: "false"
8
+ :port: 3000
9
+ :pid_file: log/mongrel.pid
@@ -0,0 +1,92 @@
1
+ ###############################################
2
+ # mongrel_simple_ctrl.rb
3
+ #
4
+ # Control script for the Mongrel server
5
+ ###############################################
6
+ require "optparse"
7
+ require "win32/service"
8
+ include Win32
9
+
10
+ # I start the service name with an 'A' so that it appears at the top
11
+ SERVICE_NAME = "MongrelSvc"
12
+ SERVICE_DISPLAYNAME = "Mongrel HTTP Server"
13
+ SCRIPT_ROOT = File.join(File.dirname(__FILE__), '.')
14
+ SCRIPT_NAME = "mongrel_simple_service.rb"
15
+ SERVICE_SCRIPT = File.expand_path(SCRIPT_ROOT + '/' + SCRIPT_NAME)
16
+
17
+ OPTIONS = {}
18
+
19
+ ARGV.options do |opts|
20
+ opts.on("-d", "--delete", "Delete the service"){ OPTIONS[:delete] = true }
21
+ opts.on("-u", "--uninstall","Delete the service"){ OPTIONS[:uninstall] = true }
22
+ opts.on("-s", "--start", "Start the service"){ OPTIONS[:start] = true }
23
+ opts.on("-x", "--stop", "Stop the service"){ OPTIONS[:stop] = true }
24
+ opts.on("-i", "--install","Install the service"){ OPTIONS[:install] = true }
25
+
26
+ opts.on("-h", "--help", "Show this help message."){ puts opts; exit }
27
+
28
+ opts.parse!
29
+ end
30
+
31
+ # Install the service
32
+ if OPTIONS[:install]
33
+ require 'rbconfig'
34
+
35
+ svc = Service.new
36
+ svc.create_service{ |s|
37
+ s.service_name = SERVICE_NAME
38
+ s.display_name = SERVICE_DISPLAYNAME
39
+ s.binary_path_name = Config::CONFIG['bindir'] + '/ruby ' + SERVICE_SCRIPT
40
+ s.dependencies = []
41
+ }
42
+ svc.close
43
+ puts "#{SERVICE_DISPLAYNAME} service installed"
44
+ end
45
+
46
+ # Start the service
47
+ if OPTIONS[:start]
48
+ Service.start(SERVICE_NAME)
49
+ started = false
50
+ while started == false
51
+ s = Service.status(SERVICE_NAME)
52
+ started = true if s.current_state == "running"
53
+ break if started == true
54
+ puts "One moment, " + s.current_state
55
+ sleep 1
56
+ end
57
+ puts "#{SERVICE_DISPLAYNAME} service started"
58
+ end
59
+
60
+ # Stop the service
61
+ if OPTIONS[:stop]
62
+ Service.stop(SERVICE_NAME)
63
+ stopped = false
64
+ while stopped == false
65
+ s = Service.status(SERVICE_NAME)
66
+ stopped = true if s.current_state == "stopped"
67
+ break if stopped == true
68
+ puts "One moment, " + s.current_state
69
+ sleep 1
70
+ end
71
+ puts "#{SERVICE_DISPLAYNAME} service stopped"
72
+ end
73
+
74
+ # Delete the service. Stop it first.
75
+ if OPTIONS[:delete] || OPTIONS[:uninstall]
76
+ begin
77
+ Service.stop(SERVICE_NAME)
78
+ rescue
79
+ end
80
+ begin
81
+ Service.delete(SERVICE_NAME)
82
+ rescue
83
+ end
84
+ puts "#{SERVICE_DISPLAYNAME} service deleted"
85
+ end
86
+ # END mongrel_rails_ctrl.rb
87
+
88
+
89
+
90
+
91
+
92
+
@@ -0,0 +1,116 @@
1
+ # This script emualtes script/server behavior but running webrick http server
2
+ require 'rubygems'
3
+
4
+ require 'mongrel'
5
+ require 'yaml'
6
+ require 'zlib'
7
+
8
+ require 'win32/service'
9
+
10
+ DEBUG_LOG_FILE = File.expand_path(File.dirname(__FILE__) + '/debug.log')
11
+
12
+ class SimpleHandler < Mongrel::HttpHandler
13
+ def process(request, response)
14
+ response.start do |head,out|
15
+ head["Content-Type"] = "text/html"
16
+ results = "<html><body>Your request:<br /><pre>#{request.params.to_yaml}</pre><a href=\"/files\">View the files.</a></body></html>"
17
+ if request.params["HTTP_ACCEPT_ENCODING"] == "gzip,deflate"
18
+ head["Content-Encoding"] = "deflate"
19
+ # send it back deflated
20
+ out << Zlib::Deflate.deflate(results)
21
+ else
22
+ # no gzip supported, send it back normal
23
+ out << results
24
+ end
25
+ end
26
+ end
27
+ end
28
+
29
+ class MongrelDaemon < Win32::Daemon
30
+ def initialize(options)
31
+ @options = options
32
+ end
33
+
34
+ def service_init
35
+ File.open(DEBUG_LOG_FILE,"a+") { |f| f.puts("#{Time.now} - service_init entered") }
36
+
37
+ File.open(DEBUG_LOG_FILE,"a+") { |f| f.puts("Mongrel running on #{@options[:ip]}:#{@options[:port]} with docroot #{@options[:server_root]}") }
38
+
39
+ @simple = SimpleHandler.new
40
+ @files = Mongrel::DirHandler.new(@options[:server_root])
41
+
42
+ @http_server = Mongrel::HttpServer.new(@options[:ip], @options[:port])
43
+ @http_server.register("/", @simple)
44
+ @http_server.register("/files", @files)
45
+
46
+ File.open(DEBUG_LOG_FILE,"a+") { |f| f.puts("#{Time.now} - service_init left") }
47
+ end
48
+
49
+ def service_stop
50
+ File.open(DEBUG_LOG_FILE,"a+"){ |f|
51
+ f.puts "stop signal received: " + Time.now.to_s
52
+ f.puts "sending stop to mongrel threads: " + Time.now.to_s
53
+ }
54
+ #@http_server.stop
55
+ end
56
+
57
+ def service_pause
58
+ File.open(DEBUG_LOG_FILE,"a+"){ |f|
59
+ f.puts "pause signal received: " + Time.now.to_s
60
+ }
61
+ end
62
+
63
+ def service_resume
64
+ File.open(DEBUG_LOG_FILE,"a+"){ |f|
65
+ f.puts "continue/resume signal received: " + Time.now.to_s
66
+ }
67
+ end
68
+
69
+ def service_main
70
+ File.open(DEBUG_LOG_FILE,"a+") { |f| f.puts("#{Time.now} - service_main entered") }
71
+
72
+ begin
73
+ File.open(DEBUG_LOG_FILE,"a+") { |f| f.puts("#{Time.now} - http_server.run") }
74
+ @http_server.run
75
+
76
+ # No runner thread was needed after all!
77
+ #@runner = Thread.new do
78
+ # @http_server.acceptor.join
79
+ #end
80
+ #File.open("d:\\test.log","a+") { |f| f.puts("#{Time.now} - runner.run") }
81
+ #@runner.run
82
+
83
+ # here is where magic happens!
84
+ # if put blocking code here, the thread never left service_main, and the rb_func_call in service.c
85
+ # never exit, even if the stop signal is received.
86
+ #
87
+ # to probe my theory, just comment the while loop and remove the '1' from sleep function
88
+ # service start ok, but fail to stop.
89
+ #
90
+ # Even if no functional code is in service_main (because we have other working threads),
91
+ # we must monitor the state of the service to exit when the STOP event is received.
92
+ #
93
+ # Note: maybe not loop in 1 second intervals?
94
+ while state == RUNNING
95
+ sleep 1
96
+ end
97
+
98
+ rescue StandardError, Exception, interrupt => err
99
+ File.open(DEBUG_LOG_FILE,"a+"){ |f| f.puts("#{Time.now} - Error: #{err}") }
100
+ File.open(DEBUG_LOG_FILE,"a+"){ |f| f.puts("BACKTRACE: " + err.backtrace.join("\n")) }
101
+
102
+ end
103
+
104
+ File.open(DEBUG_LOG_FILE,"a+") { |f| f.puts("#{Time.now} - service_main left") }
105
+ end
106
+
107
+ end
108
+
109
+ OPTIONS = {
110
+ :port => 3000,
111
+ :ip => "0.0.0.0",
112
+ :server_root => File.expand_path(File.dirname(__FILE__)),
113
+ }
114
+
115
+ web_server = MongrelDaemon.new(OPTIONS)
116
+ web_server.mainloop
@@ -0,0 +1,57 @@
1
+ set daemon 60
2
+ set logfile syslog facility log_daemon
3
+ set mailserver localhost
4
+ set mail-format { from: monit@localhost }
5
+ set alert root@localhost
6
+
7
+ check process sshd with pidfile /var/run/sshd.pid
8
+ start program "/etc/init.d/ssh start"
9
+ stop program "/etc/init.d/ssh stop"
10
+ if failed port 22 protocol ssh then restart
11
+ if 5 restarts within 5 cycles then timeout
12
+
13
+ check process mysql with pidfile /var/run/mysqld/mysqld.pid
14
+ group database
15
+ start program = "/etc/init.d/mysql start"
16
+ stop program = "/etc/init.d/mysql stop"
17
+ if failed host 127.0.0.1 port 3306 then restart
18
+ if 5 restarts within 5 cycles then timeout
19
+
20
+ check process httpd with pidfile /usr/local/apache2/logs/httpd.pid
21
+ group www-data
22
+ start program "/usr/local/apache2/bin/apachectl start"
23
+ stop program "/usr/local/apache2/bin/apachectl stop"
24
+ if failed host localhost port 80 protocol http
25
+ and request "/" then alert
26
+ if cpu is greater than 60% for 2 cycles then alert
27
+ if cpu > 80% for 5 cycles then restart
28
+ if children > 250 then restart
29
+ if loadavg(5min) greater than 10 for 8 cycles then alert
30
+ if 3 restarts within 5 cycles then timeout
31
+
32
+ check process mongrel_8000 with pidfile /var/rails/MYAPP/log/mongrel.8000.pid
33
+ group root
34
+ if failed host 127.0.0.1 port 8000 protocol http
35
+ and request "/" then alert
36
+ if cpu is greater than 60% for 2 cycles then alert
37
+ if cpu > 80% for 5 cycles then restart
38
+ if loadavg(5min) greater than 10 for 8 cycles then restart
39
+ if 3 restarts within 5 cycles then timeout
40
+
41
+ check process mongrel_8001 with pidfile /var/rails/MYAPP/log/mongrel.8001.pid
42
+ group root
43
+ if failed host 127.0.0.1 port 8001 protocol http
44
+ and request "/" then alert
45
+ if cpu is greater than 60% for 2 cycles then alert
46
+ if cpu > 80% for 5 cycles then alert
47
+ if loadavg(5min) greater than 10 for 8 cycles then alert
48
+ if 3 restarts within 5 cycles then timeout
49
+
50
+ check process postfix with pidfile /var/spool/postfix/pid/master.pid
51
+ group mail
52
+ start program = "/etc/init.d/postfix start"
53
+ stop program = "/etc/init.d/postfix stop"
54
+ if failed port 25 protocol smtp then restart
55
+ if 5 restarts within 5 cycles then timeout
56
+
57
+
@@ -0,0 +1,19 @@
1
+ require 'socket'
2
+ devrand = open("/dev/random","r")
3
+
4
+ loop do
5
+ s = TCPSocket.new(ARGV[0],ARGV[1])
6
+ s.write("GET / HTTP/1.1\r\n")
7
+ total = 0
8
+ begin
9
+ loop do
10
+ r = devrand.read(10)
11
+ n = s.write(r)
12
+ total += n
13
+ end
14
+ rescue Object
15
+ STDERR.puts "#$!: #{total}"
16
+ end
17
+ s.close
18
+ sleep 1
19
+ end
@@ -0,0 +1,52 @@
1
+ $LOAD_PATH << File.join(File.dirname(__FILE__), "..", "lib")
2
+ require 'mongrel'
3
+ require 'yaml'
4
+
5
+ class SimpleHandler < Mongrel::HttpHandler
6
+ def process(request, response)
7
+ response.start do |head,out|
8
+ head["Content-Type"] = "text/html"
9
+ results = "<html><body>Your request:<br /><pre>#{request.params.to_yaml}</pre><a href=\"/files\">View the files.</a></body></html>"
10
+ out << results
11
+ end
12
+ end
13
+ end
14
+
15
+ class DumbHandler < Mongrel::HttpHandler
16
+ def process(request, response)
17
+ response.start do |head,out|
18
+ head["Content-Type"] = "text/html"
19
+ out.write("test")
20
+ end
21
+ end
22
+ end
23
+
24
+
25
+ if ARGV.length != 3
26
+ STDERR.puts "usage: simpletest.rb <host> <port> <docroot>"
27
+ exit(1)
28
+ end
29
+
30
+ stats = Mongrel::StatisticsFilter.new(:sample_rate => 1)
31
+
32
+ config = Mongrel::Configurator.new :host => ARGV[0], :port => ARGV[1] do
33
+ listener do
34
+ uri "/", :handler => SimpleHandler.new
35
+ uri "/", :handler => Mongrel::DeflateFilter.new
36
+ uri "/", :handler => stats
37
+ uri "/dumb", :handler => DumbHandler.new
38
+ uri "/dumb", :handler => Mongrel::DeflateFilter.new
39
+ uri "/dumb", :handler => stats
40
+ uri "/files", :handler => Mongrel::DirHandler.new(ARGV[2])
41
+ uri "/files", :handler => stats
42
+ uri "/status", :handler => Mongrel::StatusHandler.new(:stats_filter => stats)
43
+ redirect "/redir1", "/"
44
+ redirect "/to", /to/, 'w'
45
+ end
46
+
47
+ trap("INT") { stop }
48
+ run
49
+ end
50
+
51
+ puts "Mongrel running on #{ARGV[0]}:#{ARGV[1]} with docroot #{ARGV[2]}"
52
+ config.join
@@ -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
@@ -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,402 @@
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
+
12
+ static VALUE mMongrel;
13
+ static VALUE cHttpParser;
14
+ static VALUE eHttpParserError;
15
+
16
+ #define id_handler_map rb_intern("@handler_map")
17
+ #define id_http_body rb_intern("@http_body")
18
+
19
+ static VALUE global_http_prefix;
20
+ static VALUE global_request_method;
21
+ static VALUE global_request_uri;
22
+ static VALUE global_fragment;
23
+ static VALUE global_query_string;
24
+ static VALUE global_http_version;
25
+ static VALUE global_content_length;
26
+ static VALUE global_http_content_length;
27
+ static VALUE global_request_path;
28
+ static VALUE global_content_type;
29
+ static VALUE global_http_content_type;
30
+ static VALUE global_gateway_interface;
31
+ static VALUE global_gateway_interface_value;
32
+ static VALUE global_server_name;
33
+ static VALUE global_server_port;
34
+ static VALUE global_server_protocol;
35
+ static VALUE global_server_protocol_value;
36
+ static VALUE global_http_host;
37
+ static VALUE global_mongrel_version;
38
+ static VALUE global_server_software;
39
+ static VALUE global_port_80;
40
+
41
+ #define TRIE_INCREASE 30
42
+
43
+ /** Defines common length and error messages for input length validation. */
44
+ #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."
45
+
46
+ /** Validates the max length of given input and throws an HttpParserError exception if over. */
47
+ #define VALIDATE_MAX_LENGTH(len, N) if(len > MAX_##N##_LENGTH) { rb_raise(eHttpParserError, MAX_##N##_LENGTH_ERR); }
48
+
49
+ /** Defines global strings in the init method. */
50
+ #define DEF_GLOBAL(N, val) global_##N = rb_obj_freeze(rb_str_new2(val)); rb_global_variable(&global_##N)
51
+
52
+
53
+ /* Defines the maximum allowed lengths for various input elements.*/
54
+ DEF_MAX_LENGTH(FIELD_NAME, 256);
55
+ DEF_MAX_LENGTH(FIELD_VALUE, 80 * 1024);
56
+ DEF_MAX_LENGTH(REQUEST_URI, 1024 * 12);
57
+ DEF_MAX_LENGTH(FRAGMENT, 1024); /* Don't know if this length is specified somewhere or not */
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 fragment(void *data, const char *at, size_t length)
109
+ {
110
+ VALUE req = (VALUE)data;
111
+ VALUE val = Qnil;
112
+
113
+ VALIDATE_MAX_LENGTH(length, FRAGMENT);
114
+
115
+ val = rb_str_new(at, length);
116
+ rb_hash_aset(req, global_fragment, val);
117
+ }
118
+
119
+ void request_path(void *data, const char *at, size_t length)
120
+ {
121
+ VALUE req = (VALUE)data;
122
+ VALUE val = Qnil;
123
+
124
+ VALIDATE_MAX_LENGTH(length, REQUEST_PATH);
125
+
126
+ val = rb_str_new(at, length);
127
+ rb_hash_aset(req, global_request_path, val);
128
+ }
129
+
130
+ void query_string(void *data, const char *at, size_t length)
131
+ {
132
+ VALUE req = (VALUE)data;
133
+ VALUE val = Qnil;
134
+
135
+ VALIDATE_MAX_LENGTH(length, QUERY_STRING);
136
+
137
+ val = rb_str_new(at, length);
138
+ rb_hash_aset(req, global_query_string, val);
139
+ }
140
+
141
+ void http_version(void *data, const char *at, size_t length)
142
+ {
143
+ VALUE req = (VALUE)data;
144
+ VALUE val = rb_str_new(at, length);
145
+ rb_hash_aset(req, global_http_version, val);
146
+ }
147
+
148
+ /** Finalizes the request header to have a bunch of stuff that's
149
+ needed. */
150
+
151
+ void header_done(void *data, const char *at, size_t length)
152
+ {
153
+ VALUE req = (VALUE)data;
154
+ VALUE temp = Qnil;
155
+ VALUE ctype = Qnil;
156
+ VALUE clen = Qnil;
157
+ char *colon = NULL;
158
+
159
+ clen = rb_hash_aref(req, global_http_content_length);
160
+ if(clen != Qnil) {
161
+ rb_hash_aset(req, global_content_length, clen);
162
+ }
163
+
164
+ ctype = rb_hash_aref(req, global_http_content_type);
165
+ if(ctype != Qnil) {
166
+ rb_hash_aset(req, global_content_type, ctype);
167
+ }
168
+
169
+ rb_hash_aset(req, global_gateway_interface, global_gateway_interface_value);
170
+ if((temp = rb_hash_aref(req, global_http_host)) != Qnil) {
171
+ /* ruby better close strings off with a '\0' dammit */
172
+ colon = strchr(RSTRING(temp)->ptr, ':');
173
+ if(colon != NULL) {
174
+ rb_hash_aset(req, global_server_name, rb_str_substr(temp, 0, colon - RSTRING(temp)->ptr));
175
+ rb_hash_aset(req, global_server_port,
176
+ rb_str_substr(temp, colon - RSTRING(temp)->ptr+1,
177
+ RSTRING(temp)->len));
178
+ } else {
179
+ rb_hash_aset(req, global_server_name, temp);
180
+ rb_hash_aset(req, global_server_port, global_port_80);
181
+ }
182
+ }
183
+
184
+ /* grab the initial body and stuff it into an ivar */
185
+ rb_ivar_set(req, id_http_body, rb_str_new(at, length));
186
+ rb_hash_aset(req, global_server_protocol, global_server_protocol_value);
187
+ rb_hash_aset(req, global_server_software, global_mongrel_version);
188
+ }
189
+
190
+
191
+ void HttpParser_free(void *data) {
192
+ TRACE();
193
+
194
+ if(data) {
195
+ free(data);
196
+ }
197
+ }
198
+
199
+
200
+ VALUE HttpParser_alloc(VALUE klass)
201
+ {
202
+ VALUE obj;
203
+ http_parser *hp = ALLOC_N(http_parser, 1);
204
+ TRACE();
205
+ hp->http_field = http_field;
206
+ hp->request_method = request_method;
207
+ hp->request_uri = request_uri;
208
+ hp->fragment = fragment;
209
+ hp->request_path = request_path;
210
+ hp->query_string = query_string;
211
+ hp->http_version = http_version;
212
+ hp->header_done = header_done;
213
+ http_parser_init(hp);
214
+
215
+ obj = Data_Wrap_Struct(klass, NULL, HttpParser_free, hp);
216
+
217
+ return obj;
218
+ }
219
+
220
+
221
+ /**
222
+ * call-seq:
223
+ * parser.new -> parser
224
+ *
225
+ * Creates a new parser.
226
+ */
227
+ VALUE HttpParser_init(VALUE self)
228
+ {
229
+ http_parser *http = NULL;
230
+ DATA_GET(self, http_parser, http);
231
+ http_parser_init(http);
232
+
233
+ return self;
234
+ }
235
+
236
+
237
+ /**
238
+ * call-seq:
239
+ * parser.reset -> nil
240
+ *
241
+ * Resets the parser to it's initial state so that you can reuse it
242
+ * rather than making new ones.
243
+ */
244
+ VALUE HttpParser_reset(VALUE self)
245
+ {
246
+ http_parser *http = NULL;
247
+ DATA_GET(self, http_parser, http);
248
+ http_parser_init(http);
249
+
250
+ return Qnil;
251
+ }
252
+
253
+
254
+ /**
255
+ * call-seq:
256
+ * parser.finish -> true/false
257
+ *
258
+ * Finishes a parser early which could put in a "good" or bad state.
259
+ * You should call reset after finish it or bad things will happen.
260
+ */
261
+ VALUE HttpParser_finish(VALUE self)
262
+ {
263
+ http_parser *http = NULL;
264
+ DATA_GET(self, http_parser, http);
265
+ http_parser_finish(http);
266
+
267
+ return http_parser_is_finished(http) ? Qtrue : Qfalse;
268
+ }
269
+
270
+
271
+ /**
272
+ * call-seq:
273
+ * parser.execute(req_hash, data, start) -> Integer
274
+ *
275
+ * Takes a Hash and a String of data, parses the String of data filling in the Hash
276
+ * returning an Integer to indicate how much of the data has been read. No matter
277
+ * what the return value, you should call HttpParser#finished? and HttpParser#error?
278
+ * to figure out if it's done parsing or there was an error.
279
+ *
280
+ * This function now throws an exception when there is a parsing error. This makes
281
+ * the logic for working with the parser much easier. You can still test for an
282
+ * error, but now you need to wrap the parser with an exception handling block.
283
+ *
284
+ * The third argument allows for parsing a partial request and then continuing
285
+ * the parsing from that position. It needs all of the original data as well
286
+ * so you have to append to the data buffer as you read.
287
+ */
288
+ VALUE HttpParser_execute(VALUE self, VALUE req_hash, VALUE data, VALUE start)
289
+ {
290
+ http_parser *http = NULL;
291
+ int from = 0;
292
+ char *dptr = NULL;
293
+ long dlen = 0;
294
+
295
+ DATA_GET(self, http_parser, http);
296
+
297
+ from = FIX2INT(start);
298
+ dptr = RSTRING(data)->ptr;
299
+ dlen = RSTRING(data)->len;
300
+
301
+ if(from >= dlen) {
302
+ rb_raise(eHttpParserError, "Requested start is after data buffer end.");
303
+ } else {
304
+ http->data = (void *)req_hash;
305
+ http_parser_execute(http, dptr, dlen, from);
306
+
307
+ VALIDATE_MAX_LENGTH(http_parser_nread(http), HEADER);
308
+
309
+ if(http_parser_has_error(http)) {
310
+ rb_raise(eHttpParserError, "Invalid HTTP format, parsing fails.");
311
+ } else {
312
+ return INT2FIX(http_parser_nread(http));
313
+ }
314
+ }
315
+ }
316
+
317
+
318
+
319
+ /**
320
+ * call-seq:
321
+ * parser.error? -> true/false
322
+ *
323
+ * Tells you whether the parser is in an error state.
324
+ */
325
+ VALUE HttpParser_has_error(VALUE self)
326
+ {
327
+ http_parser *http = NULL;
328
+ DATA_GET(self, http_parser, http);
329
+
330
+ return http_parser_has_error(http) ? Qtrue : Qfalse;
331
+ }
332
+
333
+
334
+ /**
335
+ * call-seq:
336
+ * parser.finished? -> true/false
337
+ *
338
+ * Tells you whether the parser is finished or not and in a good state.
339
+ */
340
+ VALUE HttpParser_is_finished(VALUE self)
341
+ {
342
+ http_parser *http = NULL;
343
+ DATA_GET(self, http_parser, http);
344
+
345
+ return http_parser_is_finished(http) ? Qtrue : Qfalse;
346
+ }
347
+
348
+
349
+ /**
350
+ * call-seq:
351
+ * parser.nread -> Integer
352
+ *
353
+ * Returns the amount of data processed so far during this processing cycle. It is
354
+ * set to 0 on initialize or reset calls and is incremented each time execute is called.
355
+ */
356
+ VALUE HttpParser_nread(VALUE self)
357
+ {
358
+ http_parser *http = NULL;
359
+ DATA_GET(self, http_parser, http);
360
+
361
+ return INT2FIX(http->nread);
362
+ }
363
+
364
+ void Init_http11()
365
+ {
366
+
367
+ mMongrel = rb_define_module("Mongrel");
368
+
369
+ DEF_GLOBAL(http_prefix, "HTTP_");
370
+ DEF_GLOBAL(request_method, "REQUEST_METHOD");
371
+ DEF_GLOBAL(request_uri, "REQUEST_URI");
372
+ DEF_GLOBAL(fragment, "FRAGMENT");
373
+ DEF_GLOBAL(query_string, "QUERY_STRING");
374
+ DEF_GLOBAL(http_version, "HTTP_VERSION");
375
+ DEF_GLOBAL(request_path, "REQUEST_PATH");
376
+ DEF_GLOBAL(content_length, "CONTENT_LENGTH");
377
+ DEF_GLOBAL(http_content_length, "HTTP_CONTENT_LENGTH");
378
+ DEF_GLOBAL(content_type, "CONTENT_TYPE");
379
+ DEF_GLOBAL(http_content_type, "HTTP_CONTENT_TYPE");
380
+ DEF_GLOBAL(gateway_interface, "GATEWAY_INTERFACE");
381
+ DEF_GLOBAL(gateway_interface_value, "CGI/1.2");
382
+ DEF_GLOBAL(server_name, "SERVER_NAME");
383
+ DEF_GLOBAL(server_port, "SERVER_PORT");
384
+ DEF_GLOBAL(server_protocol, "SERVER_PROTOCOL");
385
+ DEF_GLOBAL(server_protocol_value, "HTTP/1.1");
386
+ DEF_GLOBAL(http_host, "HTTP_HOST");
387
+ DEF_GLOBAL(mongrel_version, "Mongrel 1.1.5"); /* XXX Why is this defined here? */
388
+ DEF_GLOBAL(server_software, "SERVER_SOFTWARE");
389
+ DEF_GLOBAL(port_80, "80");
390
+
391
+ eHttpParserError = rb_define_class_under(mMongrel, "HttpParserError", rb_eIOError);
392
+
393
+ cHttpParser = rb_define_class_under(mMongrel, "HttpParser", rb_cObject);
394
+ rb_define_alloc_func(cHttpParser, HttpParser_alloc);
395
+ rb_define_method(cHttpParser, "initialize", HttpParser_init,0);
396
+ rb_define_method(cHttpParser, "reset", HttpParser_reset,0);
397
+ rb_define_method(cHttpParser, "finish", HttpParser_finish,0);
398
+ rb_define_method(cHttpParser, "execute", HttpParser_execute,3);
399
+ rb_define_method(cHttpParser, "error?", HttpParser_has_error,0);
400
+ rb_define_method(cHttpParser, "finished?", HttpParser_is_finished,0);
401
+ rb_define_method(cHttpParser, "nread", HttpParser_nread,0);
402
+ }