mongrel 0.3.11 → 0.3.12

Sign up to get free protection for your applications and to get access to all the features.
Files changed (199) hide show
  1. data/Rakefile +4 -2
  2. data/bin/mongrel_rails +65 -101
  3. data/bin/mongrel_rails_service +1 -1
  4. data/bin/mongrel_rails_svc +131 -115
  5. data/doc/rdoc/classes/Class.html +197 -0
  6. data/doc/rdoc/classes/Class.src/M000001.html +24 -0
  7. data/doc/rdoc/classes/Class.src/M000002.html +62 -0
  8. data/doc/rdoc/classes/Class.src/M000003.html +21 -0
  9. data/doc/rdoc/classes/Class.src/M000004.html +20 -0
  10. data/doc/rdoc/classes/IO.html +170 -0
  11. data/doc/rdoc/classes/IO.src/M000005.html +19 -0
  12. data/doc/rdoc/classes/IO.src/M000006.html +19 -0
  13. data/doc/rdoc/classes/Kernel.html +159 -0
  14. data/doc/rdoc/classes/Kernel.src/M000025.html +19 -0
  15. data/doc/rdoc/classes/Kernel.src/M000026.html +25 -0
  16. data/doc/rdoc/classes/Mongrel.html +181 -0
  17. data/doc/rdoc/classes/Mongrel/CGIWrapper.html +383 -0
  18. data/doc/rdoc/classes/Mongrel/CGIWrapper.src/M000090.html +24 -0
  19. data/doc/rdoc/classes/Mongrel/CGIWrapper.src/M000091.html +47 -0
  20. data/doc/rdoc/classes/Mongrel/CGIWrapper.src/M000092.html +34 -0
  21. data/doc/rdoc/classes/Mongrel/CGIWrapper.src/M000093.html +27 -0
  22. data/doc/rdoc/classes/Mongrel/CGIWrapper.src/M000094.html +25 -0
  23. data/doc/rdoc/classes/Mongrel/CGIWrapper.src/M000095.html +18 -0
  24. data/doc/rdoc/classes/Mongrel/CGIWrapper.src/M000096.html +18 -0
  25. data/doc/rdoc/classes/Mongrel/CGIWrapper.src/M000097.html +18 -0
  26. data/doc/rdoc/classes/Mongrel/CGIWrapper.src/M000098.html +19 -0
  27. data/doc/rdoc/classes/Mongrel/Camping.html +177 -0
  28. data/doc/rdoc/classes/Mongrel/Camping.src/M000048.html +22 -0
  29. data/doc/rdoc/classes/Mongrel/Camping/CampingHandler.html +165 -0
  30. data/doc/rdoc/classes/Mongrel/Camping/CampingHandler.src/M000049.html +18 -0
  31. data/doc/rdoc/classes/Mongrel/Camping/CampingHandler.src/M000050.html +27 -0
  32. data/doc/rdoc/classes/Mongrel/Command.html +119 -0
  33. data/doc/rdoc/classes/Mongrel/Command/Base.html +337 -0
  34. data/doc/rdoc/classes/Mongrel/Command/Base.src/M000029.html +24 -0
  35. data/doc/rdoc/classes/Mongrel/Command/Base.src/M000030.html +41 -0
  36. data/doc/rdoc/classes/Mongrel/Command/Base.src/M000031.html +18 -0
  37. data/doc/rdoc/classes/Mongrel/Command/Base.src/M000032.html +18 -0
  38. data/doc/rdoc/classes/Mongrel/Command/Base.src/M000033.html +18 -0
  39. data/doc/rdoc/classes/Mongrel/Command/Base.src/M000034.html +22 -0
  40. data/doc/rdoc/classes/Mongrel/Command/Base.src/M000035.html +18 -0
  41. data/doc/rdoc/classes/Mongrel/Command/Base.src/M000036.html +18 -0
  42. data/doc/rdoc/classes/Mongrel/Command/Base.src/M000037.html +18 -0
  43. data/doc/rdoc/classes/Mongrel/Command/Base.src/M000038.html +18 -0
  44. data/doc/rdoc/classes/Mongrel/Command/Registry.html +192 -0
  45. data/doc/rdoc/classes/Mongrel/Command/Registry.src/M000039.html +20 -0
  46. data/doc/rdoc/classes/Mongrel/Command/Registry.src/M000040.html +25 -0
  47. data/doc/rdoc/classes/Mongrel/Command/Registry.src/M000041.html +46 -0
  48. data/doc/rdoc/classes/Mongrel/Configurator.html +563 -0
  49. data/doc/rdoc/classes/Mongrel/Configurator.src/M000099.html +24 -0
  50. data/doc/rdoc/classes/Mongrel/Configurator.src/M000100.html +23 -0
  51. data/doc/rdoc/classes/Mongrel/Configurator.src/M000101.html +18 -0
  52. data/doc/rdoc/classes/Mongrel/Configurator.src/M000102.html +32 -0
  53. data/doc/rdoc/classes/Mongrel/Configurator.src/M000103.html +19 -0
  54. data/doc/rdoc/classes/Mongrel/Configurator.src/M000104.html +31 -0
  55. data/doc/rdoc/classes/Mongrel/Configurator.src/M000105.html +33 -0
  56. data/doc/rdoc/classes/Mongrel/Configurator.src/M000106.html +18 -0
  57. data/doc/rdoc/classes/Mongrel/Configurator.src/M000107.html +25 -0
  58. data/doc/rdoc/classes/Mongrel/Configurator.src/M000108.html +19 -0
  59. data/doc/rdoc/classes/Mongrel/Configurator.src/M000109.html +22 -0
  60. data/doc/rdoc/classes/Mongrel/Configurator.src/M000110.html +21 -0
  61. data/doc/rdoc/classes/Mongrel/Configurator.src/M000111.html +18 -0
  62. data/doc/rdoc/classes/Mongrel/Configurator.src/M000112.html +27 -0
  63. data/doc/rdoc/classes/Mongrel/Configurator.src/M000113.html +46 -0
  64. data/doc/rdoc/classes/Mongrel/Configurator.src/M000114.html +18 -0
  65. data/doc/rdoc/classes/Mongrel/Const.html +286 -0
  66. data/doc/rdoc/classes/Mongrel/DirHandler.html +288 -0
  67. data/doc/rdoc/classes/Mongrel/DirHandler.src/M000058.html +20 -0
  68. data/doc/rdoc/classes/Mongrel/DirHandler.src/M000059.html +42 -0
  69. data/doc/rdoc/classes/Mongrel/DirHandler.src/M000060.html +40 -0
  70. data/doc/rdoc/classes/Mongrel/DirHandler.src/M000061.html +51 -0
  71. data/doc/rdoc/classes/Mongrel/DirHandler.src/M000062.html +40 -0
  72. data/doc/rdoc/classes/Mongrel/DirHandler.src/M000063.html +18 -0
  73. data/doc/rdoc/classes/Mongrel/Error404Handler.html +171 -0
  74. data/doc/rdoc/classes/Mongrel/Error404Handler.src/M000115.html +18 -0
  75. data/doc/rdoc/classes/Mongrel/Error404Handler.src/M000116.html +18 -0
  76. data/doc/rdoc/classes/Mongrel/HeaderOut.html +185 -0
  77. data/doc/rdoc/classes/Mongrel/HeaderOut.src/M000072.html +18 -0
  78. data/doc/rdoc/classes/Mongrel/HeaderOut.src/M000073.html +18 -0
  79. data/doc/rdoc/classes/Mongrel/HttpHandler.html +152 -0
  80. data/doc/rdoc/classes/Mongrel/HttpHandler.src/M000074.html +17 -0
  81. data/doc/rdoc/classes/Mongrel/HttpHandlerPlugin.html +169 -0
  82. data/doc/rdoc/classes/Mongrel/HttpHandlerPlugin.src/M000027.html +18 -0
  83. data/doc/rdoc/classes/Mongrel/HttpHandlerPlugin.src/M000028.html +17 -0
  84. data/doc/rdoc/classes/Mongrel/HttpParser.html +271 -0
  85. data/doc/rdoc/classes/Mongrel/HttpParser.src/M000051.html +28 -0
  86. data/doc/rdoc/classes/Mongrel/HttpParser.src/M000052.html +29 -0
  87. data/doc/rdoc/classes/Mongrel/HttpParser.src/M000053.html +29 -0
  88. data/doc/rdoc/classes/Mongrel/HttpParser.src/M000054.html +41 -0
  89. data/doc/rdoc/classes/Mongrel/HttpParser.src/M000055.html +27 -0
  90. data/doc/rdoc/classes/Mongrel/HttpParser.src/M000056.html +27 -0
  91. data/doc/rdoc/classes/Mongrel/HttpParser.src/M000057.html +28 -0
  92. data/doc/rdoc/classes/Mongrel/HttpRequest.html +222 -0
  93. data/doc/rdoc/classes/Mongrel/HttpRequest.src/M000117.html +28 -0
  94. data/doc/rdoc/classes/Mongrel/HttpRequest.src/M000118.html +20 -0
  95. data/doc/rdoc/classes/Mongrel/HttpRequest.src/M000119.html +20 -0
  96. data/doc/rdoc/classes/Mongrel/HttpRequest.src/M000120.html +32 -0
  97. data/doc/rdoc/classes/Mongrel/HttpResponse.html +371 -0
  98. data/doc/rdoc/classes/Mongrel/HttpResponse.src/M000075.html +26 -0
  99. data/doc/rdoc/classes/Mongrel/HttpResponse.src/M000076.html +20 -0
  100. data/doc/rdoc/classes/Mongrel/HttpResponse.src/M000077.html +25 -0
  101. data/doc/rdoc/classes/Mongrel/HttpResponse.src/M000078.html +22 -0
  102. data/doc/rdoc/classes/Mongrel/HttpResponse.src/M000079.html +22 -0
  103. data/doc/rdoc/classes/Mongrel/HttpResponse.src/M000080.html +23 -0
  104. data/doc/rdoc/classes/Mongrel/HttpResponse.src/M000081.html +18 -0
  105. data/doc/rdoc/classes/Mongrel/HttpResponse.src/M000082.html +20 -0
  106. data/doc/rdoc/classes/Mongrel/HttpResponse.src/M000083.html +18 -0
  107. data/doc/rdoc/classes/Mongrel/HttpServer.html +360 -0
  108. data/doc/rdoc/classes/Mongrel/HttpServer.src/M000064.html +24 -0
  109. data/doc/rdoc/classes/Mongrel/HttpServer.src/M000065.html +65 -0
  110. data/doc/rdoc/classes/Mongrel/HttpServer.src/M000066.html +24 -0
  111. data/doc/rdoc/classes/Mongrel/HttpServer.src/M000067.html +60 -0
  112. data/doc/rdoc/classes/Mongrel/HttpServer.src/M000068.html +28 -0
  113. data/doc/rdoc/classes/Mongrel/HttpServer.src/M000069.html +18 -0
  114. data/doc/rdoc/classes/Mongrel/HttpServer.src/M000070.html +22 -0
  115. data/doc/rdoc/classes/Mongrel/HttpServer.src/M000071.html +18 -0
  116. data/doc/rdoc/classes/Mongrel/Rails.html +112 -0
  117. data/doc/rdoc/classes/Mongrel/Rails/RailsConfigurator.html +223 -0
  118. data/doc/rdoc/classes/Mongrel/Rails/RailsConfigurator.src/M000042.html +35 -0
  119. data/doc/rdoc/classes/Mongrel/Rails/RailsConfigurator.src/M000043.html +25 -0
  120. data/doc/rdoc/classes/Mongrel/Rails/RailsConfigurator.src/M000044.html +32 -0
  121. data/doc/rdoc/classes/Mongrel/Rails/RailsHandler.html +250 -0
  122. data/doc/rdoc/classes/Mongrel/Rails/RailsHandler.src/M000045.html +22 -0
  123. data/doc/rdoc/classes/Mongrel/Rails/RailsHandler.src/M000046.html +48 -0
  124. data/doc/rdoc/classes/Mongrel/Rails/RailsHandler.src/M000047.html +23 -0
  125. data/doc/rdoc/classes/Mongrel/StopServer.html +117 -0
  126. data/doc/rdoc/classes/Mongrel/URIClassifier.html +301 -0
  127. data/doc/rdoc/classes/Mongrel/URIClassifier.src/M000084.html +18 -0
  128. data/doc/rdoc/classes/Mongrel/URIClassifier.src/M000085.html +18 -0
  129. data/doc/rdoc/classes/Mongrel/URIClassifier.src/M000086.html +39 -0
  130. data/doc/rdoc/classes/Mongrel/URIClassifier.src/M000087.html +51 -0
  131. data/doc/rdoc/classes/Mongrel/URIClassifier.src/M000088.html +36 -0
  132. data/doc/rdoc/classes/Mongrel/URIClassifier.src/M000089.html +83 -0
  133. data/doc/rdoc/classes/MongrelDbg.html +209 -0
  134. data/doc/rdoc/classes/MongrelDbg.src/M000020.html +19 -0
  135. data/doc/rdoc/classes/MongrelDbg.src/M000021.html +20 -0
  136. data/doc/rdoc/classes/MongrelDbg.src/M000022.html +22 -0
  137. data/doc/rdoc/classes/MongrelDbg.src/M000023.html +21 -0
  138. data/doc/rdoc/classes/MongrelDbg.src/M000024.html +18 -0
  139. data/doc/rdoc/classes/ObjectTracker.html +176 -0
  140. data/doc/rdoc/classes/ObjectTracker.src/M000016.html +22 -0
  141. data/doc/rdoc/classes/ObjectTracker.src/M000017.html +18 -0
  142. data/doc/rdoc/classes/ObjectTracker.src/M000018.html +18 -0
  143. data/doc/rdoc/classes/ObjectTracker.src/M000019.html +46 -0
  144. data/doc/rdoc/classes/RequestLog.html +113 -0
  145. data/doc/rdoc/classes/RequestLog/Files.html +144 -0
  146. data/doc/rdoc/classes/RequestLog/Files.src/M000121.html +19 -0
  147. data/doc/rdoc/classes/RequestLog/Objects.html +144 -0
  148. data/doc/rdoc/classes/RequestLog/Objects.src/M000122.html +19 -0
  149. data/doc/rdoc/classes/RequestLog/Params.html +144 -0
  150. data/doc/rdoc/classes/RequestLog/Params.src/M000123.html +19 -0
  151. data/doc/rdoc/classes/Stats.html +306 -0
  152. data/doc/rdoc/classes/Stats.src/M000009.html +19 -0
  153. data/doc/rdoc/classes/Stats.src/M000010.html +23 -0
  154. data/doc/rdoc/classes/Stats.src/M000011.html +26 -0
  155. data/doc/rdoc/classes/Stats.src/M000012.html +18 -0
  156. data/doc/rdoc/classes/Stats.src/M000013.html +18 -0
  157. data/doc/rdoc/classes/Stats.src/M000014.html +19 -0
  158. data/doc/rdoc/classes/Stats.src/M000015.html +20 -0
  159. data/doc/rdoc/classes/TCPServer.html +173 -0
  160. data/doc/rdoc/classes/TCPServer.src/M000007.html +19 -0
  161. data/doc/rdoc/created.rid +1 -0
  162. data/doc/rdoc/files/COPYING.html +756 -0
  163. data/doc/rdoc/files/LICENSE.html +756 -0
  164. data/doc/rdoc/files/README.html +302 -0
  165. data/doc/rdoc/files/ext/http11/http11_c.html +101 -0
  166. data/doc/rdoc/files/lib/mongrel/camping_rb.html +108 -0
  167. data/doc/rdoc/files/lib/mongrel/cgi_rb.html +108 -0
  168. data/doc/rdoc/files/lib/mongrel/command_rb.html +111 -0
  169. data/doc/rdoc/files/lib/mongrel/debug_rb.html +110 -0
  170. data/doc/rdoc/files/lib/mongrel/handlers_rb.html +109 -0
  171. data/doc/rdoc/files/lib/mongrel/init_rb.html +109 -0
  172. data/doc/rdoc/files/lib/mongrel/rails_rb.html +111 -0
  173. data/doc/rdoc/files/lib/mongrel/stats_rb.html +117 -0
  174. data/doc/rdoc/files/lib/mongrel/tcphack_rb.html +109 -0
  175. data/doc/rdoc/files/lib/mongrel_rb.html +118 -0
  176. data/doc/rdoc/fr_class_index.html +60 -0
  177. data/doc/rdoc/fr_file_index.html +40 -0
  178. data/doc/rdoc/fr_method_index.html +149 -0
  179. data/doc/rdoc/index.html +24 -0
  180. data/doc/rdoc/rdoc-style.css +208 -0
  181. data/examples/builder.rb +29 -0
  182. data/examples/camping/blog.rb +11 -2
  183. data/examples/simpletest.rb +20 -7
  184. data/ext/http11/http11.c +71 -14
  185. data/ext/http11/http11_parser.c +27 -24
  186. data/ext/http11/http11_parser.h +2 -1
  187. data/lib/http11.bundle +0 -0
  188. data/lib/mongrel.rb +498 -135
  189. data/lib/mongrel/command.rb +1 -1
  190. data/lib/mongrel/debug.rb +259 -0
  191. data/lib/mongrel/handlers.rb +76 -22
  192. data/lib/mongrel/rails.rb +165 -72
  193. data/lib/mongrel/stats.rb +71 -0
  194. data/test/test_configurator.rb +67 -0
  195. data/test/test_debug.rb +31 -0
  196. data/test/test_http11.rb +16 -0
  197. data/test/test_response.rb +5 -0
  198. data/test/test_stats.rb +28 -0
  199. metadata +224 -2
@@ -0,0 +1,29 @@
1
+ require 'mongrel'
2
+
3
+ class TestPlugin < GemPlugin::Plugin "/handlers"
4
+ include Mongrel::HttpHandlerPlugin
5
+
6
+ def process(request, response)
7
+ STDERR.puts "My options are: #{options.inspect}"
8
+ STDERR.puts "Request Was:"
9
+ STDERR.puts request.params.to_yaml
10
+ end
11
+ end
12
+
13
+ config = Mongrel::Configurator.new :host => "127.0.0.1" do
14
+ load_plugins :includes => ["mongrel"], :excludes => ["rails"]
15
+ daemonize :cwd => Dir.pwd, :log_file => "mongrel.log", :pid_file => "mongrel.pid"
16
+
17
+ listener :port => 3000 do
18
+ uri "/app", :handler => plugin("/handlers/testplugin", :test => "that")
19
+ uri "/app", :handler => Mongrel::DirHandler.new(".")
20
+ load_plugins :includes => ["mongrel", "rails"]
21
+ end
22
+
23
+ trap("INT") { stop }
24
+ run
25
+ end
26
+
27
+ config.join
28
+
29
+
@@ -277,9 +277,18 @@ if __FILE__ == $0
277
277
  Blog::Models::Base.logger = Logger.new('camping.log')
278
278
  Blog::Models::Base.threaded_connections=false
279
279
  Blog.create
280
+
281
+ # Use the Configurator as an example rather than Mongrel::Camping.start
282
+ config = Mongrel::Configurator.new :host => "0.0.0.0" do
283
+ listener :port => 3002 do
284
+ uri "/blog", :handler => CampingHandler.new(Blog)
285
+ uri "/favicon", :handler => Mongrel::Error404Handler.new("")
286
+ trap("INT") { stop }
287
+ run
288
+ end
289
+ end
280
290
 
281
- server = Mongrel::Camping::start("0.0.0.0",3002,"/blog",Blog)
282
291
  puts "** Blog example is running at http://localhost:3002/blog"
283
292
  puts "** Default username is `admin', password is `camping'"
284
- server.acceptor.join
293
+ config.join
285
294
  end
@@ -19,18 +19,31 @@ class SimpleHandler < Mongrel::HttpHandler
19
19
  end
20
20
  end
21
21
 
22
+ class DumbHandler < Mongrel::HttpHandler
23
+ def process(request, response)
24
+ response.start do |head,out|
25
+ head["Content-Type"] = "text/html"
26
+ out.write("test")
27
+ end
28
+ end
29
+ end
30
+
31
+
22
32
  if ARGV.length != 3
23
33
  STDERR.puts "usage: simpletest.rb <host> <port> <docroot>"
24
34
  exit(1)
25
35
  end
26
36
 
27
- h = Mongrel::HttpServer.new(ARGV[0], ARGV[1].to_i)
28
- h.register("/", SimpleHandler.new)
29
- h.register("/files", Mongrel::DirHandler.new(ARGV[2]))
30
- h.run
37
+ config = Mongrel::Configurator.new :host => ARGV[0], :port => ARGV[1] do
38
+ listener do
39
+ uri "/", :handler => SimpleHandler.new
40
+ uri "/dumb", :handler => DumbHandler.new
41
+ uri "/files", :handler => Mongrel::DirHandler.new(ARGV[2])
42
+ end
31
43
 
32
- trap("INT") { h.stop }
44
+ trap("INT") { stop }
45
+ run
46
+ end
33
47
 
34
48
  puts "Mongrel running on #{ARGV[0]}:#{ARGV[1]} with docroot #{ARGV[2]}"
35
-
36
- h.acceptor.join
49
+ config.join
@@ -16,7 +16,24 @@ static VALUE global_request_method;
16
16
  static VALUE global_request_uri;
17
17
  static VALUE global_query_string;
18
18
  static VALUE global_http_version;
19
-
19
+ static VALUE global_content_length;
20
+ static VALUE global_http_content_length;
21
+ static VALUE global_content_type;
22
+ static VALUE global_http_content_type;
23
+ static VALUE global_gateway_interface;
24
+ static VALUE global_gateway_interface_value;
25
+ static VALUE global_interface_value;
26
+ static VALUE global_remote_address;
27
+ static VALUE global_server_name;
28
+ static VALUE global_server_port;
29
+ static VALUE global_server_protocol;
30
+ static VALUE global_server_protocol_value;
31
+ static VALUE global_http_host;
32
+ static VALUE global_mongrel_version;
33
+ static VALUE global_server_software;
34
+ static VALUE global_port_80;
35
+
36
+ #define DEF_GLOBAL(name, val) global_##name = rb_obj_freeze(rb_str_new2(val)); rb_global_variable(&global_##name);
20
37
 
21
38
  void http_field(void *data, const char *field, size_t flen, const char *value, size_t vlen)
22
39
  {
@@ -67,8 +84,37 @@ void http_version(void *data, const char *at, size_t length)
67
84
  rb_hash_aset(req, global_http_version, val);
68
85
  }
69
86
 
87
+ /** Finalizes the request header to have a bunch of stuff that's
88
+ needed. */
70
89
 
71
-
90
+ void header_done(void *data, const char *at, size_t length)
91
+ {
92
+ VALUE req = (VALUE)data;
93
+ VALUE temp = Qnil;
94
+ VALUE host = Qnil;
95
+ VALUE port = Qnil;
96
+ char *colon = NULL;
97
+
98
+ rb_hash_aset(req, global_content_length, rb_hash_aref(req, global_http_content_length));
99
+ rb_hash_aset(req, global_content_type, rb_hash_aref(req, global_http_content_type));
100
+ rb_hash_aset(req, global_gateway_interface, global_gateway_interface_value);
101
+ if((temp = rb_hash_aref(req, global_http_host)) != Qnil) {
102
+ // ruby better close strings off with a '\0' dammit
103
+ colon = strchr(RSTRING(temp)->ptr, ':');
104
+ if(colon != NULL) {
105
+ rb_hash_aset(req, global_server_name, rb_str_substr(temp, 0, colon - RSTRING(temp)->ptr));
106
+ rb_hash_aset(req, global_server_port,
107
+ rb_str_substr(temp, colon - RSTRING(temp)->ptr+1,
108
+ RSTRING(temp)->len));
109
+ } else {
110
+ rb_hash_aset(req, global_server_name, temp);
111
+ rb_hash_aset(req, global_server_port, global_port_80);
112
+ }
113
+ }
114
+
115
+ rb_hash_aset(req, global_server_protocol, global_server_protocol_value);
116
+ rb_hash_aset(req, global_server_software, global_mongrel_version);
117
+ }
72
118
 
73
119
 
74
120
  void HttpParser_free(void *data) {
@@ -90,7 +136,8 @@ VALUE HttpParser_alloc(VALUE klass)
90
136
  hp->request_uri = request_uri;
91
137
  hp->query_string = query_string;
92
138
  hp->http_version = http_version;
93
-
139
+ hp->header_done = header_done;
140
+
94
141
  obj = Data_Wrap_Struct(klass, NULL, HttpParser_free, hp);
95
142
 
96
143
  return obj;
@@ -413,17 +460,27 @@ void Init_http11()
413
460
  mMongrel = rb_define_module("Mongrel");
414
461
  id_handler_map = rb_intern("@handler_map");
415
462
 
416
- global_http_prefix = rb_str_new2("HTTP_");
417
- rb_global_variable(&global_http_prefix);
418
- global_request_method = rb_str_new2("REQUEST_METHOD");
419
- rb_global_variable(&global_request_method);
420
- global_request_uri = rb_str_new2("REQUEST_URI");
421
- rb_global_variable(&global_request_uri);
422
- global_query_string = rb_str_new2("QUERY_STRING");
423
- rb_global_variable(&global_query_string);
424
- global_http_version = rb_str_new2("HTTP_VERSION");
425
- rb_global_variable(&global_http_version);
426
-
463
+ DEF_GLOBAL(http_prefix, "HTTP_");
464
+ DEF_GLOBAL(request_method, "REQUEST_METHOD");
465
+ DEF_GLOBAL(request_uri, "REQUEST_URI");
466
+ DEF_GLOBAL(query_string, "QUERY_STRING");
467
+ DEF_GLOBAL(http_version, "HTTP_VERSION");
468
+ DEF_GLOBAL(content_length, "CONTENT_LENGTH");
469
+ DEF_GLOBAL(http_content_length, "HTTP_CONTENT_LENGTH");
470
+ DEF_GLOBAL(content_type, "CONTENT_TYPE");
471
+ DEF_GLOBAL(http_content_type, "HTTP_CONTENT_TYPE");
472
+ DEF_GLOBAL(gateway_interface, "GATEWAY_INTERFACE");
473
+ DEF_GLOBAL(gateway_interface_value, "CGI/1.2");
474
+ DEF_GLOBAL(remote_address, "REMOTE_ADDR");
475
+ DEF_GLOBAL(server_name, "SERVER_NAME");
476
+ DEF_GLOBAL(server_port, "SERVER_PORT");
477
+ DEF_GLOBAL(server_protocol, "SERVER_PROTOCOL");
478
+ DEF_GLOBAL(server_protocol_value, "HTTP/1.1");
479
+ DEF_GLOBAL(http_host, "HTTP_HOST");
480
+ DEF_GLOBAL(mongrel_version, "Mongrel 0.3.12");
481
+ DEF_GLOBAL(server_software, "SERVER_SOFTWARE");
482
+ DEF_GLOBAL(port_80, "80");
483
+
427
484
  cHttpParser = rb_define_class_under(mMongrel, "HttpParser", rb_cObject);
428
485
  rb_define_alloc_func(cHttpParser, HttpParser_alloc);
429
486
  rb_define_method(cHttpParser, "initialize", HttpParser_init,0);
@@ -9,7 +9,7 @@
9
9
  #define MARK(S,F) assert((F) - (S)->mark >= 0); (S)->mark = (F);
10
10
 
11
11
  /** machine **/
12
- #line 98 "ext/http11/http11_parser.rl"
12
+ #line 101 "ext/http11/http11_parser.rl"
13
13
 
14
14
 
15
15
  /** Data **/
@@ -21,7 +21,7 @@ static int http_parser_first_final = 53;
21
21
 
22
22
  static int http_parser_error = 1;
23
23
 
24
- #line 102 "ext/http11/http11_parser.rl"
24
+ #line 105 "ext/http11/http11_parser.rl"
25
25
 
26
26
  int http_parser_init(http_parser *parser) {
27
27
  int cs = 0;
@@ -30,7 +30,7 @@ int http_parser_init(http_parser *parser) {
30
30
  {
31
31
  cs = http_parser_start;
32
32
  }
33
- #line 106 "ext/http11/http11_parser.rl"
33
+ #line 109 "ext/http11/http11_parser.rl"
34
34
  parser->cs = cs;
35
35
  parser->body_start = NULL;
36
36
  parser->content_len = 0;
@@ -305,14 +305,17 @@ case 21:
305
305
  tr40:
306
306
  #line 46 "ext/http11/http11_parser.rl"
307
307
  {
308
- parser->body_start = p+1; goto _out53;
308
+ parser->body_start = p+1;
309
+ if(parser->header_done != NULL)
310
+ parser->header_done(parser->data, p, 0);
311
+ goto _out53;
309
312
  }
310
313
  goto st53;
311
314
  st53:
312
315
  if ( ++p == pe )
313
316
  goto _out53;
314
317
  case 53:
315
- #line 316 "ext/http11/http11_parser.c"
318
+ #line 319 "ext/http11/http11_parser.c"
316
319
  goto st1;
317
320
  tr36:
318
321
  #line 16 "ext/http11/http11_parser.rl"
@@ -322,7 +325,7 @@ st22:
322
325
  if ( ++p == pe )
323
326
  goto _out22;
324
327
  case 22:
325
- #line 326 "ext/http11/http11_parser.c"
328
+ #line 329 "ext/http11/http11_parser.c"
326
329
  switch( (*p) ) {
327
330
  case 33: goto st22;
328
331
  case 58: goto tr32;
@@ -357,7 +360,7 @@ st23:
357
360
  if ( ++p == pe )
358
361
  goto _out23;
359
362
  case 23:
360
- #line 361 "ext/http11/http11_parser.c"
363
+ #line 364 "ext/http11/http11_parser.c"
361
364
  if ( (*p) == 13 )
362
365
  goto tr49;
363
366
  goto tr52;
@@ -369,7 +372,7 @@ st24:
369
372
  if ( ++p == pe )
370
373
  goto _out24;
371
374
  case 24:
372
- #line 373 "ext/http11/http11_parser.c"
375
+ #line 376 "ext/http11/http11_parser.c"
373
376
  if ( (*p) == 13 )
374
377
  goto tr49;
375
378
  goto st24;
@@ -381,7 +384,7 @@ st25:
381
384
  if ( ++p == pe )
382
385
  goto _out25;
383
386
  case 25:
384
- #line 385 "ext/http11/http11_parser.c"
387
+ #line 388 "ext/http11/http11_parser.c"
385
388
  switch( (*p) ) {
386
389
  case 43: goto st25;
387
390
  case 58: goto st26;
@@ -406,7 +409,7 @@ st26:
406
409
  if ( ++p == pe )
407
410
  goto _out26;
408
411
  case 26:
409
- #line 410 "ext/http11/http11_parser.c"
412
+ #line 413 "ext/http11/http11_parser.c"
410
413
  switch( (*p) ) {
411
414
  case 32: goto tr34;
412
415
  case 37: goto st27;
@@ -454,7 +457,7 @@ st29:
454
457
  if ( ++p == pe )
455
458
  goto _out29;
456
459
  case 29:
457
- #line 458 "ext/http11/http11_parser.c"
460
+ #line 461 "ext/http11/http11_parser.c"
458
461
  switch( (*p) ) {
459
462
  case 32: goto tr34;
460
463
  case 37: goto st31;
@@ -525,7 +528,7 @@ st33:
525
528
  if ( ++p == pe )
526
529
  goto _out33;
527
530
  case 33:
528
- #line 529 "ext/http11/http11_parser.c"
531
+ #line 532 "ext/http11/http11_parser.c"
529
532
  switch( (*p) ) {
530
533
  case 32: goto tr46;
531
534
  case 37: goto tr51;
@@ -547,7 +550,7 @@ st34:
547
550
  if ( ++p == pe )
548
551
  goto _out34;
549
552
  case 34:
550
- #line 551 "ext/http11/http11_parser.c"
553
+ #line 554 "ext/http11/http11_parser.c"
551
554
  switch( (*p) ) {
552
555
  case 32: goto tr46;
553
556
  case 37: goto st35;
@@ -569,7 +572,7 @@ st35:
569
572
  if ( ++p == pe )
570
573
  goto _out35;
571
574
  case 35:
572
- #line 573 "ext/http11/http11_parser.c"
575
+ #line 576 "ext/http11/http11_parser.c"
573
576
  if ( (*p) < 65 ) {
574
577
  if ( 48 <= (*p) && (*p) <= 57 )
575
578
  goto st36;
@@ -600,7 +603,7 @@ st37:
600
603
  if ( ++p == pe )
601
604
  goto _out37;
602
605
  case 37:
603
- #line 604 "ext/http11/http11_parser.c"
606
+ #line 607 "ext/http11/http11_parser.c"
604
607
  if ( (*p) == 69 )
605
608
  goto st38;
606
609
  goto st1;
@@ -619,7 +622,7 @@ st39:
619
622
  if ( ++p == pe )
620
623
  goto _out39;
621
624
  case 39:
622
- #line 623 "ext/http11/http11_parser.c"
625
+ #line 626 "ext/http11/http11_parser.c"
623
626
  if ( (*p) == 69 )
624
627
  goto st40;
625
628
  goto st1;
@@ -645,7 +648,7 @@ st42:
645
648
  if ( ++p == pe )
646
649
  goto _out42;
647
650
  case 42:
648
- #line 649 "ext/http11/http11_parser.c"
651
+ #line 652 "ext/http11/http11_parser.c"
649
652
  if ( (*p) == 80 )
650
653
  goto st43;
651
654
  goto st1;
@@ -692,7 +695,7 @@ st48:
692
695
  if ( ++p == pe )
693
696
  goto _out48;
694
697
  case 48:
695
- #line 696 "ext/http11/http11_parser.c"
698
+ #line 699 "ext/http11/http11_parser.c"
696
699
  switch( (*p) ) {
697
700
  case 79: goto st49;
698
701
  case 85: goto st38;
@@ -713,7 +716,7 @@ st50:
713
716
  if ( ++p == pe )
714
717
  goto _out50;
715
718
  case 50:
716
- #line 717 "ext/http11/http11_parser.c"
719
+ #line 720 "ext/http11/http11_parser.c"
717
720
  if ( (*p) == 82 )
718
721
  goto st51;
719
722
  goto st1;
@@ -788,15 +791,15 @@ case 52:
788
791
 
789
792
  _out: {}
790
793
  }
791
- #line 125 "ext/http11/http11_parser.rl"
794
+ #line 128 "ext/http11/http11_parser.rl"
792
795
 
793
796
  parser->cs = cs;
794
797
  parser->nread = p - buffer;
795
798
  if(parser->body_start) {
796
799
  /* final \r\n combo encountered so stop right here */
797
800
 
798
- #line 799 "ext/http11/http11_parser.c"
799
- #line 131 "ext/http11/http11_parser.rl"
801
+ #line 802 "ext/http11/http11_parser.c"
802
+ #line 134 "ext/http11/http11_parser.rl"
800
803
  parser->nread++;
801
804
  }
802
805
 
@@ -808,8 +811,8 @@ int http_parser_finish(http_parser *parser)
808
811
  int cs = parser->cs;
809
812
 
810
813
 
811
- #line 812 "ext/http11/http11_parser.c"
812
- #line 142 "ext/http11/http11_parser.rl"
814
+ #line 815 "ext/http11/http11_parser.c"
815
+ #line 145 "ext/http11/http11_parser.rl"
813
816
 
814
817
  parser->cs = cs;
815
818
 
@@ -26,7 +26,8 @@ typedef struct http_parser {
26
26
  element_cb request_uri;
27
27
  element_cb query_string;
28
28
  element_cb http_version;
29
-
29
+ element_cb header_done;
30
+
30
31
  } http_parser;
31
32
 
32
33
  int http_parser_init(http_parser *parser);
Binary file
@@ -5,8 +5,8 @@ require 'stringio'
5
5
  require 'mongrel/cgi'
6
6
  require 'mongrel/handlers'
7
7
  require 'mongrel/command'
8
- require 'timeout'
9
8
  require 'mongrel/tcphack'
9
+ require 'yaml'
10
10
 
11
11
 
12
12
  # Mongrel module containing all of the classes (include C extensions) for running
@@ -88,73 +88,52 @@ module Mongrel
88
88
  # REMOTE_USER, or REMOTE_HOST parameters since those are either a security problem or
89
89
  # too taxing on performance.
90
90
  module Const
91
- # This is the part of the path after the SCRIPT_NAME. URIClassifier will determine this.
92
- PATH_INFO="PATH_INFO"
93
- # This is the intial part that your handler is identified as by URIClassifier.
94
- SCRIPT_NAME="SCRIPT_NAME"
95
- # The original URI requested by the client. Passed to URIClassifier to build PATH_INFO and SCRIPT_NAME.
96
- REQUEST_URI='REQUEST_URI'
97
-
98
- # Content length (also available as HTTP_CONTENT_LENGTH).
99
- CONTENT_LENGTH='CONTENT_LENGTH'
100
-
101
- # Content length (also available as CONTENT_LENGTH).
102
- HTTP_CONTENT_LENGTH='HTTP_CONTENT_LENGTH'
103
-
104
- # Content type (also available as HTTP_CONTENT_TYPE).
105
- CONTENT_TYPE='CONTENT_TYPE'
106
-
107
- # Content type (also available as CONTENT_TYPE).
108
- HTTP_CONTENT_TYPE='HTTP_CONTENT_TYPE'
109
-
110
- # Gateway interface key in the HttpRequest parameters.
111
- GATEWAY_INTERFACE='GATEWAY_INTERFACE'
112
- # We claim to support CGI/1.2.
113
- GATEWAY_INTERFACE_VALUE='CGI/1.2'
114
-
115
- # Hosts remote IP address. Mongrel does not do DNS resolves since that slows
116
- # processing down considerably.
117
- REMOTE_ADDR='REMOTE_ADDR'
118
-
119
- # This is not given since Mongrel does not do DNS resolves. It is only here for
120
- # completeness for the CGI standard.
121
- REMOTE_HOST='REMOTE_HOST'
91
+ DATE = "Date".freeze
122
92
 
123
- # The name/host of our server as given by the HttpServer.new(host,port) call.
124
- SERVER_NAME='SERVER_NAME'
125
-
126
- # The port of our server as given by the HttpServer.new(host,port) call.
127
- SERVER_PORT='SERVER_PORT'
93
+ # This is the part of the path after the SCRIPT_NAME. URIClassifier will determine this.
94
+ PATH_INFO="PATH_INFO".freeze
128
95
 
129
- # SERVER_NAME and SERVER_PORT come from this.
130
- HTTP_HOST='HTTP_HOST'
96
+ # This is the intial part that your handler is identified as by URIClassifier.
97
+ SCRIPT_NAME="SCRIPT_NAME".freeze
131
98
 
132
- # Official server protocol key in the HttpRequest parameters.
133
- SERVER_PROTOCOL='SERVER_PROTOCOL'
134
- # Mongrel claims to support HTTP/1.1.
135
- SERVER_PROTOCOL_VALUE='HTTP/1.1'
99
+ # The original URI requested by the client. Passed to URIClassifier to build PATH_INFO and SCRIPT_NAME.
100
+ REQUEST_URI='REQUEST_URI'.freeze
136
101
 
137
- # The actual server software being used (it's Mongrel man).
138
- SERVER_SOFTWARE='SERVER_SOFTWARE'
139
-
140
- # Current Mongrel version (used for SERVER_SOFTWARE and other response headers).
141
- MONGREL_VERSION='Mongrel 0.3.10'
102
+ MONGREL_VERSION="0.3.12".freeze
142
103
 
143
104
  # The standard empty 404 response for bad requests. Use Error4040Handler for custom stuff.
144
- ERROR_404_RESPONSE="HTTP/1.1 404 Not Found\r\nConnection: close\r\nServer: #{MONGREL_VERSION}\r\n\r\nNOT FOUND"
105
+ ERROR_404_RESPONSE="HTTP/1.1 404 Not Found\r\nConnection: close\r\nServer: #{MONGREL_VERSION}\r\n\r\nNOT FOUND".freeze
106
+
107
+ CONTENT_LENGTH="CONTENT_LENGTH".freeze
145
108
 
146
109
  # A common header for indicating the server is too busy. Not used yet.
147
- ERROR_503_RESPONSE="HTTP/1.1 503 Service Unavailable\r\n\r\nBUSY"
110
+ ERROR_503_RESPONSE="HTTP/1.1 503 Service Unavailable\r\n\r\nBUSY".freeze
148
111
 
149
112
  # The basic max request size we'll try to read.
150
113
  CHUNK_SIZE=(16 * 1024)
151
114
 
115
+ # Format to generate a correct RFC 1123 date. rdoc for Time is wrong, there is no httpdate function.
116
+ RFC_1123_DATE_FORMAT="%a, %d %B %Y %H:%M:%S GMT".freeze
117
+
118
+ # A frozen format for this is about 15% faster
119
+ STATUS_FORMAT = "HTTP/1.1 %d %s\r\nContent-Length: %d\r\nConnection: close\r\n".freeze
120
+ CONTENT_TYPE = "Content-Type".freeze
121
+ LAST_MODIFIED = "Last-Modified".freeze
122
+ ETAG = "ETag".freeze
123
+ SLASH = "/".freeze
124
+ REQUEST_METHOD="REQUEST_METHOD".freeze
125
+ GET="GET".freeze
126
+ HEAD="HEAD".freeze
127
+ # ETag is based on the apache standard of hex mtime-size-inode (inode is 0 on win32)
128
+ ETAG_FORMAT="\"%x-%x-%x\"".freeze
129
+ HEADER_FORMAT="%s: %s\r\n".freeze
130
+ LINE_END="\r\n".freeze
152
131
  end
153
132
 
154
133
 
155
134
  # When a handler is found for a registered URI then this class is constructed
156
135
  # and passed to your HttpHandler::process method. You should assume that
157
- # *one* handler processes all requests. Included in the HttpReqeust is a
136
+ # *one* handler processes all requests. Included in the HttpRequest is a
158
137
  # HttpRequest.params Hash that matches common CGI params, and a HttpRequest.body
159
138
  # which is a string containing the request body (raw for now).
160
139
  #
@@ -172,28 +151,49 @@ module Mongrel
172
151
  @body = initial_body || ""
173
152
  @params = params
174
153
  @socket = socket
175
-
176
- # fix up the CGI requirements
177
- params[Const::CONTENT_LENGTH] = params[Const::HTTP_CONTENT_LENGTH] || 0
178
- params[Const::CONTENT_TYPE] = params[Const::HTTP_CONTENT_TYPE] if params[Const::HTTP_CONTENT_TYPE]
179
- params[Const::GATEWAY_INTERFACE]=Const::GATEWAY_INTERFACE_VALUE
180
- params[Const::REMOTE_ADDR]=socket.peeraddr[3]
181
- if params.has_key? Const::HTTP_HOST
182
- host,port = params[Const::HTTP_HOST].split(":")
183
- params[Const::SERVER_NAME]=host
184
- params[Const::SERVER_PORT]=port || 80
185
- end
186
- params[Const::SERVER_PROTOCOL]=Const::SERVER_PROTOCOL_VALUE
187
- params[Const::SERVER_SOFTWARE]=Const::MONGREL_VERSION
188
-
189
154
 
190
155
  # now, if the initial_body isn't long enough for the content length we have to fill it
191
156
  # TODO: adapt for big ass stuff by writing to a temp file
192
- clen = params[Const::HTTP_CONTENT_LENGTH].to_i
157
+ clen = params[Const::CONTENT_LENGTH].to_i
193
158
  if @body.length < clen
194
159
  @body << @socket.read(clen - @body.length)
195
160
  end
161
+
162
+ end
163
+
164
+ def self.escape(s)
165
+ s.to_s.gsub(/([^ a-zA-Z0-9_.-]+)/n) {
166
+ '%'+$1.unpack('H2'*$1.size).join('%').upcase
167
+ }.tr(' ', '+')
168
+ end
169
+
170
+
171
+ def self.unescape(s)
172
+ s.tr('+', ' ').gsub(/((?:%[0-9a-fA-F]{2})+)/n){
173
+ [$1.delete('%')].pack('H*')
174
+ }
196
175
  end
176
+
177
+
178
+ def self.query_parse(qs, d = '&;')
179
+ params = {}
180
+ (qs||'').split(/[#{d}] */n).inject(params) { |h,p|
181
+ k, v=unescape(p).split('=',2)
182
+ if cur = params[k]
183
+ if cur.class == Array
184
+ params[k] << v
185
+ else
186
+ params[k] = [cur, v]
187
+ end
188
+ else
189
+ params[k] = v
190
+ end
191
+ }
192
+
193
+ return params
194
+ end
195
+
196
+
197
197
  end
198
198
 
199
199
 
@@ -213,11 +213,9 @@ module Mongrel
213
213
 
214
214
  # Simply writes "#{key}: #{value}" to an output buffer.
215
215
  def[]=(key,value)
216
- @out.write(key)
217
- @out.write(": ")
218
- @out.write(value)
219
- @out.write("\r\n")
216
+ @out.write(Const::HEADER_FORMAT % [key, value])
220
217
  end
218
+
221
219
  end
222
220
 
223
221
  # Writes and controls your response to the client using the HTTP/1.1 specification.
@@ -255,48 +253,79 @@ module Mongrel
255
253
  attr_reader :header
256
254
  attr_reader :status
257
255
  attr_writer :status
256
+ attr_reader :body_sent
257
+ attr_reader :header_sent
258
+ attr_reader :status_sent
258
259
 
259
- def initialize(socket)
260
+ def initialize(socket, filter = nil)
260
261
  @socket = socket
261
262
  @body = StringIO.new
262
263
  @status = 404
263
264
  @header = HeaderOut.new(StringIO.new)
265
+ @header[Const::DATE] = HttpServer.httpdate(Time.now)
266
+ @filter = filter
267
+ @body_sent = false
268
+ @header_sent = false
269
+ @status_sent = false
264
270
  end
265
271
 
266
272
  # Receives a block passing it the header and body for you to work with.
267
273
  # When the block is finished it writes everything you've done to
268
274
  # the socket in the proper order. This lets you intermix header and
269
- # body content as needed.
270
- def start(status=200)
275
+ # body content as needed. Handlers are able to modify pretty much
276
+ # any part of the request in the chain, and can stop further processing
277
+ # by simple passing "finalize=true" to the start method. By default
278
+ # all handlers run and then mongrel finalizes the request when they're
279
+ # all done.
280
+ def start(status=200, finalize=false)
271
281
  @status = status.to_i
272
282
  yield @header, @body
273
- finished
283
+ finished if finalize
274
284
  end
275
285
 
276
286
  # Primarily used in exception handling to reset the response output in order to write
277
- # an alternative response.
287
+ # an alternative response. It will abort with an exception if you have already
288
+ # sent the header or the body. This is pretty catastrophic actually.
278
289
  def reset
279
- @header.out.rewind
280
- @body.rewind
290
+ if @body_sent
291
+ raise "You have already sent the request body."
292
+ elsif @header_sent
293
+ raise "You have already sent the request headers."
294
+ else
295
+ @header.out.rewind
296
+ @body.rewind
297
+ end
281
298
  end
282
299
 
283
- def send_status
284
- status = "HTTP/1.1 #{@status} #{HTTP_STATUS_CODES[@status]}\r\nContent-Length: #{@body.length}\r\nConnection: close\r\n"
285
- @socket.write(status)
300
+ def send_status(content_length=nil)
301
+ if not @status_sent
302
+ content_length ||= @body.length
303
+ @socket.write(Const::STATUS_FORMAT % [status, HTTP_STATUS_CODES[@status], content_length])
304
+ @status_sent = true
305
+ end
286
306
  end
287
307
 
288
308
  def send_header
289
- @header.out.rewind
290
- @socket.write(@header.out.read)
291
- @socket.write("\r\n")
309
+ if not @header_sent
310
+ @header.out.rewind
311
+ @socket.write(@header.out.read + Const::LINE_END)
312
+ @header_sent = true
313
+ end
292
314
  end
293
315
 
294
316
  def send_body
295
- @body.rewind
296
- # connection: close is also added to ensure that the client does not pipeline.
297
- @socket.write(@body.read)
317
+ if not @body_sent
318
+ @body.rewind
319
+ # connection: close is also added to ensure that the client does not pipeline.
320
+ @socket.write(@body.read)
321
+ @body_sent = true
322
+ end
298
323
  end
299
324
 
325
+ def write(data)
326
+ @socket.write(data)
327
+ end
328
+
300
329
  # This takes whatever has been done to header and body and then writes it in the
301
330
  # proper format to make an HTTP/1.1 response.
302
331
  def finished
@@ -304,6 +333,11 @@ module Mongrel
304
333
  send_header
305
334
  send_body
306
335
  end
336
+
337
+ def done
338
+ (@status_sent and @header_sent and @body_sent)
339
+ end
340
+
307
341
  end
308
342
 
309
343
 
@@ -327,41 +361,30 @@ module Mongrel
327
361
  # hold your breath until Ruby 1.9 is actually finally useful.
328
362
  class HttpServer
329
363
  attr_reader :acceptor
364
+ attr_reader :workers
365
+ attr_reader :classifier
330
366
 
331
367
  # Creates a working server on host:port (strange things happen if port isn't a Number).
332
- # Use HttpServer::run to start the server.
333
- #
334
- # The num_processors variable has varying affects on how requests are processed. You'd
335
- # think adding more processing threads (processors) would make the server faster, but
336
- # that's just not true. There's actually an effect of how Ruby does threads such that
337
- # the more processors waiting on the request queue, the slower the system is to handle
338
- # each request. But, the lower the number of processors the fewer concurrent responses
339
- # the server can make.
368
+ # Use HttpServer::run to start the server and HttpServer.acceptor.join to
369
+ # join the thread that's processing incoming requests on the socket.
340
370
  #
341
- # 20 is the default number of processors and is based on experimentation on a few
342
- # systems. If you find that you overload Mongrel too much
343
- # try changing it higher. If you find that responses are way too slow
344
- # try lowering it (after you've tuned your stuff of course).
345
- def initialize(host, port, num_processors=20, timeout=120)
371
+ # The num_processors optional argument is the maximum number of concurrent
372
+ # processors to accept, anything over this is closed immediately to maintain
373
+ # server processing performance. This may seem mean but it is the most efficient
374
+ # way to deal with overload. Other schemes involve still parsing the client's request
375
+ # which defeats the point of an overload handling system.
376
+ #
377
+ # The timeout parameter is a sleep timeout (in hundredths of a second) that is placed between
378
+ # socket.accept calls in order to give the server a cheap throttle time. It defaults to 0 and
379
+ # actually if it is 0 then the sleep is not done at all.
380
+ def initialize(host, port, num_processors=(2**30-1), timeout=0)
346
381
  @socket = TCPServer.new(host, port)
347
-
348
382
  @classifier = URIClassifier.new
349
- @req_queue = Queue.new
350
383
  @host = host
351
384
  @port = port
352
- @processors = []
353
-
354
- # create the worker threads
355
- num_processors.times do |i|
356
- @processors << Thread.new do
357
- while client = @req_queue.deq
358
- Timeout::timeout(timeout) do
359
- process_client(client)
360
- end
361
- end
362
- end
363
- end
364
-
385
+ @workers = ThreadGroup.new
386
+ @timeout = timeout
387
+ @num_processors = num_processors
365
388
  end
366
389
 
367
390
 
@@ -374,19 +397,31 @@ module Mongrel
374
397
  begin
375
398
  parser = HttpParser.new
376
399
  params = {}
400
+
377
401
  data = client.readpartial(Const::CHUNK_SIZE)
378
402
 
379
403
  while true
380
404
  nread = parser.execute(params, data)
405
+
381
406
  if parser.finished?
382
- script_name, path_info, handler = @classifier.resolve(params[Const::REQUEST_URI])
407
+ script_name, path_info, handlers = @classifier.resolve(params[Const::REQUEST_URI])
383
408
 
384
- if handler
409
+ if handlers
385
410
  params[Const::PATH_INFO] = path_info
386
411
  params[Const::SCRIPT_NAME] = script_name
412
+
387
413
  request = HttpRequest.new(params, data[nread ... data.length], client)
388
414
  response = HttpResponse.new(client)
389
- handler.process(request, response)
415
+
416
+ handlers.each do |handler|
417
+ handler.process(request, response)
418
+ break if response.done
419
+ end
420
+
421
+ if not response.done
422
+ response.finished
423
+ end
424
+
390
425
  else
391
426
  client.write(Const::ERROR_404_RESPONSE)
392
427
  end
@@ -399,11 +434,7 @@ module Mongrel
399
434
  data << client.readpartial(Const::CHUNK_SIZE)
400
435
  end
401
436
  end
402
- rescue EOFError
403
- # ignored
404
- rescue Errno::ECONNRESET
405
- # ignored
406
- rescue Errno::EPIPE
437
+ rescue EOFError,Errno::ECONNRESET,Errno::EPIPE,Errno::EINVAL
407
438
  # ignored
408
439
  rescue => details
409
440
  STDERR.puts "ERROR(#{details.class}): #{details}"
@@ -413,16 +444,45 @@ module Mongrel
413
444
  end
414
445
  end
415
446
 
447
+ # Used internally to kill off any worker threads that have taken too long
448
+ # to complete processing. Only called if there are too many processors
449
+ # currently servicing.
450
+ def reap_dead_workers(worker_list)
451
+ mark = Time.now
452
+ worker_list.each do |w|
453
+ if mark - w[:started_on] > 10 * @timeout
454
+ STDERR.puts "Thread #{w.inspect} is too old, killing."
455
+ w.raise(StopServer.new("Timed out thread."))
456
+ end
457
+ end
458
+ end
459
+
460
+
416
461
  # Runs the thing. It returns the thread used so you can "join" it. You can also
417
462
  # access the HttpServer::acceptor attribute to get the thread later.
418
463
  def run
419
464
  BasicSocket.do_not_reverse_lookup=true
420
- @acceptor = Thread.new do
421
- Thread.current[:stopped] = false
422
465
 
423
- while not Thread.current[:stopped]
466
+ @acceptor = Thread.new do
467
+ while true
424
468
  begin
425
- @req_queue << @socket.accept
469
+ client = @socket.accept
470
+ worker_list = @workers.list
471
+ if worker_list.length >= @num_processors
472
+ STDERR.puts "Server overloaded with #{worker_list.length} processors (#@num_processors max). Dropping connection."
473
+ client.close
474
+ reap_dead_workers(worker_list)
475
+ else
476
+ thread = Thread.new do
477
+ process_client(client)
478
+ end
479
+
480
+ thread[:started_on] = Time.now
481
+ thread.priority=1
482
+ @workers.add(thread)
483
+
484
+ sleep @timeout/100 if @timeout > 0
485
+ end
426
486
  rescue StopServer
427
487
  STDERR.puts "Server stopped. Exiting."
428
488
  @socket.close if not @socket.closed?
@@ -435,17 +495,14 @@ module Mongrel
435
495
 
436
496
  # now that processing is done we feed enough false onto the request queue to get
437
497
  # each processor to exit and stop processing.
438
- @processors.length.times { @req_queue << false }
439
498
 
440
499
  # finally we wait until the queue is empty
441
- while @req_queue.length > 0
442
- STDERR.puts "Shutdown waiting for #{@req_queue.length} requests" if @req_queue.length > 0
500
+ while @workers.list.length > 0
501
+ STDERR.puts "Shutdown waiting for #{@workers.list.length} requests" if @workers.list.length > 0
443
502
  sleep 1
444
503
  end
445
504
  end
446
505
 
447
- @acceptor.priority = 1
448
-
449
506
  return @acceptor
450
507
  end
451
508
 
@@ -454,11 +511,22 @@ module Mongrel
454
511
  # found in the prefix of a request then your handler's HttpHandler::process method
455
512
  # is called. See Mongrel::URIClassifier#register for more information.
456
513
  def register(uri, handler)
457
- @classifier.register(uri, handler)
514
+ script_name, path_info, handlers = @classifier.resolve(uri)
515
+
516
+ if not handlers
517
+ @classifier.register(uri, [handler])
518
+ else
519
+ if path_info.length == 0 or (script_name == Const::SLASH and path_info == Const::SLASH)
520
+ handlers << handler
521
+ else
522
+ @classifier.register(uri, [handler])
523
+ end
524
+ end
458
525
  end
459
526
 
460
- # Removes any handler registered at the given URI. See Mongrel::URIClassifier#unregister
461
- # for more information.
527
+ # Removes any handlers registered at the given URI. See Mongrel::URIClassifier#unregister
528
+ # for more information. Remember this removes them *all* so the entire
529
+ # processing chain goes away.
462
530
  def unregister(uri)
463
531
  @classifier.unregister(uri)
464
532
  end
@@ -467,13 +535,308 @@ module Mongrel
467
535
  # off the request queue before finally exiting.
468
536
  def stop
469
537
  stopper = Thread.new do
470
- @acceptor[:stopped] = true
471
538
  exc = StopServer.new
472
539
  @acceptor.raise(exc)
473
540
  end
474
541
  stopper.priority = 10
475
542
  end
476
543
 
544
+ # Given the a time object it converts it to GMT and applies the RFC1123 format to it.
545
+ def HttpServer.httpdate(date)
546
+ date.gmtime.strftime(Const::RFC_1123_DATE_FORMAT)
547
+ end
548
+
549
+ end
550
+
551
+
552
+ # Implements a simple DSL for configuring a Mongrel server for your
553
+ # purposes. More used by framework implementers to setup Mongrel
554
+ # how they like, but could be used by regular folks to add more things
555
+ # to an existing mongrel configuration.
556
+ #
557
+ # It is used like this:
558
+ #
559
+ # require 'mongrel'
560
+ # config = Mongrel::Configurator.new :host => "127.0.0.1" do
561
+ # listener :port => 3000 do
562
+ # uri "/app", :handler => Mongrel::DirHandler.new(".", load_mime_map("mime.yaml"))
563
+ # end
564
+ # run
565
+ # end
566
+ #
567
+ # This will setup a simple DirHandler at the current directory and load additional
568
+ # mime types from mimy.yaml. The :host => "127.0.0.1" is actually not
569
+ # specific to the servers but just a hash of default parameters that all
570
+ # server or uri calls receive.
571
+ #
572
+ # When you are inside the block after Mongrel::Configurator.new you can simply
573
+ # call functions that are part of Configurator (like server, uri, daemonize, etc)
574
+ # without having to refer to anything else. You can also call these functions on
575
+ # the resulting object directly for additional configuration.
576
+ #
577
+ # A major thing about Configurator is that it actually lets you configure
578
+ # multiple listeners for any hosts and ports you want. These are kept in a
579
+ # map config.listeners so you can get to them.
580
+ class Configurator
581
+ attr_reader :listeners
582
+ attr_reader :defaults
583
+ attr_reader :needs_restart
584
+
585
+ # You pass in initial defaults and then a block to continue configuring.
586
+ def initialize(defaults={}, &blk)
587
+ @listeners = {}
588
+ @defaults = defaults
589
+ @needs_restart = false
590
+
591
+ if blk
592
+ cloaker(&blk).bind(self).call
593
+ end
594
+ end
595
+
596
+ # Do not call this. You were warned.
597
+ def cloaker &blk
598
+ (class << self; self; end).class_eval do
599
+ define_method :cloaker_, &blk
600
+ meth = instance_method( :cloaker_ )
601
+ remove_method :cloaker_
602
+ meth
603
+ end
604
+ end
605
+
606
+ # This will resolve the given options against the defaults.
607
+ # Normally just used internally.
608
+ def resolve_defaults(options)
609
+ options.merge(@defaults)
610
+ end
611
+
612
+ # Starts a listener block. This is the only one that actually takes
613
+ # a block and then you make Configurator.uri calls in order to setup
614
+ # your URIs and handlers. If you write your Handlers as GemPlugins
615
+ # then you can use load_plugins and plugin to load them.
616
+ #
617
+ # It expects the following options (or defaults):
618
+ #
619
+ # * :host => Host name to bind.
620
+ # * :port => Port to bind.
621
+ # * :num_processors => The maximum number of concurrent threads allowed. (950 default)
622
+ # * :timeout => 1/100th of a second timeout between requests. (10 is 1/10th, 0 is not timeout)
623
+ #
624
+ def listener(options={},&blk)
625
+ ops = resolve_defaults(options)
626
+ ops[:num_processors] ||= 950
627
+ ops[:timeout] ||= 0
628
+
629
+ @listener = Mongrel::HttpServer.new(ops[:host], ops[:port].to_i, ops[:num_processors].to_i, ops[:timeout].to_i)
630
+ @listener_name = "#{ops[:host]}:#{ops[:port]}"
631
+ @listeners[@listener_name] = @listener
632
+
633
+ if blk
634
+ cloaker(&blk).bind(self).call
635
+ end
636
+
637
+ # all done processing this listener setup
638
+ @listener = nil
639
+ @listener_name = nil
640
+ end
641
+
642
+
643
+ # Called inside a Configurator.listener block in order to
644
+ # add URI->handler mappings for that listener. Use this as
645
+ # many times as you like. It expects the following options
646
+ # or defaults:
647
+ #
648
+ # * :handler => Handler to use for this location.
649
+ def uri(location, options={})
650
+ ops = resolve_defaults(options)
651
+ @listener.register(location, ops[:handler])
652
+ end
653
+
654
+
655
+ # Daemonizes the current Ruby script turning all the
656
+ # listeners into an actual "server" or detached process.
657
+ # You must call this *before* frameworks that open files
658
+ # as otherwise the files will be closed by this function.
659
+ #
660
+ # Does not work for Win32 systems (the call is silently ignored).
661
+ #
662
+ # Requires the following options or defaults:
663
+ #
664
+ # * :cwd => Directory to change to.
665
+ # * :log_file => Where to write STDOUT and STDERR.
666
+ # * :pid_file => Where to write the process ID.
667
+ #
668
+ # It is safe to call this on win32 as it will only require daemons
669
+ # if NOT win32.
670
+ def daemonize(options={})
671
+ ops = resolve_defaults(options)
672
+ # save this for later since daemonize will hose it
673
+ if RUBY_PLATFORM !~ /mswin/
674
+ require 'daemons/daemonize'
675
+
676
+ Daemonize.daemonize(log_file=File.join(ops[:cwd], ops[:log_file]))
677
+
678
+ # change back to the original starting directory
679
+ Dir.chdir(ops[:cwd])
680
+
681
+ open(ops[:pid_file],"w") {|f| f.write(Process.pid) }
682
+ else
683
+ log "WARNING: Win32 does not support daemon mode."
684
+ end
685
+ end
686
+
687
+
688
+ # Uses the GemPlugin system to easily load plugins based on their
689
+ # gem dependencies. You pass in either an :includes => [] or
690
+ # :excludes => [] setting listing the names of plugins to include
691
+ # or exclude from the loading.
692
+ def load_plugins(options={})
693
+ ops = resolve_defaults(options)
694
+
695
+ load_settings = {}
696
+ if ops[:includes]
697
+ ops[:includes].each do |plugin|
698
+ load_settings[plugin] = GemPlugin::INCLUDE
699
+ end
700
+ end
701
+
702
+ if ops[:excludes]
703
+ ops[:excludes].each do |plugin|
704
+ load_settings[plugin] = GemPlugin::EXCLUDE
705
+ end
706
+ end
707
+
708
+ GemPlugin::Manager.instance.load(load_settings)
709
+ end
710
+
711
+
712
+ # Easy way to load a YAML file and apply default settings.
713
+ def load_yaml(file, default={})
714
+ default.merge(YAML.load_file(file))
715
+ end
716
+
717
+
718
+ # Loads the MIME map file and checks that it is correct
719
+ # on loading. This is commonly passed to Mongrel::DirHandler
720
+ # or any framework handler that uses DirHandler to serve files.
721
+ # You can also include a set of default MIME types as additional
722
+ # settings. See Mongrel::DirHandler for how the MIME types map
723
+ # is organized.
724
+ def load_mime_map(file, mime={})
725
+ # configure any requested mime map
726
+ log "Loading additional MIME types from #{file}"
727
+ mime = load_yaml(file, mime)
728
+
729
+ # check all the mime types to make sure they are the right format
730
+ mime.each {|k,v| log "WARNING: MIME type #{k} must start with '.'" if k.index(".") != 0 }
731
+
732
+ return mime
733
+ end
734
+
735
+
736
+ # Loads and creates a plugin for you based on the given
737
+ # name and configured with the selected options. The options
738
+ # are merged with the defaults prior to passing them in.
739
+ def plugin(name, options={})
740
+ ops = resolve_defaults(options)
741
+ GemPlugin::Manager.instance.create(name, ops)
742
+ end
743
+
744
+
745
+ # Works like a meta run method which goes through all the
746
+ # configured listeners. Use the Configurator.join method
747
+ # to prevent Ruby from exiting until each one is done.
748
+ def run
749
+ @listeners.each {|name,s|
750
+ log "Running #{name} listener."
751
+ s.run
752
+ }
753
+
754
+ end
755
+
756
+ # Calls .stop on all the configured listeners so they
757
+ # stop processing requests (gracefully).
758
+ def stop
759
+ @listeners.each {|name,s|
760
+ log "Stopping #{name} listener."
761
+ s.stop
762
+ }
763
+ end
764
+
765
+
766
+ # This method should actually be called *outside* of the
767
+ # Configurator block so that you can control it. In otherwords
768
+ # do it like: config.join.
769
+ def join
770
+ @listeners.values.each {|s| s.acceptor.join }
771
+ end
772
+
773
+
774
+ # Calling this before you register your URIs to the given location
775
+ # will setup a set of handlers that log open files, objects, and the
776
+ # parameters for each request. This helps you track common problems
777
+ # found in Rails applications that are either slow or become unresponsive
778
+ # after a little while.
779
+ def debug(location)
780
+ require 'mongrel/debug'
781
+ ObjectTracker.configure
782
+ MongrelDbg.configure
783
+ MongrelDbg.begin_trace :objects
784
+ MongrelDbg.begin_trace :rails
785
+ MongrelDbg.begin_trace :files
786
+
787
+ uri location, :handler => plugin("/handlers/requestlog::files")
788
+ uri location, :handler => plugin("/handlers/requestlog::objects")
789
+ uri location, :handler => plugin("/handlers/requestlog::params")
790
+ end
791
+
792
+
793
+ # Sets up the standard signal handlers that are used on most Ruby
794
+ # It only configures if the platform is not win32 and doesn't do
795
+ # a HUP signal since this is typically framework specific.
796
+ #
797
+ # Requires a :pid_file option to indicate a file to delete.
798
+ # It sets the MongrelConfig.needs_restart attribute if
799
+ # the start command should reload. It's up to you to detect this
800
+ # and do whatever is needed for a "restart".
801
+ #
802
+ # This command is safely ignored if the platform is win32 (with a warning)
803
+ def setup_signals(options={})
804
+ ops = resolve_defaults(options)
805
+
806
+ if RUBY_PLATFORM !~ /mswin/
807
+ # graceful shutdown
808
+ trap("TERM") {
809
+ log "TERM signal received."
810
+ stop
811
+ File.unlink ops[:pid_file] if File.exist?(ops[:pid_file])
812
+ }
813
+
814
+ # restart
815
+ trap("USR2") {
816
+ log "USR2 signal received."
817
+ stop
818
+ File.unlink ops[:pid_file] if File.exist?(ops[:pid_file])
819
+ @needs_restart = true
820
+ }
821
+
822
+ trap("INT") {
823
+ log "INT signal received."
824
+ stop
825
+ File.unlink ops[:pid_file] if File.exist?(ops[:pid_file])
826
+ @needs_restart = false
827
+ }
828
+
829
+ log "Signals ready. TERM => stop. USR2 => restart. INT => stop (no restart)."
830
+ else
831
+ log "WARNING: Win32 does not have signals support."
832
+ end
833
+ end
834
+
835
+ # Logs a simple message to STDERR (or the mongrel log if in daemon mode).
836
+ def log(msg)
837
+ STDERR.print "** ", msg, "\n"
838
+ end
839
+
477
840
  end
478
841
 
479
842
  end