mongrel_esi 0.4.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (73) hide show
  1. data/COPYING +53 -0
  2. data/LICENSE +471 -0
  3. data/README +186 -0
  4. data/Rakefile +141 -0
  5. data/bin/mongrel_esi +271 -0
  6. data/ext/esi/common.rl +41 -0
  7. data/ext/esi/esi_parser.c +387 -0
  8. data/ext/esi/extconf.rb +6 -0
  9. data/ext/esi/machine.rb +499 -0
  10. data/ext/esi/parser.c +1675 -0
  11. data/ext/esi/parser.h +113 -0
  12. data/ext/esi/parser.rb +49 -0
  13. data/ext/esi/parser.rl +398 -0
  14. data/ext/esi/ruby_esi.rl +135 -0
  15. data/ext/esi/run-test.rb +3 -0
  16. data/ext/esi/test/common.rl +41 -0
  17. data/ext/esi/test/parser.c +1676 -0
  18. data/ext/esi/test/parser.h +113 -0
  19. data/ext/esi/test/parser.rl +398 -0
  20. data/ext/esi/test/test.c +373 -0
  21. data/ext/esi/test1.rb +56 -0
  22. data/ext/esi/test2.rb +45 -0
  23. data/lib/esi/cache.rb +207 -0
  24. data/lib/esi/config.rb +154 -0
  25. data/lib/esi/dispatcher.rb +27 -0
  26. data/lib/esi/handler.rb +236 -0
  27. data/lib/esi/invalidator.rb +40 -0
  28. data/lib/esi/logger.rb +46 -0
  29. data/lib/esi/router.rb +84 -0
  30. data/lib/esi/tag/attempt.rb +6 -0
  31. data/lib/esi/tag/base.rb +85 -0
  32. data/lib/esi/tag/except.rb +24 -0
  33. data/lib/esi/tag/include.rb +190 -0
  34. data/lib/esi/tag/invalidate.rb +54 -0
  35. data/lib/esi/tag/try.rb +40 -0
  36. data/lib/multi_dirhandler.rb +70 -0
  37. data/setup.rb +1585 -0
  38. data/test/integration/basic_test.rb +39 -0
  39. data/test/integration/cache_test.rb +37 -0
  40. data/test/integration/docs/content/500.html +16 -0
  41. data/test/integration/docs/content/500_with_failover.html +16 -0
  42. data/test/integration/docs/content/500_with_failover_to_alt.html +8 -0
  43. data/test/integration/docs/content/ajax_test_page.html +15 -0
  44. data/test/integration/docs/content/cookie_variable.html +3 -0
  45. data/test/integration/docs/content/foo.html +15 -0
  46. data/test/integration/docs/content/include_in_include.html +15 -0
  47. data/test/integration/docs/content/malformed_transforms.html +16 -0
  48. data/test/integration/docs/content/malformed_transforms.html-correct +11 -0
  49. data/test/integration/docs/content/static-failover.html +20 -0
  50. data/test/integration/docs/content/test2.html +1 -0
  51. data/test/integration/docs/content/test3.html +17 -0
  52. data/test/integration/docs/esi_invalidate.html +6 -0
  53. data/test/integration/docs/esi_mixed_content.html +15 -0
  54. data/test/integration/docs/esi_test_content.html +27 -0
  55. data/test/integration/docs/index.html +688 -0
  56. data/test/integration/docs/test1.html +1 -0
  57. data/test/integration/docs/test3.html +9 -0
  58. data/test/integration/docs/test_failover.html +1 -0
  59. data/test/integration/handler_test.rb +270 -0
  60. data/test/integration/help.rb +234 -0
  61. data/test/net/get_test.rb +197 -0
  62. data/test/net/net_helper.rb +16 -0
  63. data/test/net/server_test.rb +249 -0
  64. data/test/unit/base_tag_test.rb +44 -0
  65. data/test/unit/esi-sample.html +56 -0
  66. data/test/unit/help.rb +77 -0
  67. data/test/unit/include_request_test.rb +69 -0
  68. data/test/unit/include_tag_test.rb +14 -0
  69. data/test/unit/parser_test.rb +478 -0
  70. data/test/unit/router_test.rb +34 -0
  71. data/test/unit/sample.html +21 -0
  72. data/tools/rakehelp.rb +119 -0
  73. metadata +182 -0
data/README ADDED
@@ -0,0 +1,186 @@
1
+ =About=
2
+ MongrelESI is meant to make caching easier by distributing the cache logic.
3
+
4
+ The idea is to represent each part of the page as a unique URL.
5
+
6
+ The whole page may not be cachable, but most of it probably is. The cache server works by scanning the requested document for
7
+ specific <esi:* tags before servering the response. MongrelESI currently, only supports a few basic instructions
8
+
9
+ =Tags=
10
+
11
+ esi:include:
12
+ src - request path, response replaces esi:include tag
13
+ timeout - how long to wait on the response
14
+ max-age - how long to cache the entity
15
+ onerror - whether continue on errors or trigger an exception
16
+
17
+ esi:invalidate:
18
+ encloses invalidation instructions.
19
+ these instructions currently only support basic invalidation
20
+ e.g.
21
+ <esi:invalidate output="no">
22
+ <?xml version="1.0"?>
23
+ <!DOCTYPE INVALIDATION SYSTEM "internal:///WCSinvalidation.dtd">
24
+ <INVALIDATION VERSION="WCS-1.1">
25
+ <OBJECT>
26
+ <BASICSELECTOR URI="/path/to/invalidate"/>
27
+ <ACTION REMOVALTTL="0"/>
28
+ <INFO VALUE="invalidating fragment test 1"/>
29
+ </OBJECT>
30
+ </INVALIDATION>
31
+ </esi:invalidate>
32
+
33
+ In the above example, only /path/to/invalidate will be invalidated, advanced regex style selectors are only support with ruby as the
34
+ cache storage.
35
+
36
+ esi:try/esi:attempt/esi:except
37
+ if any tags within the attempt block raise an exception and the onerror attribute is not equal to continue then
38
+ the server will fall back to the markup within the except block
39
+
40
+ =Proxy Config=
41
+
42
+ MongrelESI is a proxy server. To configure where requests should be proxied modify the config/routes.yml file.
43
+
44
+ Here's an example:
45
+
46
+ ESI::Config.define(listeners) do|config|
47
+ # define request path routing rules
48
+ config.routes do|s|
49
+ s.match( /^\/(content|samples|extras).*/ ) do|r|
50
+ r.servers = ['127.0.0.1:4000']
51
+ end
52
+ s.default do|r|
53
+ r.servers = ['127.0.0.1:3000']
54
+ end
55
+ end
56
+ end
57
+
58
+ This sample configuration will route all urls starting with /content, /samples, or /extras
59
+ to the servers running at 127.0.0.1 on port 4000.
60
+ Everything else that matches the .* will be routed to the server running on port 3000.
61
+ Optionally the caching duration can be specificied explicity for each host
62
+
63
+ s.default do|r|
64
+ r.servers = ['127.0.0.1:3000']
65
+ r.cache_ttl = 300
66
+ end
67
+
68
+ This example will cache all requests for 300 seconds, but normally you would want to set the ttl on each individual fragment or include.
69
+
70
+ A typical include would look like the following:
71
+
72
+ <esi:include src="/content/1" max-age="600+600"/>
73
+
74
+ Requesting /home, might respond with the following:
75
+
76
+ <html>
77
+ <head><title>Your Page</title>
78
+ </head>
79
+ <body>
80
+ <div class="header"><esi:include src="/content/1 max-age="600+600"/></div>
81
+ <div>Some content</div>
82
+ <div>Some more content</div>
83
+ </body>
84
+ </html>
85
+
86
+ If the uri is not already cached, the cache server will request /content/1, which will respond with:
87
+
88
+ <div>hello User42</div>
89
+
90
+ And finally, MongrelESI will respond to the original client request with the combined documents:
91
+ <html>
92
+ <head><title>Your Page</title>
93
+ </head>
94
+ <body>
95
+ <div class="header"><div>hello User42</div></div>
96
+ <div>Some content</div>
97
+ <div>Some more content</div>
98
+ </body>
99
+ </html>
100
+
101
+
102
+ == Integrating with your applications ==
103
+
104
+ MongrelESI was built to support integrating lots of applications together.
105
+
106
+ Primarily, you'll want to integrate by having one base application and many tiny applications that
107
+ surface within your primary application. In the rails sense think components. In a bigger sense think
108
+ multiple application technologies all integrating into the same application. Imagine a java application
109
+ returning up to the minute chat conversations in one part of the page, while rails delivers a reliable listing of
110
+ current forum discussion, and maybe a python presense indicator for forum posts...
111
+
112
+ One thing to keep in mind is mongrel esi is a proxy server. This means for each request it needs to know where to route
113
+ the requests. To support this config.rb can express any regex to match a url and determine where a request should be
114
+ forwarded as well as defining a default route.
115
+
116
+ It is also sometimes convenient to allow multiple applications to store or house static content. For this to work mongrel esi
117
+ needs to know in what directories to search for static files. This can be done by using the MultiDirHandler (see rev-config.rb)
118
+
119
+
120
+ == Supported Features of ESI ==
121
+
122
+ From [http://www.w3.org/TR/esi-lang esi-lang]
123
+
124
+ MongrelESI supports basic include, exception handling and invalidation. It does not include support for
125
+ Variable or Conditional processing. It does have suppport for COOKIE variables.
126
+ (e.g. $(HTTP_COOKIE{name}) will be replaced with the value of the cookie, name)
127
+
128
+ + Inclusion - ESI can compose pages by assembling included content, which is fetched from the network.
129
+ This allows each such fragment to have its own metadata (e.g., cacheability and handling information) seperately associated.
130
+
131
+ - Variable support - ESI 1.0 supports the use of variables based on HTTP request attributes in a manner
132
+ reminiscent of the Common Gateway Interface. These variables can be used by ESI statements or written directly into the processed markup.
133
+
134
+ - Conditional processing - ESI allows conditional logic with Boolean comparisons to be used to influence how a template is processed.
135
+
136
+ + Exception and error handling - ESI provides for specification of alternate and default resources in a number of situations.
137
+
138
+
139
+
140
+ == HTTP Readings ==
141
+
142
+ * http://www.jmarshall.com/easy/http/
143
+
144
+
145
+ == Adding New Features and Bug Fixing ==
146
+
147
+ Before adding a new feature or bug fix, it's important to add a test to verify the behavior.
148
+
149
+ Also, help out by keeping the Changelog updated with new features and major bug fixes
150
+
151
+
152
+ == Future ideas of improving integration ==
153
+
154
+ NOTE: this is a work in progress, below is how I'd like to integrate with rails in the future
155
+
156
+ cd to/path/to/your/primary/rails/app
157
+
158
+ mongrel_esi add . --default
159
+ mongrel_esi add path/to/your/rails/secondary_app2
160
+ mongrel_esi add path/to/your/rails/secondary_app3
161
+ mongrel_esi add path/to/your/rails/secondary_app4
162
+
163
+ mongrel_esi start
164
+ - starts up mongrel in each of the applications
165
+
166
+
167
+ == Credits ==
168
+
169
+ Support:
170
+ Zed A. Shaw -- For writing [http://mongrel.rubyforge.org/ mongrel]
171
+ Adrian D. Thurston -- For writing [http://www.cs.queensu.ca/~thurston/ragel/ ragel]
172
+
173
+ The [http://www.ruby-lang.org/ ruby] community, for all their insights and help
174
+
175
+ Author:
176
+ Todd Fisher
177
+
178
+ Co-Authors:
179
+ Aaron Batalion -- Design ideas and integrating with Rails, fragment_fu. Ragel advocate
180
+ Richard Kilmer -- Thread saftey dispatcher, production ready
181
+ Adam Bair -- Writing the initial testing framework and logger.
182
+ Jeff Damick -- Writing the initial configuration.
183
+
184
+
185
+ And of course thanks to everyone in #ruby-lang for putting up with my questions
186
+ MenTaLguY for the great help in making good use of ruby
data/Rakefile ADDED
@@ -0,0 +1,141 @@
1
+ # much of this file orginated as part of mongrel
2
+ require 'rake'
3
+ require 'rake/testtask'
4
+ require 'rake/clean'
5
+ require 'rake/gempackagetask'
6
+ require 'rake/rdoctask'
7
+ require 'tools/rakehelp'
8
+ require 'fileutils'
9
+ include FileUtils
10
+
11
+ setup_tests
12
+
13
+ setup_clean ["ext/esi/*.{bundle,so,obj,pdb,lib,def,exp}", "ext/esi/Makefile", "pkg", "lib/*.bundle", "*.gem", "doc/site/output", ".config"]
14
+
15
+ setup_rdoc ['README', 'LICENSE', 'COPYING', 'lib/**/*.rb', 'doc/**/*.rdoc']
16
+
17
+ desc "Does a full compile, test run"
18
+ task :default => [:compile, :test]
19
+
20
+ desc "Compiles all extensions"
21
+ task :compile => [:esi] do
22
+ if Dir.glob(File.join("lib","esi.*")).length == 0
23
+ STDERR.puts "!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!"
24
+ STDERR.puts "Gem actually failed to build. Your system is"
25
+ STDERR.puts "NOT configured properly to build MongrelESI."
26
+ STDERR.puts "!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!"
27
+ exit(1)
28
+ end
29
+ end
30
+
31
+ setup_extension("esi", "esi")
32
+
33
+ task :package => [:clean,:compile,:test,:rerdoc]
34
+
35
+ namespace :ragel do
36
+ desc 'test the ragel version'
37
+ task :verify do
38
+ version = `ragel --version`.scan(/version ([0-9\.]+)/).first.first
39
+ if version < "5.24"
40
+ puts "You need to install a version of ragel greater or equal to 5.24"
41
+ exit(1)
42
+ else
43
+ puts "Using ragel #{version}"
44
+ end
45
+ end
46
+
47
+ desc 'generate the ruby ragel parser'
48
+ task :ruby => :verify do
49
+ Dir.chdir "ext/esi" do
50
+ sh "ragel -R ruby_esi.rl | rlgen-ruby -o machine.rb"
51
+ raise "Failed to build Ruby source" unless File.exist? "machine.rb"
52
+ end
53
+ end
54
+
55
+ desc 'generate the ragel parser'
56
+ task :gen => :verify do
57
+ Dir.chdir "ext/esi" do
58
+ sh "ragel parser.rl | rlgen-cd -G1 -o parser.c"
59
+ raise "Failed to build ESI parser source" unless File.exist? "parser.c"
60
+ end
61
+ end
62
+
63
+ desc 'generate the ruby ragel parser'
64
+ task :ruby => :verify do
65
+ Dir.chdir "ext/esi" do
66
+ sh "ragel -R ruby_esi.rl | rlgen-ruby -o machine.rb"
67
+ raise "Failed to build Ruby source" unless File.exist? "machine.rb"
68
+ end
69
+ end
70
+
71
+ desc 'generate a PNG graph of the parser'
72
+ task :graph => :verify do
73
+ Dir.chdir "ext/esi" do
74
+ sh 'ragel -R ruby_esi.rl | rlgen-dot -p > esi.dot'
75
+ sh 'dot -Tpng esi.dot -o ../../esi.png'
76
+ end
77
+ end
78
+ end
79
+
80
+ namespace :size do
81
+ desc 'Number of lines of code and tests'
82
+ task :measure => [:tests,:code]
83
+
84
+ desc 'Number of lines of tests'
85
+ task :tests do
86
+ sh 'find test/ -name "*.rb" | xargs wc -l'
87
+ end
88
+
89
+ desc 'Number of lines of code'
90
+ task :code do
91
+ sh 'find lib/ ext/ -name "*.r*" | grep -v svn | xargs wc -l'
92
+ end
93
+ end
94
+
95
+ name="mongrel_esi"
96
+ version="0.4.0"
97
+
98
+ setup_gem(name, version) do |spec|
99
+ spec.summary = "A small fast ESI HTTP Server built on top of Mongrel"
100
+ spec.description = spec.summary
101
+ spec.test_files = Dir.glob('test/test_*.rb')
102
+ spec.author="Todd A. Fisher"
103
+ spec.executables=['mongrel_esi']
104
+ spec.files += %w(COPYING LICENSE README Rakefile setup.rb)
105
+
106
+ spec.required_ruby_version = '>= 1.8.5'
107
+
108
+ if RUBY_PLATFORM =~ /mswin/
109
+ spec.platform = Gem::Platform::CURRENT
110
+ else
111
+ spec.add_dependency('daemons', '>= 1.0.3') # XXX: verify we are using this correctly
112
+ spec.add_dependency('fastthread', '>= 0.6.2') # mongrel needs it so do we
113
+ end
114
+
115
+ spec.add_dependency('hpricot', '>= 0.6') # used for invalidation protocol parsing
116
+ spec.add_dependency('memcache-client', '>= 1.5.0')
117
+ spec.add_dependency('cgi_multipart_eof_fix', '>= 1.0.0') # mongrel needs it so do we
118
+ spec.add_dependency('mongrel', '>= 1.0.1') # we need mongrel
119
+ end
120
+
121
+ task :install do
122
+ sh %{rake package}
123
+ sh %{gem install pkg/#{name}-#{version}}
124
+ end
125
+
126
+ task :uninstall => [:clean] do
127
+ sh %{gem uninstall #{name}}
128
+ end
129
+
130
+
131
+ task :gem_source do
132
+ mkdir_p "pkg/gems"
133
+
134
+ FileList["**/*.gem"].each { |gem| mv gem, "pkg/gems" }
135
+ FileList["pkg/*.tgz"].each {|tgz| rm tgz }
136
+ rm_rf "pkg/#{name}-#{version}"
137
+
138
+ sh %{ index_gem_repository.rb -d pkg }
139
+ # TODO: setup something like this
140
+ #sh %{ scp -r ChangeLog pkg/* rubyforge.org:/var/www/gforge-projects/mongrel/releases/ }
141
+ end
data/bin/mongrel_esi ADDED
@@ -0,0 +1,271 @@
1
+ #!/usr/bin/env ruby
2
+ # much of this file orginated as part of mongrel_rails
3
+ require 'yaml'
4
+ require 'rubygems'
5
+ require 'mongrel/handlers.rb'
6
+ require 'ostruct'
7
+
8
+ SERVER_ROOT=File.expand_path("#{File.dirname(__FILE__)}/../")
9
+ $: << "#{SERVER_ROOT}/lib"
10
+ $: << "#{SERVER_ROOT}/ext"
11
+ require "esi/dispatcher"
12
+
13
+ module Mongrel
14
+
15
+ class Start < GemPlugin::Plugin "/commands"
16
+ include Mongrel::Command::Base
17
+
18
+ def configure
19
+ options [
20
+ ['-d', '--daemonize', "Run daemonized in the background", :@daemon, false],
21
+ ['-p', '--port PORT', "Which port to bind to", :@port, 2000],
22
+ ['-a', '--address ADDR', "Address to bind to", :@address, "0.0.0.0"],
23
+ ['-l', '--log FILE', "Where to write log messages", :@log_file, "log/mongrel-esi.log"],
24
+ ['-P', '--pid FILE', "Where to write the PID", :@pid_file, "log/mongrel-esi.pid"],
25
+ ['-c', '--chdir PATH', "Change to dir before starting (will be expanded)", :@cwd, Dir.pwd],
26
+ ['-B', '--debug', "Enable debugging mode", :@debug, false],
27
+ ['-C', '--config PATH', "Use a config file", :@config_file, nil],
28
+ ['-S', '--script PATH', "Load the given file as an extra config script", :@config_script, nil],
29
+ ['-R', '--routes PATH', "Define simple routes /request_path1/:hostname:port, /request_path2/:hostname:port", :@routing, nil],
30
+ ['-s', '--cache TYPE', "Define the type of cache storage default is ruby, available caches are [memcached,ruby]", :@cache, 'ruby'],
31
+ ['', '--cache-options OPTIONS', "Options for selected cache server, ruby has non, memcached has port, servers, etc...", :@cache_options, {}],
32
+ ['-t', '--allowed-content-types TYPE', "List of content types to auto enable ESI processing", :@allowed_content_types, nil],
33
+ ['-o', '--enable-for-surrogate-only', "Only enable ESI processing with the surrgoate ESI header is present", :@enable_for_surrogate_only, false],
34
+ ['-i', '--enable-invalidator', "Start the invalidation server", :@invalidator, false],
35
+ ['', '--user USER', "User to run as", :@user, nil],
36
+ ['', '--group GROUP', "Group to run as", :@group, nil],
37
+ ['', '--prefix PATH', "URL prefix for cache server", :@prefix, nil]
38
+ ]
39
+ end
40
+
41
+ def validate
42
+ @cwd = File.expand_path(@cwd)
43
+ valid_dir? @cwd, "Invalid path to change to during daemon mode: #@cwd"
44
+
45
+ # Change there to start, then we'll have to come back after daemonize
46
+ Dir.chdir(@cwd)
47
+
48
+ valid?(@prefix[0].chr == "/" && @prefix[-1].chr != "/", "Prefix must begin with / and not end in /") if @prefix
49
+ valid_dir? File.dirname(@log_file), "Path to log file not valid: #@log_file"
50
+ valid_dir? File.dirname(@pid_file), "Path to pid file not valid: #@pid_file"
51
+ valid_exists? @mime_map, "MIME mapping file does not exist: #@mime_map" if @mime_map
52
+ valid_exists? @config_file, "Config file not there: #@config_file" if @config_file
53
+ valid_user? @user if @user
54
+ valid_group? @group if @group
55
+
56
+ valid?(['memcached','ruby'].include?(@cache), "Cache must be one of memcached or ruby" )
57
+
58
+ # parse routing rules
59
+ routes = []
60
+ if !@routing
61
+ # default routing rules
62
+ @routing = "default:127.0.0.1:3000"
63
+ end
64
+ @routing.split(',').each do|rule|
65
+ parts = rule.split(':')
66
+ if parts.size < 3
67
+ STDERR.puts "You must supply a url to match separated by a : the hostname and the port"
68
+ STDERR.puts "For default routing rules use the keyword 'default', such as default:hostname:port"
69
+ @valid = false
70
+ break
71
+ end
72
+ route = {}
73
+ route[:match_url] = parts[0]
74
+ route[:host] = parts[1]
75
+ route[:port] = parts[2]
76
+ route[:cache_ttl] = parts[3] if parts.size == 4
77
+ routes << route
78
+ end
79
+
80
+ @routing = routes
81
+
82
+ if @allowed_content_types
83
+ @allowed_content_types = @allowed_content_types.split(',').collect{|type| type}
84
+ end
85
+
86
+
87
+ return @valid
88
+ end
89
+
90
+ def run
91
+ # Config file settings will override command line settings
92
+ settings = { :host => @address, :port => @port, :cwd => @cwd,
93
+ :log_file => @log_file, :pid_file => @pid_file,
94
+ :daemon => @daemon, :debug => @debug, :includes => ["mongrel-esi"],
95
+ :user => @user, :group => @group, :prefix => @prefix, :config_file => @config_file,
96
+ :routing => @routing, :cache => @cache, :cache_options => OpenStruct.new( @cache_options ),
97
+ :allowed_content_types => @allowed_content_types, :config_script => @config_script,
98
+ :enable_for_surrogate_only => @enable_for_surrogate_only, :invalidator => @invalidator
99
+ }
100
+
101
+ if @config_file
102
+ settings.merge! YAML.load_file(@config_file)
103
+ STDERR.puts "** Loading settings from #{@config_file} (they override command line)." unless settings[:daemon]
104
+ end
105
+
106
+ config = Mongrel::Configurator.new(settings) do
107
+
108
+ if defaults[:daemon]
109
+ if File.exist? defaults[:pid_file]
110
+ log "!!! PID file #{defaults[:pid_file]} already exists. Mongrel could be running already. Check your #{defaults[:log_file]} for errors."
111
+ log "!!! Exiting with error. You must stop mongrel and clear the .pid before I'll attempt a start."
112
+ exit 1
113
+ end
114
+
115
+ daemonize
116
+ log "Daemonized, any open files are closed. Look at #{defaults[:pid_file]} and #{defaults[:log_file]} for info."
117
+ log "Settings loaded from #{@config_file} (they override command line)." if @config_file
118
+ end
119
+
120
+ log "Starting Mongrel listening at #{defaults[:host]}:#{defaults[:port]}"
121
+
122
+ listener do
123
+
124
+ mime = {}
125
+ if defaults[:mime_map]
126
+ log "Loading additional MIME types from #{defaults[:mime_map]}"
127
+ mime = load_mime_map(defaults[:mime_map], mime)
128
+ end
129
+
130
+ if defaults[:debug]
131
+ log "Installing debugging prefixed filters. Look in log/mongrel_debug for the files."
132
+ debug "/"
133
+ end
134
+
135
+ log "Mounting ESI at #{defaults[:prefix]}..." if defaults[:prefix]
136
+
137
+ uri defaults[:prefix] || "/", :handler => ESI::Dispatcher.new(settings)
138
+
139
+ log "Loading any ESI specific GemPlugins"
140
+ load_plugins
141
+
142
+ if defaults[:config_script]
143
+ log "Loading #{defaults[:config_script]} external config script"
144
+ run_config(defaults[:config_script])
145
+ end
146
+
147
+ setup_signals
148
+ end
149
+
150
+ end
151
+
152
+ config.run
153
+ config.log "Mongrel available at #{settings[:host]}:#{settings[:port]}"
154
+
155
+ if config.defaults[:daemon]
156
+ config.write_pid_file
157
+ else
158
+ config.log "Use CTRL-C to stop."
159
+ end
160
+
161
+ config.join
162
+
163
+ if config.needs_restart
164
+ if RUBY_PLATFORM !~ /mswin/
165
+ cmd = "ruby #{__FILE__} start #{original_args.join(' ')}"
166
+ config.log "Restarting with arguments: #{cmd}"
167
+ config.stop
168
+ config.remove_pid_file
169
+
170
+ if config.defaults[:daemon]
171
+ system cmd
172
+ else
173
+ STDERR.puts "Can't restart unless in daemon mode."
174
+ exit 1
175
+ end
176
+ else
177
+ config.log "Win32 does not support restarts. Exiting."
178
+ end
179
+ end
180
+ end
181
+
182
+ end
183
+
184
+ def Mongrel::send_signal(signal, pid_file)
185
+ pid = open(pid_file).read.to_i
186
+ print "Sending #{signal} to Mongrel at PID #{pid}..."
187
+ begin
188
+ Process.kill(signal, pid)
189
+ rescue Errno::ESRCH
190
+ puts "Process does not exist. Not running."
191
+ end
192
+
193
+ puts "Done."
194
+ end
195
+
196
+
197
+ class Stop < GemPlugin::Plugin "/commands"
198
+ include Mongrel::Command::Base
199
+
200
+ def configure
201
+ options [
202
+ ['-c', '--chdir PATH', "Change to dir before starting (will be expanded).", :@cwd, "."],
203
+ ['-f', '--force', "Force the shutdown (kill -9).", :@force, false],
204
+ ['-w', '--wait SECONDS', "Wait SECONDS before forcing shutdown", :@wait, "0"],
205
+ ['-P', '--pid FILE', "Where the PID file is located.", :@pid_file, "log/mongrel-esi.pid"]
206
+ ]
207
+ end
208
+
209
+ def validate
210
+ @cwd = File.expand_path(@cwd)
211
+ valid_dir? @cwd, "Invalid path to change to during daemon mode: #@cwd"
212
+
213
+ Dir.chdir @cwd
214
+
215
+ valid_exists? @pid_file, "PID file #@pid_file does not exist. Not running?"
216
+ return @valid
217
+ end
218
+
219
+ def run
220
+ if @force
221
+ @wait.to_i.times do |waiting|
222
+ exit(0) if not File.exist? @pid_file
223
+ sleep 1
224
+ end
225
+
226
+ Mongrel::send_signal("KILL", @pid_file) if File.exist? @pid_file
227
+ else
228
+ Mongrel::send_signal("TERM", @pid_file)
229
+ end
230
+ end
231
+ end
232
+
233
+ class Restart < GemPlugin::Plugin "/commands"
234
+ include Mongrel::Command::Base
235
+
236
+ def configure
237
+ options [
238
+ ['-c', '--chdir PATH', "Change to dir before starting (will be expanded)", :@cwd, '.'],
239
+ ['-s', '--soft', "Do a soft restart rather than a process exit restart", :@soft, false],
240
+ ['-P', '--pid FILE', "Where the PID file is located", :@pid_file, "log/mongrel-esi.pid"]
241
+ ]
242
+ end
243
+
244
+ def validate
245
+ @cwd = File.expand_path(@cwd)
246
+ valid_dir? @cwd, "Invalid path to change to during daemon mode: #@cwd"
247
+
248
+ Dir.chdir @cwd
249
+
250
+ valid_exists? @pid_file, "PID file #@pid_file does not exist. Not running?"
251
+ return @valid
252
+ end
253
+
254
+ def run
255
+ if @soft
256
+ Mongrel::send_signal("HUP", @pid_file)
257
+ else
258
+ Mongrel::send_signal("USR2", @pid_file)
259
+ end
260
+ end
261
+ end
262
+
263
+ end
264
+
265
+
266
+ GemPlugin::Manager.instance.load "mongrel-esi" => GemPlugin::INCLUDE, "esi" => GemPlugin::EXCLUDE
267
+
268
+
269
+ if not Mongrel::Command::Registry.instance.run ARGV
270
+ exit 1
271
+ end