mongrel 0.3.11 → 0.3.12

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