freels-mongrel 1.1.2

Sign up to get free protection for your applications and to get access to all the features.
Files changed (72) hide show
  1. data/CHANGELOG +12 -0
  2. data/COPYING +55 -0
  3. data/LICENSE +55 -0
  4. data/Manifest +70 -0
  5. data/README +74 -0
  6. data/Rakefile +237 -0
  7. data/TODO +4 -0
  8. data/bin/mongrel_rails +284 -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 +15 -0
  23. data/ext/http11/extconf.rb +6 -0
  24. data/ext/http11/http11.c +527 -0
  25. data/ext/http11/http11_parser.c +1216 -0
  26. data/ext/http11/http11_parser.h +49 -0
  27. data/ext/http11/http11_parser.java.rl +171 -0
  28. data/ext/http11/http11_parser.rl +165 -0
  29. data/ext/http11/http11_parser_common.rl +55 -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/mongrel.rb +359 -0
  34. data/lib/mongrel/camping.rb +107 -0
  35. data/lib/mongrel/cgi.rb +182 -0
  36. data/lib/mongrel/command.rb +220 -0
  37. data/lib/mongrel/configurator.rb +389 -0
  38. data/lib/mongrel/const.rb +114 -0
  39. data/lib/mongrel/debug.rb +203 -0
  40. data/lib/mongrel/gems.rb +22 -0
  41. data/lib/mongrel/handlers.rb +472 -0
  42. data/lib/mongrel/header_out.rb +28 -0
  43. data/lib/mongrel/http_request.rb +155 -0
  44. data/lib/mongrel/http_response.rb +163 -0
  45. data/lib/mongrel/init.rb +10 -0
  46. data/lib/mongrel/logger.rb +74 -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 +47 -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 +104 -0
  63. data/test/test_http11.rb +272 -0
  64. data/test/test_redirect_handler.rb +44 -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 +116 -0
  70. data/test/testhelp.rb +74 -0
  71. data/tools/trickletest.rb +45 -0
  72. metadata +202 -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,15 @@
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
+ #define ARRAY_SIZE(x) (sizeof(x)/sizeof(x[0]))
8
+
9
+ #ifdef DEBUG
10
+ #define TRACE() fprintf(stderr, "> %s:%d:%s\n", __FILE__, __LINE__, __FUNCTION__)
11
+ #else
12
+ #define TRACE()
13
+ #endif
14
+
15
+ #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,527 @@
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
+
11
+ #ifndef RSTRING_PTR
12
+ #define RSTRING_PTR(s) (RSTRING(s))
13
+ #endif
14
+ #ifndef RSTRING_LEN
15
+ #define RSTRING_LEN(s) (RSTRING(s))
16
+ #endif
17
+
18
+ static VALUE mMongrel;
19
+ static VALUE cHttpParser;
20
+ static VALUE eHttpParserError;
21
+
22
+ #define id_handler_map rb_intern("@handler_map")
23
+ #define id_http_body rb_intern("@http_body")
24
+ #define HTTP_PREFIX "HTTP_"
25
+ #define HTTP_PREFIX_LEN (sizeof(HTTP_PREFIX) - 1)
26
+
27
+ static VALUE global_request_method;
28
+ static VALUE global_request_uri;
29
+ static VALUE global_fragment;
30
+ static VALUE global_query_string;
31
+ static VALUE global_http_version;
32
+ static VALUE global_content_length;
33
+ static VALUE global_http_content_length;
34
+ static VALUE global_request_path;
35
+ static VALUE global_content_type;
36
+ static VALUE global_http_content_type;
37
+ static VALUE global_gateway_interface;
38
+ static VALUE global_gateway_interface_value;
39
+ static VALUE global_server_name;
40
+ static VALUE global_server_port;
41
+ static VALUE global_server_protocol;
42
+ static VALUE global_server_protocol_value;
43
+ static VALUE global_http_host;
44
+ static VALUE global_mongrel_version;
45
+ static VALUE global_server_software;
46
+ static VALUE global_port_80;
47
+
48
+ #define TRIE_INCREASE 30
49
+
50
+ /** Defines common length and error messages for input length validation. */
51
+ #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."
52
+
53
+ /** Validates the max length of given input and throws an HttpParserError exception if over. */
54
+ #define VALIDATE_MAX_LENGTH(len, N) if(len > MAX_##N##_LENGTH) { rb_raise(eHttpParserError, MAX_##N##_LENGTH_ERR); }
55
+
56
+ /** Defines global strings in the init method. */
57
+ #define DEF_GLOBAL(N, val) global_##N = rb_obj_freeze(rb_str_new2(val)); rb_global_variable(&global_##N)
58
+
59
+
60
+ /* Defines the maximum allowed lengths for various input elements.*/
61
+ DEF_MAX_LENGTH(FIELD_NAME, 256);
62
+ DEF_MAX_LENGTH(FIELD_VALUE, 80 * 1024);
63
+ DEF_MAX_LENGTH(REQUEST_URI, 1024 * 12);
64
+ DEF_MAX_LENGTH(FRAGMENT, 1024); /* Don't know if this length is specified somewhere or not */
65
+ DEF_MAX_LENGTH(REQUEST_PATH, 1024);
66
+ DEF_MAX_LENGTH(QUERY_STRING, (1024 * 10));
67
+ DEF_MAX_LENGTH(HEADER, (1024 * (80 + 32)));
68
+
69
+ struct common_field {
70
+ const signed long len;
71
+ const char *name;
72
+ VALUE value;
73
+ };
74
+
75
+ /*
76
+ * A list of common HTTP headers we expect to receive.
77
+ * This allows us to avoid repeatedly creating identical string
78
+ * objects to be used with rb_hash_aset().
79
+ */
80
+ static struct common_field common_http_fields[] = {
81
+ # define f(N) { (sizeof(N) - 1), N, Qnil }
82
+ f("ACCEPT"),
83
+ f("ACCEPT_CHARSET"),
84
+ f("ACCEPT_ENCODING"),
85
+ f("ACCEPT_LANGUAGE"),
86
+ f("ALLOW"),
87
+ f("AUTHORIZATION"),
88
+ f("CACHE_CONTROL"),
89
+ f("CONNECTION"),
90
+ f("CONTENT_ENCODING"),
91
+ f("CONTENT_LENGTH"),
92
+ f("CONTENT_TYPE"),
93
+ f("COOKIE"),
94
+ f("DATE"),
95
+ f("EXPECT"),
96
+ f("FROM"),
97
+ f("HOST"),
98
+ f("IF_MATCH"),
99
+ f("IF_MODIFIED_SINCE"),
100
+ f("IF_NONE_MATCH"),
101
+ f("IF_RANGE"),
102
+ f("IF_UNMODIFIED_SINCE"),
103
+ f("KEEP_ALIVE"), /* Firefox sends this */
104
+ f("MAX_FORWARDS"),
105
+ f("PRAGMA"),
106
+ f("PROXY_AUTHORIZATION"),
107
+ f("RANGE"),
108
+ f("REFERER"),
109
+ f("TE"),
110
+ f("TRAILER"),
111
+ f("TRANSFER_ENCODING"),
112
+ f("UPGRADE"),
113
+ f("USER_AGENT"),
114
+ f("VIA"),
115
+ f("X_FORWARDED_FOR"), /* common for proxies */
116
+ f("X_REAL_IP"), /* common for proxies */
117
+ f("WARNING")
118
+ # undef f
119
+ };
120
+
121
+ /*
122
+ * qsort(3) and bsearch(3) improve average performance slightly, but may
123
+ * not be worth it for lack of portability to certain platforms...
124
+ */
125
+ #if defined(HAVE_QSORT_BSEARCH)
126
+ /* sort by length, then by name if there's a tie */
127
+ static int common_field_cmp(const void *a, const void *b)
128
+ {
129
+ struct common_field *cfa = (struct common_field *)a;
130
+ struct common_field *cfb = (struct common_field *)b;
131
+ signed long diff = cfa->len - cfb->len;
132
+ return diff ? diff : memcmp(cfa->name, cfb->name, cfa->len);
133
+ }
134
+ #endif /* HAVE_QSORT_BSEARCH */
135
+
136
+ static void init_common_fields(void)
137
+ {
138
+ int i;
139
+ struct common_field *cf = common_http_fields;
140
+ char tmp[256]; /* MAX_FIELD_NAME_LENGTH */
141
+ memcpy(tmp, HTTP_PREFIX, HTTP_PREFIX_LEN);
142
+
143
+ for(i = 0; i < ARRAY_SIZE(common_http_fields); cf++, i++) {
144
+ memcpy(tmp + HTTP_PREFIX_LEN, cf->name, cf->len + 1);
145
+ cf->value = rb_obj_freeze(rb_str_new(tmp, HTTP_PREFIX_LEN + cf->len));
146
+ rb_global_variable(&cf->value);
147
+ }
148
+
149
+ #if defined(HAVE_QSORT_BSEARCH)
150
+ qsort(common_http_fields,
151
+ ARRAY_SIZE(common_http_fields),
152
+ sizeof(struct common_field),
153
+ common_field_cmp);
154
+ #endif /* HAVE_QSORT_BSEARCH */
155
+ }
156
+
157
+ static VALUE find_common_field_value(const char *field, size_t flen)
158
+ {
159
+ #if defined(HAVE_QSORT_BSEARCH)
160
+ struct common_field key;
161
+ struct common_field *found;
162
+ key.name = field;
163
+ key.len = (signed long)flen;
164
+ found = (struct common_field *)bsearch(&key, common_http_fields,
165
+ ARRAY_SIZE(common_http_fields),
166
+ sizeof(struct common_field),
167
+ common_field_cmp);
168
+ return found ? found->value : Qnil;
169
+ #else /* !HAVE_QSORT_BSEARCH */
170
+ int i;
171
+ struct common_field *cf = common_http_fields;
172
+ for(i = 0; i < ARRAY_SIZE(common_http_fields); i++, cf++) {
173
+ if (cf->len == flen && !memcmp(cf->name, field, flen))
174
+ return cf->value;
175
+ }
176
+ return Qnil;
177
+ #endif /* !HAVE_QSORT_BSEARCH */
178
+ }
179
+
180
+ void http_field(void *data, const char *field, size_t flen, const char *value, size_t vlen)
181
+ {
182
+ VALUE req = (VALUE)data;
183
+ VALUE v = Qnil;
184
+ VALUE f = Qnil;
185
+
186
+ VALIDATE_MAX_LENGTH(flen, FIELD_NAME);
187
+ VALIDATE_MAX_LENGTH(vlen, FIELD_VALUE);
188
+
189
+ v = rb_str_new(value, vlen);
190
+
191
+ f = find_common_field_value(field, flen);
192
+
193
+ if (f == Qnil) {
194
+ /*
195
+ * We got a strange header that we don't have a memoized value for.
196
+ * Fallback to creating a new string to use as a hash key.
197
+ *
198
+ * using rb_str_new(NULL, len) here is faster than rb_str_buf_new(len)
199
+ * in my testing, because: there's no minimum allocation length (and
200
+ * no check for it, either), RSTRING_LEN(f) does not need to be
201
+ * written twice, and and RSTRING_PTR(f) will already be
202
+ * null-terminated for us.
203
+ */
204
+ f = rb_str_new(NULL, HTTP_PREFIX_LEN + flen);
205
+ memcpy(RSTRING_PTR(f), HTTP_PREFIX, HTTP_PREFIX_LEN);
206
+ memcpy(RSTRING_PTR(f) + HTTP_PREFIX_LEN, field, flen);
207
+ assert(*(RSTRING_PTR(f) + RSTRING_LEN(f)) == '\0'); /* paranoia */
208
+ /* fprintf(stderr, "UNKNOWN HEADER <%s>\n", RSTRING_PTR(f)); */
209
+ }
210
+
211
+ rb_hash_aset(req, f, v);
212
+ }
213
+
214
+ void request_method(void *data, const char *at, size_t length)
215
+ {
216
+ VALUE req = (VALUE)data;
217
+ VALUE val = Qnil;
218
+
219
+ val = rb_str_new(at, length);
220
+ rb_hash_aset(req, global_request_method, val);
221
+ }
222
+
223
+ void request_uri(void *data, const char *at, size_t length)
224
+ {
225
+ VALUE req = (VALUE)data;
226
+ VALUE val = Qnil;
227
+
228
+ VALIDATE_MAX_LENGTH(length, REQUEST_URI);
229
+
230
+ val = rb_str_new(at, length);
231
+ rb_hash_aset(req, global_request_uri, val);
232
+ }
233
+
234
+ void fragment(void *data, const char *at, size_t length)
235
+ {
236
+ VALUE req = (VALUE)data;
237
+ VALUE val = Qnil;
238
+
239
+ VALIDATE_MAX_LENGTH(length, FRAGMENT);
240
+
241
+ val = rb_str_new(at, length);
242
+ rb_hash_aset(req, global_fragment, val);
243
+ }
244
+
245
+ void request_path(void *data, const char *at, size_t length)
246
+ {
247
+ VALUE req = (VALUE)data;
248
+ VALUE val = Qnil;
249
+
250
+ VALIDATE_MAX_LENGTH(length, REQUEST_PATH);
251
+
252
+ val = rb_str_new(at, length);
253
+ rb_hash_aset(req, global_request_path, val);
254
+ }
255
+
256
+ void query_string(void *data, const char *at, size_t length)
257
+ {
258
+ VALUE req = (VALUE)data;
259
+ VALUE val = Qnil;
260
+
261
+ VALIDATE_MAX_LENGTH(length, QUERY_STRING);
262
+
263
+ val = rb_str_new(at, length);
264
+ rb_hash_aset(req, global_query_string, val);
265
+ }
266
+
267
+ void http_version(void *data, const char *at, size_t length)
268
+ {
269
+ VALUE req = (VALUE)data;
270
+ VALUE val = rb_str_new(at, length);
271
+ rb_hash_aset(req, global_http_version, val);
272
+ }
273
+
274
+ /** Finalizes the request header to have a bunch of stuff that's
275
+ needed. */
276
+
277
+ void header_done(void *data, const char *at, size_t length)
278
+ {
279
+ VALUE req = (VALUE)data;
280
+ VALUE temp = Qnil;
281
+ VALUE ctype = Qnil;
282
+ VALUE clen = Qnil;
283
+ char *colon = NULL;
284
+
285
+ clen = rb_hash_aref(req, global_http_content_length);
286
+ if(clen != Qnil) {
287
+ rb_hash_aset(req, global_content_length, clen);
288
+ }
289
+
290
+ ctype = rb_hash_aref(req, global_http_content_type);
291
+ if(ctype != Qnil) {
292
+ rb_hash_aset(req, global_content_type, ctype);
293
+ }
294
+
295
+ rb_hash_aset(req, global_gateway_interface, global_gateway_interface_value);
296
+ if((temp = rb_hash_aref(req, global_http_host)) != Qnil) {
297
+ colon = memchr(RSTRING_PTR(temp), ':', RSTRING_LEN(temp));
298
+ if(colon != NULL) {
299
+ rb_hash_aset(req, global_server_name, rb_str_substr(temp, 0, colon - RSTRING_PTR(temp)));
300
+ rb_hash_aset(req, global_server_port,
301
+ rb_str_substr(temp, colon - RSTRING_PTR(temp)+1,
302
+ RSTRING_LEN(temp)));
303
+ } else {
304
+ rb_hash_aset(req, global_server_name, temp);
305
+ rb_hash_aset(req, global_server_port, global_port_80);
306
+ }
307
+ }
308
+
309
+ /* grab the initial body and stuff it into an ivar */
310
+ rb_ivar_set(req, id_http_body, rb_str_new(at, length));
311
+ rb_hash_aset(req, global_server_protocol, global_server_protocol_value);
312
+ rb_hash_aset(req, global_server_software, global_mongrel_version);
313
+ }
314
+
315
+
316
+ void HttpParser_free(void *data) {
317
+ TRACE();
318
+
319
+ if(data) {
320
+ free(data);
321
+ }
322
+ }
323
+
324
+
325
+ VALUE HttpParser_alloc(VALUE klass)
326
+ {
327
+ VALUE obj;
328
+ http_parser *hp = ALLOC_N(http_parser, 1);
329
+ TRACE();
330
+ hp->http_field = http_field;
331
+ hp->request_method = request_method;
332
+ hp->request_uri = request_uri;
333
+ hp->fragment = fragment;
334
+ hp->request_path = request_path;
335
+ hp->query_string = query_string;
336
+ hp->http_version = http_version;
337
+ hp->header_done = header_done;
338
+ http_parser_init(hp);
339
+
340
+ obj = Data_Wrap_Struct(klass, NULL, HttpParser_free, hp);
341
+
342
+ return obj;
343
+ }
344
+
345
+
346
+ /**
347
+ * call-seq:
348
+ * parser.new -> parser
349
+ *
350
+ * Creates a new parser.
351
+ */
352
+ VALUE HttpParser_init(VALUE self)
353
+ {
354
+ http_parser *http = NULL;
355
+ DATA_GET(self, http_parser, http);
356
+ http_parser_init(http);
357
+
358
+ return self;
359
+ }
360
+
361
+
362
+ /**
363
+ * call-seq:
364
+ * parser.reset -> nil
365
+ *
366
+ * Resets the parser to it's initial state so that you can reuse it
367
+ * rather than making new ones.
368
+ */
369
+ VALUE HttpParser_reset(VALUE self)
370
+ {
371
+ http_parser *http = NULL;
372
+ DATA_GET(self, http_parser, http);
373
+ http_parser_init(http);
374
+
375
+ return Qnil;
376
+ }
377
+
378
+
379
+ /**
380
+ * call-seq:
381
+ * parser.finish -> true/false
382
+ *
383
+ * Finishes a parser early which could put in a "good" or bad state.
384
+ * You should call reset after finish it or bad things will happen.
385
+ */
386
+ VALUE HttpParser_finish(VALUE self)
387
+ {
388
+ http_parser *http = NULL;
389
+ DATA_GET(self, http_parser, http);
390
+ http_parser_finish(http);
391
+
392
+ return http_parser_is_finished(http) ? Qtrue : Qfalse;
393
+ }
394
+
395
+
396
+ /**
397
+ * call-seq:
398
+ * parser.execute(req_hash, data, start) -> Integer
399
+ *
400
+ * Takes a Hash and a String of data, parses the String of data filling in the Hash
401
+ * returning an Integer to indicate how much of the data has been read. No matter
402
+ * what the return value, you should call HttpParser#finished? and HttpParser#error?
403
+ * to figure out if it's done parsing or there was an error.
404
+ *
405
+ * This function now throws an exception when there is a parsing error. This makes
406
+ * the logic for working with the parser much easier. You can still test for an
407
+ * error, but now you need to wrap the parser with an exception handling block.
408
+ *
409
+ * The third argument allows for parsing a partial request and then continuing
410
+ * the parsing from that position. It needs all of the original data as well
411
+ * so you have to append to the data buffer as you read.
412
+ */
413
+ VALUE HttpParser_execute(VALUE self, VALUE req_hash, VALUE data, VALUE start)
414
+ {
415
+ http_parser *http = NULL;
416
+ int from = 0;
417
+ char *dptr = NULL;
418
+ long dlen = 0;
419
+
420
+ DATA_GET(self, http_parser, http);
421
+
422
+ from = FIX2INT(start);
423
+ dptr = RSTRING_PTR(data);
424
+ dlen = RSTRING_LEN(data);
425
+
426
+ if(from >= dlen) {
427
+ rb_raise(eHttpParserError, "Requested start is after data buffer end.");
428
+ } else {
429
+ http->data = (void *)req_hash;
430
+ http_parser_execute(http, dptr, dlen, from);
431
+
432
+ VALIDATE_MAX_LENGTH(http_parser_nread(http), HEADER);
433
+
434
+ if(http_parser_has_error(http)) {
435
+ rb_raise(eHttpParserError, "Invalid HTTP format, parsing fails.");
436
+ } else {
437
+ return INT2FIX(http_parser_nread(http));
438
+ }
439
+ }
440
+ }
441
+
442
+
443
+
444
+ /**
445
+ * call-seq:
446
+ * parser.error? -> true/false
447
+ *
448
+ * Tells you whether the parser is in an error state.
449
+ */
450
+ VALUE HttpParser_has_error(VALUE self)
451
+ {
452
+ http_parser *http = NULL;
453
+ DATA_GET(self, http_parser, http);
454
+
455
+ return http_parser_has_error(http) ? Qtrue : Qfalse;
456
+ }
457
+
458
+
459
+ /**
460
+ * call-seq:
461
+ * parser.finished? -> true/false
462
+ *
463
+ * Tells you whether the parser is finished or not and in a good state.
464
+ */
465
+ VALUE HttpParser_is_finished(VALUE self)
466
+ {
467
+ http_parser *http = NULL;
468
+ DATA_GET(self, http_parser, http);
469
+
470
+ return http_parser_is_finished(http) ? Qtrue : Qfalse;
471
+ }
472
+
473
+
474
+ /**
475
+ * call-seq:
476
+ * parser.nread -> Integer
477
+ *
478
+ * Returns the amount of data processed so far during this processing cycle. It is
479
+ * set to 0 on initialize or reset calls and is incremented each time execute is called.
480
+ */
481
+ VALUE HttpParser_nread(VALUE self)
482
+ {
483
+ http_parser *http = NULL;
484
+ DATA_GET(self, http_parser, http);
485
+
486
+ return INT2FIX(http->nread);
487
+ }
488
+
489
+ void Init_http11()
490
+ {
491
+
492
+ mMongrel = rb_define_module("Mongrel");
493
+
494
+ DEF_GLOBAL(request_method, "REQUEST_METHOD");
495
+ DEF_GLOBAL(request_uri, "REQUEST_URI");
496
+ DEF_GLOBAL(fragment, "FRAGMENT");
497
+ DEF_GLOBAL(query_string, "QUERY_STRING");
498
+ DEF_GLOBAL(http_version, "HTTP_VERSION");
499
+ DEF_GLOBAL(request_path, "REQUEST_PATH");
500
+ DEF_GLOBAL(content_length, "CONTENT_LENGTH");
501
+ DEF_GLOBAL(http_content_length, "HTTP_CONTENT_LENGTH");
502
+ DEF_GLOBAL(content_type, "CONTENT_TYPE");
503
+ DEF_GLOBAL(http_content_type, "HTTP_CONTENT_TYPE");
504
+ DEF_GLOBAL(gateway_interface, "GATEWAY_INTERFACE");
505
+ DEF_GLOBAL(gateway_interface_value, "CGI/1.2");
506
+ DEF_GLOBAL(server_name, "SERVER_NAME");
507
+ DEF_GLOBAL(server_port, "SERVER_PORT");
508
+ DEF_GLOBAL(server_protocol, "SERVER_PROTOCOL");
509
+ DEF_GLOBAL(server_protocol_value, "HTTP/1.1");
510
+ DEF_GLOBAL(http_host, "HTTP_HOST");
511
+ DEF_GLOBAL(mongrel_version, "Mongrel 1.2"); /* XXX Why is this defined here? */
512
+ DEF_GLOBAL(server_software, "SERVER_SOFTWARE");
513
+ DEF_GLOBAL(port_80, "80");
514
+
515
+ eHttpParserError = rb_define_class_under(mMongrel, "HttpParserError", rb_eIOError);
516
+
517
+ cHttpParser = rb_define_class_under(mMongrel, "HttpParser", rb_cObject);
518
+ rb_define_alloc_func(cHttpParser, HttpParser_alloc);
519
+ rb_define_method(cHttpParser, "initialize", HttpParser_init,0);
520
+ rb_define_method(cHttpParser, "reset", HttpParser_reset,0);
521
+ rb_define_method(cHttpParser, "finish", HttpParser_finish,0);
522
+ rb_define_method(cHttpParser, "execute", HttpParser_execute,3);
523
+ rb_define_method(cHttpParser, "error?", HttpParser_has_error,0);
524
+ rb_define_method(cHttpParser, "finished?", HttpParser_is_finished,0);
525
+ rb_define_method(cHttpParser, "nread", HttpParser_nread,0);
526
+ init_common_fields();
527
+ }