ajp-rails 0.0.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.
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