unicorn 0.2.3 → 0.4.1
Sign up to get free protection for your applications and to get access to all the features.
- data/.document +1 -1
- data/.gitignore +1 -0
- data/CHANGELOG +1 -0
- data/DESIGN +4 -0
- data/GNUmakefile +30 -6
- data/Manifest +62 -3
- data/README +52 -42
- data/SIGNALS +17 -17
- data/TODO +27 -5
- data/bin/unicorn +15 -13
- data/bin/unicorn_rails +59 -22
- data/ext/unicorn/http11/http11.c +25 -104
- data/ext/unicorn/http11/http11_parser.c +24 -23
- data/ext/unicorn/http11/http11_parser.h +1 -3
- data/ext/unicorn/http11/http11_parser.rl +2 -1
- data/lib/unicorn.rb +58 -44
- data/lib/unicorn/app/old_rails.rb +23 -0
- data/lib/unicorn/app/old_rails/static.rb +58 -0
- data/lib/unicorn/cgi_wrapper.rb +151 -0
- data/lib/unicorn/configurator.rb +71 -31
- data/lib/unicorn/const.rb +9 -34
- data/lib/unicorn/http_request.rb +63 -66
- data/lib/unicorn/http_response.rb +6 -1
- data/lib/unicorn/socket.rb +15 -2
- data/test/benchmark/README +55 -0
- data/test/benchmark/big_request.rb +35 -0
- data/test/benchmark/dd.ru +18 -0
- data/test/benchmark/request.rb +47 -0
- data/test/benchmark/response.rb +29 -0
- data/test/exec/test_exec.rb +41 -157
- data/test/rails/app-1.2.3/.gitignore +2 -0
- data/test/rails/app-1.2.3/app/controllers/application.rb +4 -0
- data/test/rails/app-1.2.3/app/controllers/foo_controller.rb +34 -0
- data/test/rails/app-1.2.3/app/helpers/application_helper.rb +2 -0
- data/test/rails/app-1.2.3/config/boot.rb +9 -0
- data/test/rails/app-1.2.3/config/database.yml +12 -0
- data/test/rails/app-1.2.3/config/environment.rb +10 -0
- data/test/rails/app-1.2.3/config/environments/development.rb +7 -0
- data/test/rails/app-1.2.3/config/environments/production.rb +3 -0
- data/test/rails/app-1.2.3/config/routes.rb +4 -0
- data/test/rails/app-1.2.3/db/.gitignore +0 -0
- data/test/rails/app-1.2.3/public/404.html +1 -0
- data/test/rails/app-1.2.3/public/500.html +1 -0
- data/test/rails/app-2.0.2/.gitignore +2 -0
- data/test/rails/app-2.0.2/app/controllers/application.rb +2 -0
- data/test/rails/app-2.0.2/app/controllers/foo_controller.rb +34 -0
- data/test/rails/app-2.0.2/app/helpers/application_helper.rb +2 -0
- data/test/rails/app-2.0.2/config/boot.rb +9 -0
- data/test/rails/app-2.0.2/config/database.yml +12 -0
- data/test/rails/app-2.0.2/config/environment.rb +14 -0
- data/test/rails/app-2.0.2/config/environments/development.rb +6 -0
- data/test/rails/app-2.0.2/config/environments/production.rb +3 -0
- data/test/rails/app-2.0.2/config/routes.rb +4 -0
- data/test/rails/app-2.0.2/db/.gitignore +0 -0
- data/test/rails/app-2.0.2/public/404.html +1 -0
- data/test/rails/app-2.0.2/public/500.html +1 -0
- data/test/rails/app-2.2.2/.gitignore +2 -0
- data/test/rails/app-2.2.2/app/controllers/application.rb +2 -0
- data/test/rails/app-2.2.2/app/controllers/foo_controller.rb +34 -0
- data/test/rails/app-2.2.2/app/helpers/application_helper.rb +2 -0
- data/test/rails/app-2.2.2/config/boot.rb +109 -0
- data/test/rails/app-2.2.2/config/database.yml +12 -0
- data/test/rails/app-2.2.2/config/environment.rb +14 -0
- data/test/rails/app-2.2.2/config/environments/development.rb +5 -0
- data/test/rails/app-2.2.2/config/environments/production.rb +3 -0
- data/test/rails/app-2.2.2/config/routes.rb +4 -0
- data/test/rails/app-2.2.2/db/.gitignore +0 -0
- data/test/rails/app-2.2.2/public/404.html +1 -0
- data/test/rails/app-2.2.2/public/500.html +1 -0
- data/test/rails/app-2.3.2.1/.gitignore +2 -0
- data/test/rails/app-2.3.2.1/app/controllers/application_controller.rb +3 -0
- data/test/rails/app-2.3.2.1/app/controllers/foo_controller.rb +34 -0
- data/test/rails/app-2.3.2.1/app/helpers/application_helper.rb +2 -0
- data/test/rails/app-2.3.2.1/config/boot.rb +107 -0
- data/test/rails/app-2.3.2.1/config/database.yml +12 -0
- data/test/rails/app-2.3.2.1/config/environment.rb +14 -0
- data/test/rails/app-2.3.2.1/config/environments/development.rb +5 -0
- data/test/rails/app-2.3.2.1/config/environments/production.rb +4 -0
- data/test/rails/app-2.3.2.1/config/routes.rb +4 -0
- data/test/rails/app-2.3.2.1/db/.gitignore +0 -0
- data/test/rails/app-2.3.2.1/public/404.html +1 -0
- data/test/rails/app-2.3.2.1/public/500.html +1 -0
- data/test/rails/test_rails.rb +243 -0
- data/test/test_helper.rb +149 -2
- data/test/unit/test_configurator.rb +46 -0
- data/test/unit/test_http_parser.rb +77 -36
- data/test/unit/test_request.rb +2 -0
- data/test/unit/test_response.rb +20 -4
- data/test/unit/test_server.rb +30 -1
- data/test/unit/test_socket_helper.rb +159 -0
- data/unicorn.gemspec +5 -5
- metadata +68 -5
- data/test/benchmark/previous.rb +0 -11
- data/test/benchmark/simple.rb +0 -11
- data/test/benchmark/utils.rb +0 -82
@@ -36,10 +36,8 @@ typedef struct http_parser {
|
|
36
36
|
|
37
37
|
int http_parser_init(http_parser *parser);
|
38
38
|
int http_parser_finish(http_parser *parser);
|
39
|
-
size_t http_parser_execute(http_parser *parser, const char *data, size_t len
|
39
|
+
size_t http_parser_execute(http_parser *parser, const char *data, size_t len);
|
40
40
|
int http_parser_has_error(http_parser *parser);
|
41
41
|
int http_parser_is_finished(http_parser *parser);
|
42
42
|
|
43
|
-
#define http_parser_nread(parser) (parser)->nread
|
44
|
-
|
45
43
|
#endif
|
@@ -105,9 +105,10 @@ int http_parser_init(http_parser *parser) {
|
|
105
105
|
|
106
106
|
|
107
107
|
/** exec **/
|
108
|
-
size_t http_parser_execute(http_parser *parser, const char *buffer, size_t len
|
108
|
+
size_t http_parser_execute(http_parser *parser, const char *buffer, size_t len) {
|
109
109
|
const char *p, *pe;
|
110
110
|
int cs = parser->cs;
|
111
|
+
size_t off = parser->nread;
|
111
112
|
|
112
113
|
assert(off <= len && "offset past end of buffer");
|
113
114
|
|
data/lib/unicorn.rb
CHANGED
@@ -59,6 +59,7 @@ module Unicorn
|
|
59
59
|
@request = @rd_sig = @wr_sig = nil
|
60
60
|
@reexec_pid = 0
|
61
61
|
@config = Configurator.new(options.merge(:use_defaults => true))
|
62
|
+
@listener_opts = {}
|
62
63
|
@config.commit!(self, :skip => [:listeners, :pid])
|
63
64
|
@listeners = []
|
64
65
|
end
|
@@ -85,6 +86,9 @@ module Unicorn
|
|
85
86
|
# share the same OS-level file descriptor as the higher-level *Server
|
86
87
|
# objects; we need to prevent Socket objects from being garbage-collected
|
87
88
|
config_listeners -= listener_names
|
89
|
+
if config_listeners.empty? && @listeners.empty?
|
90
|
+
config_listeners << Unicorn::Const::DEFAULT_LISTEN
|
91
|
+
end
|
88
92
|
config_listeners.each { |addr| listen(addr) }
|
89
93
|
raise ArgumentError, "no listeners" if @listeners.empty?
|
90
94
|
self.pid = @config[:pid]
|
@@ -123,19 +127,19 @@ module Unicorn
|
|
123
127
|
raise ArgumentError, "Already running on PID:#{x} " \
|
124
128
|
"(or pid=#{path} is stale)"
|
125
129
|
end
|
126
|
-
File.open(path, 'wb') { |fp| fp.syswrite("#{$$}\n") }
|
127
130
|
end
|
128
|
-
unlink_pid_safe(@pid) if @pid
|
131
|
+
unlink_pid_safe(@pid) if @pid
|
132
|
+
File.open(path, 'wb') { |fp| fp.syswrite("#$$\n") } if path
|
129
133
|
@pid = path
|
130
134
|
end
|
131
135
|
|
132
136
|
# add a given address to the +listeners+ set, idempotently
|
133
137
|
# Allows workers to add a private, per-process listener via the
|
134
138
|
# @after_fork hook. Very useful for debugging and testing.
|
135
|
-
def listen(address)
|
139
|
+
def listen(address, opt = {}.merge(@listener_opts[address] || {}))
|
136
140
|
return if String === address && listener_names.include?(address)
|
137
141
|
|
138
|
-
if io = bind_listen(address,
|
142
|
+
if io = bind_listen(address, opt)
|
139
143
|
if Socket == io.class
|
140
144
|
@io_purgatory << io
|
141
145
|
io = server_cast(io)
|
@@ -157,12 +161,11 @@ module Unicorn
|
|
157
161
|
# this pipe is used to wake us up from select(2) in #join when signals
|
158
162
|
# are trapped. See trap_deferred
|
159
163
|
@rd_sig, @wr_sig = IO.pipe unless (@rd_sig && @wr_sig)
|
160
|
-
@rd_sig.nonblock = @wr_sig.nonblock = true
|
161
164
|
mode = nil
|
162
165
|
respawn = true
|
163
166
|
|
164
167
|
QUEUE_SIGS.each { |sig| trap_deferred(sig) }
|
165
|
-
trap(
|
168
|
+
trap(:CHLD) { |sig_nr| awaken_master }
|
166
169
|
$0 = "unicorn master"
|
167
170
|
logger.info "master process ready" # test_exec.rb relies on this message
|
168
171
|
begin
|
@@ -173,27 +176,27 @@ module Unicorn
|
|
173
176
|
murder_lazy_workers
|
174
177
|
spawn_missing_workers if respawn
|
175
178
|
master_sleep
|
176
|
-
when
|
179
|
+
when :QUIT # graceful shutdown
|
177
180
|
break
|
178
|
-
when
|
181
|
+
when :TERM, :INT # immediate shutdown
|
179
182
|
stop(false)
|
180
183
|
break
|
181
|
-
when
|
184
|
+
when :USR1 # rotate logs
|
182
185
|
logger.info "master rotating logs..."
|
183
186
|
Unicorn::Util.reopen_logs
|
184
187
|
logger.info "master done rotating logs"
|
185
|
-
kill_each_worker(
|
186
|
-
when
|
188
|
+
kill_each_worker(:USR1)
|
189
|
+
when :USR2 # exec binary, stay alive in case something went wrong
|
187
190
|
reexec
|
188
|
-
when
|
191
|
+
when :WINCH
|
189
192
|
if Process.ppid == 1 || Process.getpgrp != $$
|
190
193
|
respawn = false
|
191
194
|
logger.info "gracefully stopping all workers"
|
192
|
-
kill_each_worker(
|
195
|
+
kill_each_worker(:QUIT)
|
193
196
|
else
|
194
197
|
logger.info "SIGWINCH ignored because we're not daemonized"
|
195
198
|
end
|
196
|
-
when
|
199
|
+
when :HUP
|
197
200
|
respawn = true
|
198
201
|
if @config.config_file
|
199
202
|
load_config!
|
@@ -221,7 +224,7 @@ module Unicorn
|
|
221
224
|
|
222
225
|
# Terminates all workers, but does not exit master process
|
223
226
|
def stop(graceful = true)
|
224
|
-
kill_each_worker(graceful ?
|
227
|
+
kill_each_worker(graceful ? :QUIT : :TERM)
|
225
228
|
timeleft = @timeout
|
226
229
|
step = 0.2
|
227
230
|
reap_all_workers
|
@@ -229,7 +232,7 @@ module Unicorn
|
|
229
232
|
sleep(step)
|
230
233
|
reap_all_workers
|
231
234
|
(timeleft -= step) > 0 and next
|
232
|
-
kill_each_worker(
|
235
|
+
kill_each_worker(:KILL)
|
233
236
|
end
|
234
237
|
ensure
|
235
238
|
self.listeners = []
|
@@ -238,8 +241,7 @@ module Unicorn
|
|
238
241
|
private
|
239
242
|
|
240
243
|
# list of signals we care about and trap in master.
|
241
|
-
QUEUE_SIGS =
|
242
|
-
%w(WINCH QUIT INT TERM USR1 USR2 HUP).map { |x| x.freeze }.freeze
|
244
|
+
QUEUE_SIGS = [ :WINCH, :QUIT, :INT, :TERM, :USR1, :USR2, :HUP ].freeze
|
243
245
|
|
244
246
|
# defer a signal for later processing in #join (master process)
|
245
247
|
def trap_deferred(signal)
|
@@ -259,16 +261,16 @@ module Unicorn
|
|
259
261
|
begin
|
260
262
|
ready = IO.select([@rd_sig], nil, nil, 1)
|
261
263
|
ready && ready[0] && ready[0][0] or return
|
262
|
-
loop { @rd_sig.
|
264
|
+
loop { @rd_sig.read_nonblock(Const::CHUNK_SIZE) }
|
263
265
|
rescue Errno::EAGAIN, Errno::EINTR
|
264
266
|
end
|
265
267
|
end
|
266
268
|
|
267
269
|
def awaken_master
|
268
270
|
begin
|
269
|
-
@wr_sig.
|
270
|
-
rescue Errno::EAGAIN
|
271
|
-
|
271
|
+
@wr_sig.write_nonblock('.') # wakeup master process from IO.select
|
272
|
+
rescue Errno::EAGAIN, Errno::EINTR
|
273
|
+
# pipe is full, master should wake up anyways
|
272
274
|
retry
|
273
275
|
end
|
274
276
|
end
|
@@ -350,7 +352,7 @@ module Unicorn
|
|
350
352
|
@workers.each_pair do |pid, worker|
|
351
353
|
(now - worker.tempfile.ctime) <= @timeout and next
|
352
354
|
logger.error "worker=#{worker.nr} PID:#{pid} is too old, killing"
|
353
|
-
kill_worker(
|
355
|
+
kill_worker(:KILL, pid) # take no prisoners for @timeout violations
|
354
356
|
worker.tempfile.close rescue nil
|
355
357
|
end
|
356
358
|
end
|
@@ -363,7 +365,7 @@ module Unicorn
|
|
363
365
|
Dir.chdir(@start_ctx[:cwd])
|
364
366
|
rescue Errno::ENOENT => err
|
365
367
|
logger.fatal "#{err.inspect} (#{@start_ctx[:cwd]})"
|
366
|
-
@sig_queue <<
|
368
|
+
@sig_queue << :QUIT # forcibly emulate SIGQUIT
|
367
369
|
return
|
368
370
|
end
|
369
371
|
tempfile = Tempfile.new('') # as short as possible to save dir space
|
@@ -379,12 +381,21 @@ module Unicorn
|
|
379
381
|
# once a client is accepted, it is processed in its entirety here
|
380
382
|
# in 3 easy steps: read request, call app, write app response
|
381
383
|
def process_client(client)
|
382
|
-
|
384
|
+
client.nonblock = false
|
385
|
+
set_client_sockopt(client) if TCPSocket === client
|
386
|
+
env = @request.read(client)
|
383
387
|
app_response = @app.call(env)
|
384
388
|
HttpResponse.write(client, app_response)
|
389
|
+
# if we get any error, try to write something back to the client
|
390
|
+
# assuming we haven't closed the socket, but don't get hung up
|
391
|
+
# if the socket is already closed or broken. We'll always ensure
|
392
|
+
# the socket is closed at the end of this function
|
385
393
|
rescue EOFError,Errno::ECONNRESET,Errno::EPIPE,Errno::EINVAL,Errno::EBADF
|
386
|
-
client.
|
394
|
+
client.write_nonblock(Const::ERROR_500_RESPONSE) rescue nil
|
395
|
+
rescue HttpParserError # try to tell the client they're bad
|
396
|
+
client.write_nonblock(Const::ERROR_400_RESPONSE) rescue nil
|
387
397
|
rescue Object => e
|
398
|
+
client.write_nonblock(Const::ERROR_500_RESPONSE) rescue nil
|
388
399
|
logger.error "Read error: #{e.inspect}"
|
389
400
|
logger.error e.backtrace.join("\n")
|
390
401
|
ensure
|
@@ -405,12 +416,7 @@ module Unicorn
|
|
405
416
|
build_app! unless @preload_app
|
406
417
|
@sig_queue.clear
|
407
418
|
QUEUE_SIGS.each { |sig| trap(sig, 'IGNORE') }
|
408
|
-
trap(
|
409
|
-
trap('USR1') do
|
410
|
-
@logger.info "worker=#{worker.nr} rotating logs..."
|
411
|
-
Unicorn::Util.reopen_logs
|
412
|
-
@logger.info "worker=#{worker.nr} done rotating logs"
|
413
|
-
end
|
419
|
+
trap(:CHLD, 'DEFAULT')
|
414
420
|
|
415
421
|
$0 = "unicorn worker[#{worker.nr}]"
|
416
422
|
@rd_sig.close if @rd_sig
|
@@ -435,13 +441,24 @@ module Unicorn
|
|
435
441
|
alive = true
|
436
442
|
ready = @listeners
|
437
443
|
client = nil
|
438
|
-
|
439
|
-
trap(
|
444
|
+
[:TERM, :INT].each { |sig| trap(sig) { exit(0) } } # instant shutdown
|
445
|
+
trap(:QUIT) do
|
440
446
|
alive = false
|
441
447
|
@listeners.each { |sock| sock.close rescue nil } # break IO.select
|
442
448
|
end
|
449
|
+
reopen_logs, (rd, wr) = false, IO.pipe
|
450
|
+
trap(:USR1) { reopen_logs = true; rd.close rescue nil } # break IO.select
|
451
|
+
@logger.info "worker=#{worker.nr} ready"
|
443
452
|
|
444
453
|
while alive && @master_pid == Process.ppid
|
454
|
+
if reopen_logs
|
455
|
+
reopen_logs = false
|
456
|
+
@logger.info "worker=#{worker.nr} rotating logs..."
|
457
|
+
Unicorn::Util.reopen_logs
|
458
|
+
@logger.info "worker=#{worker.nr} done rotating logs"
|
459
|
+
wr.close rescue nil
|
460
|
+
rd, wr = IO.pipe
|
461
|
+
end
|
445
462
|
# we're a goner in @timeout seconds anyways if tempfile.chmod
|
446
463
|
# breaks, so don't trap the exception. Using fchmod() since
|
447
464
|
# futimes() is not available in base Ruby and I very strongly
|
@@ -460,17 +477,14 @@ module Unicorn
|
|
460
477
|
rescue Errno::EAGAIN
|
461
478
|
next
|
462
479
|
end
|
463
|
-
accepted =
|
464
|
-
client.nonblock = false
|
465
|
-
set_client_sockopt(client) if TCPSocket === client
|
480
|
+
accepted = true
|
466
481
|
process_client(client)
|
467
482
|
rescue Errno::ECONNABORTED
|
468
483
|
# client closed the socket even before accept
|
469
|
-
|
470
|
-
client.close rescue nil
|
471
|
-
end
|
484
|
+
client.close rescue nil
|
472
485
|
end
|
473
486
|
tempfile.chmod(nr += 1)
|
487
|
+
break if reopen_logs
|
474
488
|
end
|
475
489
|
client = nil
|
476
490
|
|
@@ -478,18 +492,18 @@ module Unicorn
|
|
478
492
|
# we're probably reasonably busy, so avoid calling select(2)
|
479
493
|
# and try to do a blind non-blocking accept(2) on everything
|
480
494
|
# before we sleep again in select
|
481
|
-
if accepted
|
495
|
+
if accepted || reopen_logs
|
482
496
|
ready = @listeners
|
483
497
|
else
|
484
498
|
begin
|
485
499
|
tempfile.chmod(nr += 1)
|
486
500
|
# timeout used so we can detect parent death:
|
487
|
-
ret = IO.select(@listeners, nil,
|
501
|
+
ret = IO.select(@listeners, nil, [rd], @timeout/2.0) or next
|
488
502
|
ready = ret[0]
|
489
503
|
rescue Errno::EINTR
|
490
504
|
ready = @listeners
|
491
505
|
rescue Errno::EBADF => e
|
492
|
-
exit(alive ? 1 : 0)
|
506
|
+
reopen_logs or exit(alive ? 1 : 0)
|
493
507
|
end
|
494
508
|
end
|
495
509
|
rescue SystemExit => e
|
@@ -542,7 +556,7 @@ module Unicorn
|
|
542
556
|
logger.info "reloading config_file=#{@config.config_file}"
|
543
557
|
@config.reload
|
544
558
|
@config.commit!(self)
|
545
|
-
kill_each_worker(
|
559
|
+
kill_each_worker(:QUIT)
|
546
560
|
logger.info "done reloading config_file=#{@config.config_file}"
|
547
561
|
rescue Object => e
|
548
562
|
logger.error "error reloading config_file=#{@config.config_file}: " \
|
@@ -0,0 +1,23 @@
|
|
1
|
+
# This code is based on the original Rails handler in Mongrel
|
2
|
+
# Copyright (c) 2005 Zed A. Shaw
|
3
|
+
# Copyright (c) 2009 Eric Wong
|
4
|
+
# You can redistribute it and/or modify it under the same terms as Ruby.
|
5
|
+
# Additional work donated by contributors. See CONTRIBUTORS for more info.
|
6
|
+
require 'unicorn/cgi_wrapper'
|
7
|
+
require 'dispatcher'
|
8
|
+
|
9
|
+
module Unicorn; module App; end; end
|
10
|
+
|
11
|
+
# Implements a handler that can run Rails.
|
12
|
+
class Unicorn::App::OldRails
|
13
|
+
|
14
|
+
def call(env)
|
15
|
+
cgi = Unicorn::CGIWrapper.new(env)
|
16
|
+
Dispatcher.dispatch(cgi,
|
17
|
+
ActionController::CgiRequest::DEFAULT_SESSION_OPTIONS,
|
18
|
+
cgi.body)
|
19
|
+
cgi.out # finalize the response
|
20
|
+
cgi.rack_response
|
21
|
+
end
|
22
|
+
|
23
|
+
end
|
@@ -0,0 +1,58 @@
|
|
1
|
+
# This code is based on the original Rails handler in Mongrel
|
2
|
+
# Copyright (c) 2005 Zed A. Shaw
|
3
|
+
# Copyright (c) 2009 Eric Wong
|
4
|
+
# You can redistribute it and/or modify it under the same terms as Ruby.
|
5
|
+
|
6
|
+
require 'rack/file'
|
7
|
+
|
8
|
+
# Static file handler for Rails < 2.3. This handler is only provided
|
9
|
+
# as a convenience for developers. Performance-minded deployments should
|
10
|
+
# use nginx (or similar) for serving static files.
|
11
|
+
#
|
12
|
+
# This supports page caching directly and will try to resolve a
|
13
|
+
# request in the following order:
|
14
|
+
#
|
15
|
+
# * If the requested exact PATH_INFO exists as a file then serve it.
|
16
|
+
# * If it exists at PATH_INFO+rest_operator+".html" exists
|
17
|
+
# then serve that.
|
18
|
+
#
|
19
|
+
# This means that if you are using page caching it will actually work
|
20
|
+
# with Unicorn and you should see a decent speed boost (but not as
|
21
|
+
# fast as if you use a static server like nginx).
|
22
|
+
class Unicorn::App::OldRails::Static
|
23
|
+
FILE_METHODS = { 'GET' => true, 'HEAD' => true }.freeze
|
24
|
+
REQUEST_METHOD = 'REQUEST_METHOD'.freeze
|
25
|
+
|
26
|
+
def initialize(app)
|
27
|
+
@app = app
|
28
|
+
@root = "#{::RAILS_ROOT}/public"
|
29
|
+
@file_server = ::Rack::File.new(@root)
|
30
|
+
end
|
31
|
+
|
32
|
+
def call(env)
|
33
|
+
# short circuit this ASAP if serving non-file methods
|
34
|
+
FILE_METHODS.include?(env[REQUEST_METHOD]) or return @app.call(env)
|
35
|
+
|
36
|
+
# first try the path as-is
|
37
|
+
path_info = env[Unicorn::Const::PATH_INFO].chomp("/")
|
38
|
+
if File.file?("#@root/#{::Rack::Utils.unescape(path_info)}")
|
39
|
+
# File exists as-is so serve it up
|
40
|
+
env[Unicorn::Const::PATH_INFO] = path_info
|
41
|
+
return @file_server.call(env)
|
42
|
+
end
|
43
|
+
|
44
|
+
# then try the cached version:
|
45
|
+
|
46
|
+
# grab the semi-colon REST operator used by old versions of Rails
|
47
|
+
# this is the reason we didn't just copy the new Rails::Rack::Static
|
48
|
+
env[Unicorn::Const::REQUEST_URI] =~ /^#{Regexp.escape(path_info)}(;[^\?]+)/
|
49
|
+
path_info << "#$1#{ActionController::Base.page_cache_extension}"
|
50
|
+
|
51
|
+
if File.file?("#@root/#{::Rack::Utils.unescape(path_info)}")
|
52
|
+
env[Unicorn::Const::PATH_INFO] = path_info
|
53
|
+
return @file_server.call(env)
|
54
|
+
end
|
55
|
+
|
56
|
+
@app.call(env) # call OldRails
|
57
|
+
end
|
58
|
+
end if defined?(Unicorn::App::OldRails)
|
@@ -0,0 +1,151 @@
|
|
1
|
+
# This code is based on the original CGIWrapper from Mongrel
|
2
|
+
# Copyright (c) 2005 Zed A. Shaw
|
3
|
+
# Copyright (c) 2009 Eric Wong
|
4
|
+
# You can redistribute it and/or modify it under the same terms as Ruby.
|
5
|
+
#
|
6
|
+
# Additional work donated by contributors. See CONTRIBUTORS for more info.
|
7
|
+
|
8
|
+
require 'cgi'
|
9
|
+
|
10
|
+
module Unicorn; end
|
11
|
+
|
12
|
+
# The beginning of a complete wrapper around Unicorn's internal HTTP
|
13
|
+
# processing system but maintaining the original Ruby CGI module. Use
|
14
|
+
# this only as a crutch to get existing CGI based systems working. It
|
15
|
+
# should handle everything, but please notify us if you see special
|
16
|
+
# warnings. This work is still very alpha so we need testers to help
|
17
|
+
# work out the various corner cases.
|
18
|
+
class Unicorn::CGIWrapper < ::CGI
|
19
|
+
undef_method :env_table
|
20
|
+
attr_reader :env_table
|
21
|
+
attr_reader :body
|
22
|
+
|
23
|
+
# these are stripped out of any keys passed to CGIWrapper.header function
|
24
|
+
NPH = 'nph'.freeze # Completely ignored, Unicorn outputs the date regardless
|
25
|
+
CONNECTION = 'connection'.freeze # Completely ignored. Why is CGI doing this?
|
26
|
+
CHARSET = 'charset'.freeze # this gets appended to Content-Type
|
27
|
+
COOKIE = 'cookie'.freeze # maps (Hash,Array,String) to "Set-Cookie" headers
|
28
|
+
STATUS = 'status'.freeze # stored as @status
|
29
|
+
Status = 'Status'.freeze # code + human-readable text, Rails sets this
|
30
|
+
|
31
|
+
# some of these are common strings, but this is the only module
|
32
|
+
# using them and the reason they're not in Unicorn::Const
|
33
|
+
SET_COOKIE = 'Set-Cookie'.freeze
|
34
|
+
CONTENT_TYPE = 'Content-Type'.freeze
|
35
|
+
CONTENT_LENGTH = 'Content-Length'.freeze # this is NOT Const::CONTENT_LENGTH
|
36
|
+
RACK_INPUT = 'rack.input'.freeze
|
37
|
+
RACK_ERRORS = 'rack.errors'.freeze
|
38
|
+
|
39
|
+
# this maps CGI header names to HTTP header names
|
40
|
+
HEADER_MAP = {
|
41
|
+
'status' => Status,
|
42
|
+
'type' => CONTENT_TYPE,
|
43
|
+
'server' => 'Server'.freeze,
|
44
|
+
'language' => 'Content-Language'.freeze,
|
45
|
+
'expires' => 'Expires'.freeze,
|
46
|
+
'length' => CONTENT_LENGTH,
|
47
|
+
}.freeze
|
48
|
+
|
49
|
+
# Takes an a Rackable environment, plus any additional CGI.new
|
50
|
+
# arguments These are used internally to create a wrapper around the
|
51
|
+
# real CGI while maintaining Rack/Unicorn's view of the world. This
|
52
|
+
# this will NOT deal well with large responses that take up a lot of
|
53
|
+
# memory, but neither does the CGI nor the original CGIWrapper from
|
54
|
+
# Mongrel...
|
55
|
+
def initialize(rack_env, *args)
|
56
|
+
@env_table = rack_env
|
57
|
+
@status = nil
|
58
|
+
@head = {}
|
59
|
+
@headv = Hash.new { |hash,key| hash[key] = [] }
|
60
|
+
@body = StringIO.new
|
61
|
+
super(*args)
|
62
|
+
end
|
63
|
+
|
64
|
+
# finalizes the response in a way Rack applications would expect
|
65
|
+
def rack_response
|
66
|
+
# @head[CONTENT_LENGTH] ||= @body.size
|
67
|
+
@headv[SET_COOKIE] += @output_cookies if @output_cookies
|
68
|
+
@headv.each_pair do |key,value|
|
69
|
+
@head[key] ||= value.join("\n") unless value.empty?
|
70
|
+
end
|
71
|
+
|
72
|
+
# Capitalized "Status:", with human-readable status code (e.g. "200 OK")
|
73
|
+
parseable_status = @head.delete(Status)
|
74
|
+
unless @status
|
75
|
+
@status ||= parseable_status.split(/ /)[0].to_i rescue 404
|
76
|
+
end
|
77
|
+
|
78
|
+
[ @status, @head, [ @body.string ] ]
|
79
|
+
end
|
80
|
+
|
81
|
+
# The header is typically called to send back the header. In our case we
|
82
|
+
# collect it into a hash for later usage. This can be called multiple
|
83
|
+
# times to set different cookies.
|
84
|
+
def header(options = "text/html")
|
85
|
+
# if they pass in a string then just write the Content-Type
|
86
|
+
if String === options
|
87
|
+
@head[CONTENT_TYPE] ||= options
|
88
|
+
else
|
89
|
+
HEADER_MAP.each_pair do |from, to|
|
90
|
+
from = options.delete(from) or next
|
91
|
+
@head[to] = from.to_s
|
92
|
+
end
|
93
|
+
|
94
|
+
@head[CONTENT_TYPE] ||= "text/html"
|
95
|
+
if charset = options.delete(CHARSET)
|
96
|
+
@head[CONTENT_TYPE] << "; charset=#{charset}"
|
97
|
+
end
|
98
|
+
|
99
|
+
# lots of ways to set cookies
|
100
|
+
if cookie = options.delete(COOKIE)
|
101
|
+
set_cookies = @headv[SET_COOKIE]
|
102
|
+
case cookie
|
103
|
+
when Array
|
104
|
+
cookie.each { |c| set_cookies << c.to_s }
|
105
|
+
when Hash
|
106
|
+
cookie.each_value { |c| set_cookies << c.to_s }
|
107
|
+
else
|
108
|
+
set_cookies << cookie.to_s
|
109
|
+
end
|
110
|
+
end
|
111
|
+
@status ||= options.delete(STATUS) # all lower-case
|
112
|
+
|
113
|
+
# drop the keys we don't want anymore
|
114
|
+
options.delete(NPH)
|
115
|
+
options.delete(CONNECTION)
|
116
|
+
|
117
|
+
# finally, set the rest of the headers as-is, allowing duplicates
|
118
|
+
options.each_pair { |k,v| @headv[k] << v }
|
119
|
+
end
|
120
|
+
|
121
|
+
# doing this fakes out the cgi library to think the headers are empty
|
122
|
+
# we then do the real headers in the out function call later
|
123
|
+
""
|
124
|
+
end
|
125
|
+
|
126
|
+
# The dumb thing is people can call header or this or both and in
|
127
|
+
# any order. So, we just reuse header and then finalize the
|
128
|
+
# HttpResponse the right way. This will have no effect if called
|
129
|
+
# the second time if the first "outputted" anything.
|
130
|
+
def out(options = "text/html")
|
131
|
+
header(options)
|
132
|
+
@body.size == 0 or return
|
133
|
+
@body << yield if block_given?
|
134
|
+
end
|
135
|
+
|
136
|
+
# Used to wrap the normal stdinput variable used inside CGI.
|
137
|
+
def stdinput
|
138
|
+
@env_table[RACK_INPUT]
|
139
|
+
end
|
140
|
+
|
141
|
+
# The stdoutput should be completely bypassed but we'll drop a
|
142
|
+
# warning just in case
|
143
|
+
def stdoutput
|
144
|
+
err = @env_table[RACK_ERRORS]
|
145
|
+
err.puts "WARNING: Your program is doing something not expected."
|
146
|
+
err.puts "Please tell Eric that stdoutput was used and what software " \
|
147
|
+
"you are running. Thanks."
|
148
|
+
@body
|
149
|
+
end
|
150
|
+
|
151
|
+
end
|