mongrel_esi 0.4.0

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 (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