ajp-rails 0.0.0

Sign up to get free protection for your applications and to get access to all the features.
data/bin/ajp-rails.rb ADDED
@@ -0,0 +1,4 @@
1
+ #!/usr/bin/ruby
2
+
3
+ require 'rubygems' rescue nil
4
+ load 'ajp-rails/rails-runner.rb'
@@ -0,0 +1,5 @@
1
+ host: localhost
2
+ port: 3009
3
+ location: /example
4
+ prefix: /ajp-mounted
5
+ directory: /path/to/app
@@ -0,0 +1,30 @@
1
+
2
+ # Assumes that follwing are set in ServerConfig or VirtualHost
3
+ # JkMount /ajp-mounted/* ajprails
4
+ # JkEnvVar SERVER_SOFTWARE NONE
5
+
6
+
7
+ Options +FollowSymLinks
8
+
9
+ # If you don't want Rails to look in certain directories,
10
+ # use the following rewrite rules so that Apache won't rewrite certain requests
11
+ #
12
+ # Example:
13
+ # RewriteCond %{REQUEST_URI} ^/notrails.*
14
+ # RewriteRule .* - [L]
15
+
16
+ # Redirect all requests not available on the filesystem to Rails
17
+ RewriteEngine On
18
+ RewriteBase /example
19
+ RewriteRule ^$ index.html [QSA]
20
+ RewriteRule ^([^.]+)$ $1.html [QSA]
21
+ RewriteCond %{REQUEST_FILENAME} !-f
22
+ RewriteRule ^(.*)$ /ajp-mounted/$1 [QSA,L]
23
+
24
+ # In case Rails experiences terminal errors
25
+ # Instead of displaying this message you can supply a file here which will be rendered instead
26
+ #
27
+ # Example:
28
+ # ErrorDocument 500 /500.html
29
+
30
+ ErrorDocument 500 "<h2>Application error</h2>Rails application failed to start properly"
@@ -0,0 +1,11 @@
1
+ # Comma separated worker names
2
+ worker.list=jkstatus,ajprails
3
+
4
+ # Definition for Ajp13 worker
5
+ #
6
+
7
+ worker.ajprails.type=ajp13
8
+ worker.ajprails.port=3009
9
+ worker.ajprails.host=localhost
10
+
11
+ worker.jkstatus.type=status
@@ -0,0 +1,40 @@
1
+ require 'dispatcher'
2
+ require 'net/ajp13/server'
3
+ require File.dirname(__FILE__) + File::SEPARATOR + 'rails-wrapper.rb'
4
+
5
+ class AjpRailsDispatcher < Dispatcher
6
+ class << self
7
+ # Dispatch the given AJP request, using the given session options, and
8
+ # returns the response object.
9
+ #--
10
+ # Be sure to handle the exceptions it raises
11
+ # on multipart requests (EOFError and ArgumentError).
12
+ #++
13
+ def dispatch(ajp_req, session_options, server_environments)
14
+ request = AjpRailsRequest.new(ajp_req, session_options, server_environments)
15
+ response = AjpRailsResponse.new
16
+ prepare_application
17
+ ActionController::Routing::Routes.recognize!(request).process(request, response).to_ajp_response(ajp_req.output_cookies)
18
+ rescue Object => exception
19
+ puts exception.message + ":" + exception.backtrace.join("\n")
20
+ failsafe_response(500) do
21
+ ActionController::Base.process_with_exception(request, AjpRailsResponse.new, exception).to_ajp_response(ajp_req.output_cookies)
22
+ end
23
+ ensure
24
+ # Do not give a failsafe response here.
25
+ reset_after_dispatch
26
+ end
27
+
28
+ # If the block raises, send status code as a last-ditch response.
29
+ def failsafe_response(status)
30
+ yield
31
+ rescue Object
32
+ begin
33
+ Net::AJP13::Response.new(status)
34
+ rescue Object
35
+ end
36
+ end
37
+ end
38
+ end
39
+
40
+
@@ -0,0 +1,162 @@
1
+ #--
2
+ # Copyright (c) 2006 Yugui <yugui@yugui.sakura.ne.jp>
3
+ #
4
+ # This program is free software; you can redistribute it and/or
5
+ # modify it under the terms of the GNU Lesser General Public
6
+ # License as published by the Free Software Foundation; version 2.1
7
+ # of the License
8
+ #
9
+ # This program is distributed in the hope that it will be useful,
10
+ # but WITHOUT ANY WARRANTY; without even the implied warranty of
11
+ # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
12
+ # Lesser General Public License for more details.
13
+ #
14
+ # You should have received a copy of the GNU Lesser General Public
15
+ # License along with this program; if not, write to the Free Software
16
+ # Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
17
+ #++
18
+ require 'rubygems' rescue nil
19
+ require 'optparse'
20
+ require File.dirname(__FILE__) + File::SEPARATOR + 'ajp_rails_dispatcher.rb'
21
+
22
+ #
23
+ # AJP server that dispatches AJP requests into Rails.
24
+ #
25
+ class RailsRunner < Net::AJP13::Server
26
+ # +option+:: Hash which contains server-wide options.
27
+ def initialize(options)
28
+ super(options['BIND_IP'], options['AJP_PORT'])
29
+ @environment_options = options
30
+ end
31
+
32
+ # Dispatches +request+ into Rails and returns Net::AJP13::Response object
33
+ # which contains the result of Rails processing.
34
+ #
35
+ # +request+:: Net::AJP13::Request object
36
+ def process_request(request)
37
+ logger.debug("processing #{request.path}")
38
+ response = AjpRailsDispatcher.dispatch(request, AjpRailsRequest::DEFAULT_SESSION_OPTIONS, @environment_options)
39
+ logger.debug("processed #{request.path}: #{response.code} #{response.message}")
40
+ response
41
+ end
42
+ end
43
+
44
+ # Maps long option names to internal hash keys.
45
+ OPTION_NAMES_TO_INTERNAL_NAMES = {
46
+ 'port' => 'AJP_PORT',
47
+ 'host' => 'BIND_IP',
48
+ 'environment' => 'RAILS_ENV',
49
+ 'location' => 'APP_LOCATION',
50
+ 'directory' => 'APP_DIRECTORY',
51
+ 'prefix' => 'DISPATCHER_PREFIX',
52
+ 'suffix' => 'DISPATCHER_SUFFIX',
53
+ 'daemon' => 'IS_DAEMON'
54
+ }
55
+
56
+ DEFAULT_OPTIONS = {
57
+ OPTION_NAMES_TO_INTERNAL_NAMES['environment'] =>
58
+ ENV['RAILS_ENV'] || 'production',
59
+ OPTION_NAMES_TO_INTERNAL_NAMES['port'] => Net::AJP13::Constants::DEFAULT_PORT,
60
+ OPTION_NAMES_TO_INTERNAL_NAMES['host'] => '127.0.0.1',
61
+ OPTION_NAMES_TO_INTERNAL_NAMES['location'] => '/',
62
+ OPTION_NAMES_TO_INTERNAL_NAMES['directory'] => '.'
63
+ }
64
+
65
+ # Parses command line options.
66
+ #
67
+ # Merges external configuration file into DEFAULT_OPTIONS if a file specified,
68
+ # and merges command line options, and returns merged options.
69
+ #
70
+ def parse_options
71
+ opts = DEFAULT_OPTIONS.dup
72
+
73
+ cmd_opts = {}
74
+ parser = OptionParser.new
75
+ parser.on('-p PORT', '--port=PORT',
76
+ "Listens to the specified port.\n" +
77
+ "Default: #{opts[OPTION_NAMES_TO_INTERNAL_NAMES['port']]}") { |v|
78
+ cmd_opts[OPTION_NAMES_TO_INTERNAL_NAMES['port']] = v
79
+ }
80
+ parser.on('-h IP', '--host=IP',
81
+ "Binds rails to the specified ip.\n" +
82
+ "Default: #{opts[OPTION_NAMES_TO_INTERNAL_NAMES['host']]}") {
83
+ cmd_opts[OPTION_NAMES_TO_INTERNAL_NAMES['host']] = v
84
+ }
85
+ parser.on('-e RAILS_ENV', '--environment=RAILS_ENV',
86
+ "Specifies the environment to run this server under (test/development/production).\n" +
87
+ "Default: ENV['RAILS_ENV'] || 'production'") { |v|
88
+ cmd_opts[OPTION_NAMES_TO_INTERNAL_NAMES['environment']] = v
89
+ }
90
+ parser.on('-l LOCATION', '--location=LOCATION',
91
+ "The base of the application's virtual path.\n" +
92
+ "Default: /") { |v|
93
+ cmd_opts[OPTION_NAMES_TO_INTERNAL_NAMES['location']] = v
94
+ }
95
+ parser.on('-d DIRECTORY', '--directory=DIRECTORY',
96
+ "The base of the application's physical path\n" +
97
+ "Default: #{opts[OPTION_NAMES_TO_INTERNAL_NAMES['directory']]}") { |v|
98
+ cmd_opts[OPTION_NAMES_TO_INTERNAL_NAMES['directory']] = v
99
+ }
100
+ parser.on('--prefix=PREFIX',
101
+ "The prefix of the ajp-mounted URLs.") { |v|
102
+ cmd_opts[OPTION_NAMES_TO_INTERNAL_NAMES['prefix']] = v
103
+ }
104
+ parser.on('--suffix=SUFFIX',
105
+ "The suffix of the ajp-mounted URLs.") { |v|
106
+ cmd_opts[OPTION_NAMES_TO_INTERNAL_NAMES['suffix']] = v
107
+ }
108
+ parser.on('--daemon', "Makes Rails run as a daemon") {|v|
109
+ cmd_opts[OPTION_NAMES_TO_INTERNAL_NAMES['daemon']] = v
110
+ }
111
+
112
+ parser.on('-c FILE', '--config=FILE',
113
+ 'Reads options from the specified file. ' +
114
+ 'The file must be YAML file.') {|value|
115
+ cmd_opts['config'] = value
116
+ }
117
+ parser.parse(ARGV)
118
+
119
+ if path = cmd_opts['config']
120
+ cmd_opts.delete('config')
121
+ if FileTest::file? path then
122
+ require 'yaml'
123
+ YAML.load_file(path).each do |key, value|
124
+ name = OPTION_NAMES_TO_INTERNAL_NAMES[key]
125
+ opts[name || key] = value
126
+ end
127
+ else
128
+ warn("Not a exsiting regular file: #{path}")
129
+ exit(1)
130
+ end
131
+ end
132
+
133
+ opts.merge(cmd_opts)
134
+ end
135
+
136
+
137
+ options = parse_options
138
+
139
+ if is_daemon = options[OPTION_NAMES_TO_INTERNAL_NAMES['daemon']]
140
+ options.delete(OPTION_NAMES_TO_INTERNAL_NAMES['daemon'])
141
+
142
+ exit if Process.fork
143
+ Process.setsid
144
+ exit if Process.fork
145
+ File.umask(0)
146
+ end
147
+
148
+ raise if defined?(RAILS_ENV)
149
+ ::RAILS_ENV = options['RAILS_ENV'].dup
150
+
151
+ Dir.chdir options[OPTION_NAMES_TO_INTERNAL_NAMES['directory']]
152
+ require 'config/environment'
153
+
154
+ if is_daemon
155
+ Dir.chdir('/')
156
+ STDIN.reopen('/dev/null')
157
+ STDOUT.reopen('/dev/null', 'w')
158
+ STDERR.reopen('/dev/null', 'w')
159
+ end
160
+
161
+ runner = RailsRunner.new(options)
162
+ runner.start
@@ -0,0 +1,289 @@
1
+ #--
2
+ # Copyright (c) 2004 David Heinemeier Hansson
3
+ # Copyright (c) 2006 Yugui <yugui@yugui.sakura.ne.jp>
4
+ #
5
+ # This program is free software; you can redistribute it and/or
6
+ # modify it under the terms of the GNU Lesser General Public
7
+ # License as published by the Free Software Foundation; version 2.1
8
+ # of the License
9
+ #
10
+ # This program is distributed in the hope that it will be useful,
11
+ # but WITHOUT ANY WARRANTY; without even the implied warranty of
12
+ # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
13
+ # Lesser General Public License for more details.
14
+ #
15
+ # You should have received a copy of the GNU Lesser General Public
16
+ # License along with this program; if not, write to the Free Software
17
+ # Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
18
+ #
19
+ #--
20
+ #
21
+ # AjpRailsRequest is based on lib/action_controller/cgi_process.rb which is
22
+ # distributed as a port of actionpack 1.1.12.
23
+ # The original file is avaiable here;
24
+ # http://rubyforge.org/frs/?group_id=249&release_id=3791
25
+ #
26
+ # The following is the permission notice of the original
27
+ # lib/action_controller/cgi_process.rb, not for this script.
28
+ #
29
+ # --
30
+ # Copyright (c) 2004 David Heinemeier Hansson
31
+ #
32
+ # Permission is hereby granted, free of charge, to any person obtaining
33
+ # a copy of this software and associated documentation files (the
34
+ # "Software"), to deal in the Software without restriction, including
35
+ # without limitation the rights to use, copy, modify, merge, publish,
36
+ # distribute, sublicense, and/or sell copies of the Software, and to
37
+ # permit persons to whom the Software is furnished to do so, subject to
38
+ # the following conditions:
39
+ #
40
+ # The above copyright notice and this permission notice shall be
41
+ # included in all copies or substantial portions of the Software.
42
+ #
43
+ # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
44
+ # EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
45
+ # MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
46
+ # NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
47
+ # LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
48
+ # OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
49
+ # WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
50
+ #++
51
+
52
+ require 'forwardable'
53
+ require 'action_controller/cgi_ext/cgi_methods'
54
+
55
+ class Net::AJP13::Request
56
+ def cookies
57
+ unless @cookies
58
+ @cookies = {}
59
+ cookie_strings = self.get_fields('cookie')
60
+ cookie_strings.each do |cookie_string|
61
+ @cookies.update(CGI::Cookie::parse(cookie_string))
62
+ end if cookie_strings
63
+ @cookies.freeze
64
+ end
65
+ @cookies
66
+ end
67
+
68
+ attr_reader :output_cookies
69
+ end
70
+
71
+ # Wraps Net::AJP13::Request to adapt it to request object in rails.
72
+ class AjpRailsRequest < ActionController::AbstractRequest
73
+ DEFAULT_SESSION_OPTIONS = ActionController::CgiRequest::DEFAULT_SESSION_OPTIONS.merge('session_key' => 'JSESSIONID')
74
+
75
+ extend Forwardable
76
+
77
+ def initialize(ajp_request, session_options, server_environments)
78
+ @req = ajp_request
79
+ @session_options = session_options
80
+ @server_environments = server_environments
81
+ @session_options['session_path'] ||= @server_environments['APP_LOCATION']
82
+
83
+ # simulates environment hash.
84
+ @env = self.class.instance_method(:environment).bind(self)
85
+ # Object#method is overridden by AbstractRequest#method
86
+
87
+ class << @env
88
+ def include?(key) !call(key).nil? end
89
+ alias :key? :include?
90
+ end
91
+ end
92
+ attr_reader :env
93
+
94
+ attr_accessor :session_options
95
+
96
+ def method
97
+ @req.method.downcase.to_sym
98
+ end
99
+
100
+ def environment(name)
101
+ case name
102
+ when /^\AHTTP_(\w+)\Z/
103
+ name = $1.tr('_', '-')
104
+ @req[name]
105
+ else
106
+ @req[name] or @server_environments[name]
107
+ end
108
+ end
109
+
110
+ def raw_post
111
+ if @req.body
112
+ @req.body
113
+ elsif @req.body_stream
114
+ stream = @req.body_stream
115
+ @req.body_stream = nil
116
+ @req.body = stream.read
117
+ else
118
+ ''
119
+ end
120
+ end
121
+
122
+ def path
123
+ path = @req.path
124
+ path = path.sub(%r!\A#{Regexp.escape(@server_environments['DISPATCHER_PREFIX'])}!, '') if @server_environments['DISPATCHER_PREFIX']
125
+ path = path.sub(%r!#{Regexp.escape(@server_environments['DISPATCHER_SUFFIX'])}\Z!, '') if @server_environments['DISPATCHER_SUFFIX']
126
+ path
127
+ end
128
+
129
+ def request_uri
130
+ if @request_uri
131
+ @request_uri
132
+ else
133
+ uri = path
134
+ qs = query_string
135
+ uri << '?' << qs[0] if qs
136
+ @request_uri = uri
137
+ end
138
+ end
139
+
140
+ def_delegators :@req, :ssl?, :cookies
141
+
142
+ def server_software
143
+ val = @req.get_attributes('server_software')
144
+ val and val[0] and /([A-Za-z]+)/ =~ val[0] and $1.downcase
145
+ end
146
+
147
+ def query_string
148
+ val = @req.get_attributes('query_string')
149
+ val and val[0]
150
+ end
151
+
152
+ def query_parameters
153
+ (qs = self.query_string).blank? ? {} : CGIMethods.parse_query_parameters(qs)
154
+ end
155
+
156
+ def post_params
157
+ @post_params ||= CGI.parse(raw_post)
158
+ @post_params
159
+ end
160
+
161
+ def request_parameters
162
+ if formatted_post?
163
+ CGIMethods.parse_formatted_request_parameters(post_format, raw_post)
164
+ else
165
+ CGIMethods.parse_request_parameters(self.post_params)
166
+ end
167
+ end
168
+
169
+ def host
170
+ @req['x-forwarded-host'] ||
171
+ ($1 if @req.server_name and /\A(.*):\d+\Z/ =~ @req.server_name) ||
172
+ (@req.server_name and @req.server_name.split(':').first) ||
173
+ ($1 if @req['host'] and /\A(.*):\d+\Z/ =~ @req['host']) ||
174
+ (@req['host'] and @req['host'].split(':').first) ||
175
+ ''
176
+ end
177
+ def port
178
+ @req['x-forwarded-host'] ? standard_port : (port_from_http_host || @req.server_port)
179
+ end
180
+
181
+ def port_from_http_host
182
+ $1.to_i if @req['host'] && /:(\d+)$/ =~ @req['host']
183
+ end
184
+
185
+ def session
186
+ unless @session
187
+ if @session_options == false
188
+ @session = Hash.new
189
+ else
190
+ stale_session_check! do
191
+ if session_options_with_string_keys['new_session'] == true
192
+ @session = new_session
193
+ else
194
+ @session = CGI::Session.new(@req, session_options_with_string_keys)
195
+ end
196
+ @session['__valid_session']
197
+ end
198
+ end
199
+ end
200
+ @session
201
+ end
202
+
203
+ def reset_session
204
+ @session.delete if CGI::Session === @session
205
+ @session = new_session
206
+ end
207
+
208
+ private
209
+ # Delete an old session if it exists then create a new one.
210
+ def new_session
211
+ if @session_options == false
212
+ Hash.new
213
+ else
214
+ CGI::Session.new(@req, session_options_with_string_keys.merge("new_session" => false)).delete rescue nil
215
+ CGI::Session.new(@req, session_options_with_string_keys.merge("new_session" => true))
216
+ end
217
+ end
218
+
219
+ def stale_session_check!
220
+ yield
221
+ rescue ArgumentError => argument_error
222
+ if argument_error.message =~ %r{undefined class/module (\w+)}
223
+ begin
224
+ Module.const_missing($1)
225
+ rescue LoadError, NameError => const_error
226
+ raise ActionController::SessionRestoreError, <<end_msg
227
+ Session contains objects whose class definition isn't available.
228
+ Remember to require the classes for all objects kept in the session.
229
+ (Original exception: #{const_error.message} [#{const_error.class}])
230
+ end_msg
231
+ end
232
+ retry
233
+ else
234
+ raise
235
+ end
236
+ end
237
+ def session_options_with_string_keys
238
+ @session_options_with_string_keys ||= DEFAULT_SESSION_OPTIONS.merge(@session_options).inject({}) { |options, (k,v)| options[k.to_s] = v; options }
239
+ end
240
+ end
241
+
242
+ class AjpRailsResponse < ActionController::AbstractResponse
243
+ def to_ajp_response(extra_cookies = nil)
244
+ raise "Unrecognized status line #{headers['Status']}" unless
245
+ md = /\A(\d{3})(?: (.+))?/.match(headers['Status'])
246
+
247
+ res = md[2] ?
248
+ Net::AJP13::Response.new(md[1].to_i, :reason_phrase => md[2]) :
249
+ Net::AJP13::Response.new(md[1])
250
+
251
+ @headers.each do |key, value|
252
+ case key.downcase
253
+ when 'status'
254
+ # do nothing
255
+ when 'expires'
256
+ res.add_field(key, CGI::rfc1123_date(options.delete(key)))
257
+ when 'cookie'
258
+ case value
259
+ when String
260
+ res.add_field('Set-Cookie', value)
261
+ when CGI::Cookie
262
+ res.add_field('Set-Cookie', value.to_s)
263
+ when Array
264
+ value.each {|cookie| res.add_field('Set-Cookie', cookie) }
265
+ when Hash
266
+ value.each_value {|cookie| res.add_field('Set-Cookie', cookie) }
267
+ end
268
+ else
269
+ res.add_field(key, value.to_s) if key != 'Status'
270
+ end
271
+ end
272
+ res['content-type'] ||= 'text/html'
273
+ if extra_cookies
274
+ extra_cookies.each do |cookie|
275
+ res.add_field('Set-Cookie', cookie.to_s)
276
+ end
277
+ end
278
+
279
+ if @body.respond_to?(:call)
280
+ buf = ''
281
+ @body.call(self, StringIO.new(buf))
282
+ res.body = buf
283
+ else
284
+ res.body = self.body
285
+ end
286
+
287
+ return res
288
+ end
289
+ end