unicorn 0.2.1 → 0.2.2
Sign up to get free protection for your applications and to get access to all the features.
- data/.document +2 -1
- data/CHANGELOG +1 -22
- data/Manifest +3 -0
- data/README +12 -8
- data/SIGNALS +51 -0
- data/bin/unicorn +2 -25
- data/bin/unicorn_rails +7 -63
- data/ext/unicorn/http11/http11.c +10 -23
- data/lib/unicorn.rb +39 -25
- data/lib/unicorn/app/exec_cgi.rb +150 -0
- data/lib/unicorn/configurator.rb +14 -1
- data/lib/unicorn/const.rb +1 -1
- data/lib/unicorn/http_request.rb +1 -3
- data/lib/unicorn/http_response.rb +13 -16
- data/lib/unicorn/launcher.rb +33 -0
- data/lib/unicorn/socket.rb +0 -1
- data/test/exec/test_exec.rb +2 -2
- data/test/unit/test_http_parser.rb +1 -2
- data/test/unit/test_request.rb +82 -0
- data/test/unit/test_response.rb +7 -0
- data/unicorn.gemspec +5 -5
- metadata +10 -4
data/.document
CHANGED
data/CHANGELOG
CHANGED
@@ -1,25 +1,4 @@
|
|
1
|
+
v0.2.2 - small bug fixes, fix Rack multi-value headers (Set-Cookie:)
|
1
2
|
v0.2.1 - Fix broken Manifest that cause unicorn_rails to not be bundled
|
2
|
-
|
3
3
|
v0.2.0 - unicorn_rails launcher script.
|
4
|
-
|
5
4
|
v0.1.0 - Unicorn - UNIX-only fork of Mongrel free of threading
|
6
|
-
|
7
|
-
-- old Mongrel changelog --
|
8
|
-
|
9
|
-
v2.0. (WIP) Rack support.
|
10
|
-
|
11
|
-
v1.1.4. Fix camping handler. Correct treatment of @throttle parameter.
|
12
|
-
|
13
|
-
v1.1.3. Fix security flaw of DirHandler; reported on mailing list.
|
14
|
-
|
15
|
-
v1.1.2. Fix worker termination bug; fix JRuby 1.0.3 load order issue; fix require issue on systems without Rubygems.
|
16
|
-
|
17
|
-
v1.1.1. Fix mongrel_rails restart bug; fix bug with Rack status codes.
|
18
|
-
|
19
|
-
v1.1. Pure Ruby URIClassifier. More modular architecture. JRuby support. Move C URIClassifier into mongrel_experimental project.
|
20
|
-
|
21
|
-
v1.0.4. Backport fixes for versioning inconsistency, mongrel_rails bug, and DirHandler bug.
|
22
|
-
|
23
|
-
v1.0.3. Fix user-switching bug; make people upgrade to the latest from the RC.
|
24
|
-
|
25
|
-
v1.0.2. Signed gem; many minor bugfixes and patches.
|
data/Manifest
CHANGED
@@ -20,10 +20,12 @@ ext/unicorn/http11/http11_parser.h
|
|
20
20
|
ext/unicorn/http11/http11_parser.rl
|
21
21
|
ext/unicorn/http11/http11_parser_common.rl
|
22
22
|
lib/unicorn.rb
|
23
|
+
lib/unicorn/app/exec_cgi.rb
|
23
24
|
lib/unicorn/configurator.rb
|
24
25
|
lib/unicorn/const.rb
|
25
26
|
lib/unicorn/http_request.rb
|
26
27
|
lib/unicorn/http_response.rb
|
28
|
+
lib/unicorn/launcher.rb
|
27
29
|
lib/unicorn/socket.rb
|
28
30
|
lib/unicorn/util.rb
|
29
31
|
setup.rb
|
@@ -37,6 +39,7 @@ test/test_helper.rb
|
|
37
39
|
test/tools/trickletest.rb
|
38
40
|
test/unit/test_configurator.rb
|
39
41
|
test/unit/test_http_parser.rb
|
42
|
+
test/unit/test_request.rb
|
40
43
|
test/unit/test_response.rb
|
41
44
|
test/unit/test_server.rb
|
42
45
|
test/unit/test_upload.rb
|
data/README
CHANGED
@@ -49,7 +49,7 @@ least a friend who can build it for you.
|
|
49
49
|
You may download the tarball from the Mongrel project page on Rubyforge
|
50
50
|
and run setup.rb after unpacking it:
|
51
51
|
|
52
|
-
|
52
|
+
http://rubyforge.org/frs/?group_id=1306
|
53
53
|
|
54
54
|
You may also install it via Rubygems on Rubyforge:
|
55
55
|
|
@@ -67,7 +67,7 @@ If you have web browser software for the World Wide Web
|
|
67
67
|
(on the Information Superhighway), you may browse the code from
|
68
68
|
your web browser and download the latest snapshot tarballs here:
|
69
69
|
|
70
|
-
* http://git.bogomips.org/cgit/unicorn.git
|
70
|
+
* http://git.bogomips.org/cgit/unicorn.git (this server runs Unicorn!)
|
71
71
|
* http://repo.or.cz/w/unicorn.git (gitweb mirror)
|
72
72
|
|
73
73
|
== Usage
|
@@ -103,12 +103,16 @@ functionality of the `unicorn' launcher.
|
|
103
103
|
|
104
104
|
== Disclaimer
|
105
105
|
|
106
|
-
There are
|
107
|
-
|
108
|
-
|
109
|
-
|
110
|
-
|
111
|
-
|
106
|
+
There are only a few instances of Unicorn deployed anywhere in the
|
107
|
+
world. The only public site known to run Unicorn at this time is
|
108
|
+
http://git.bogomips.org/cgit which runs Unicorn::App::ExecCgi to
|
109
|
+
fork()+exec() cgit.
|
110
|
+
|
111
|
+
Be one of the first brave guinea pigs to run it on your production site!
|
112
|
+
Of course there is NO WARRANTY whatsoever if anything goes wrong, but
|
113
|
+
let us know and we'll try our best to fix it. Unicorn is still in the
|
114
|
+
early stages and testing + feedback would be *greatly* appreciated;
|
115
|
+
maybe you'll get Rainbows as a reward!
|
112
116
|
|
113
117
|
== Known Issues
|
114
118
|
|
data/SIGNALS
CHANGED
@@ -22,6 +22,9 @@ processes are documented here as well.
|
|
22
22
|
should be sent to the original process once the child is verified to
|
23
23
|
be up and running.
|
24
24
|
|
25
|
+
* WINCH - gracefully stops workers but keep the master running.
|
26
|
+
This will only work for daemonized processes.
|
27
|
+
|
25
28
|
=== Worker Processes
|
26
29
|
|
27
30
|
Sending signals directly to the worker processes should not normally be
|
@@ -34,3 +37,51 @@ automatically respawned.
|
|
34
37
|
|
35
38
|
* USR1 - reopen all logs owned by the worker process
|
36
39
|
See Unicorn::Util.reopen_logs for what is considered a log.
|
40
|
+
|
41
|
+
=== Procedure to replace a running unicorn executable
|
42
|
+
|
43
|
+
You may replace a running instance of unicorn with a new one without
|
44
|
+
losing any incoming connections. Doing so will reload all of your
|
45
|
+
application code, Unicorn config, Ruby executable, and all libraries.
|
46
|
+
The only things that will not change (due to OS limitations) are:
|
47
|
+
|
48
|
+
1. The listener backlog size of already-bound sockets
|
49
|
+
|
50
|
+
2. The path to the unicorn executable script. If you want to change to
|
51
|
+
a different installation of Ruby, you can modify the shebang
|
52
|
+
line to point to your alternative interpreter.
|
53
|
+
|
54
|
+
The procedure is exactly like that of nginx:
|
55
|
+
|
56
|
+
1. Send USR2 to the master process
|
57
|
+
|
58
|
+
2. Check your process manager or pid files to see if a new master spawned
|
59
|
+
successfully. If you're using a pid file, the old process will have
|
60
|
+
".oldbin" appended to its path. You should have two master instances
|
61
|
+
of unicorn running now, both of which will have workers servicing
|
62
|
+
requests. Your process tree should look something like this:
|
63
|
+
|
64
|
+
unicorn master (old)
|
65
|
+
\_ unicorn worker[0]
|
66
|
+
\_ unicorn worker[1]
|
67
|
+
\_ unicorn worker[2]
|
68
|
+
\_ unicorn worker[3]
|
69
|
+
\_ unicorn master
|
70
|
+
\_ unicorn worker[0]
|
71
|
+
\_ unicorn worker[1]
|
72
|
+
\_ unicorn worker[2]
|
73
|
+
\_ unicorn worker[3]
|
74
|
+
|
75
|
+
4. You can now send WINCH to the old master process so only the new workers
|
76
|
+
serve requests. If your unicorn process is bound to an interactive
|
77
|
+
terminal, you can skip this step. Step 5 will be more difficult but
|
78
|
+
you can also skip it if your process is not daemonized.
|
79
|
+
|
80
|
+
5. You should now ensure that everything is running correctly with the
|
81
|
+
new workers as the old workers die off.
|
82
|
+
|
83
|
+
6a. If everything seems ok, then send QUIT to the old master. You're done!
|
84
|
+
|
85
|
+
6b. If something is broken, then send HUP to the old master to reload
|
86
|
+
the config and restart its workers. Then send QUIT to the new master
|
87
|
+
process.
|
data/bin/unicorn
CHANGED
@@ -1,6 +1,5 @@
|
|
1
1
|
#!/home/ew/bin/ruby
|
2
|
-
|
3
|
-
require 'unicorn' # require this first to populate Unicorn::DEFAULT_START_CTX
|
2
|
+
require 'unicorn/launcher'
|
4
3
|
require 'optparse'
|
5
4
|
|
6
5
|
env = "development"
|
@@ -163,27 +162,5 @@ if $DEBUG
|
|
163
162
|
})
|
164
163
|
end
|
165
164
|
|
166
|
-
|
167
|
-
if daemonize
|
168
|
-
|
169
|
-
$stdin.reopen("/dev/null")
|
170
|
-
unless ENV['UNICORN_FD']
|
171
|
-
exit if fork
|
172
|
-
Process.setsid
|
173
|
-
exit if fork
|
174
|
-
end
|
175
|
-
|
176
|
-
# We don't do a lot of standard daemonization stuff:
|
177
|
-
# * $stderr/$stderr can/will be redirected separately
|
178
|
-
# * umask is whatever was set by the parent process at startup
|
179
|
-
# and can be set in config.ru and config_file, so making it
|
180
|
-
# 0000 and potentially exposing sensitive log data can be bad
|
181
|
-
# policy.
|
182
|
-
# * Don't bother to chdir here since Unicorn is designed to
|
183
|
-
# run inside APP_ROOT. Unicorn will also re-chdir() to
|
184
|
-
# the directory it was started in when being re-executed
|
185
|
-
# to pickup code changes if the original deployment directory
|
186
|
-
# is a symlink or otherwise got replaced.
|
187
|
-
end
|
188
|
-
|
165
|
+
Unicorn::Launcher.daemonize! if daemonize
|
189
166
|
Unicorn.run(app, options)
|
data/bin/unicorn_rails
CHANGED
@@ -1,6 +1,5 @@
|
|
1
1
|
#!/home/ew/bin/ruby
|
2
|
-
|
3
|
-
require 'unicorn' # require this first to populate Unicorn::DEFAULT_START_CTX
|
2
|
+
require 'unicorn/launcher'
|
4
3
|
require 'optparse'
|
5
4
|
require 'fileutils'
|
6
5
|
|
@@ -118,49 +117,12 @@ rails_loader = lambda do ||
|
|
118
117
|
abort "#$0 must be run inside RAILS_ROOT (#{::RAILS_ROOT})"
|
119
118
|
end
|
120
119
|
|
121
|
-
if ENV['UNICORN_RAILS_USE_SYSTEM_RACK'].to_i == 0
|
122
|
-
rails_ver = Rails::VERSION::STRING
|
123
|
-
|
124
|
-
# maps Rails versions to the vendorized Rack version they bundle
|
125
|
-
version_map = {
|
126
|
-
'2.3.2' => '1.0',
|
127
|
-
# 3.0.0 => false, # assuming 3.0.0 doesn't need vendorized Rack anymore
|
128
|
-
}
|
129
|
-
rack_ver = version_map[rails_ver] or
|
130
|
-
warn "Possibly unsupported Rails version: v#{rails_ver}"
|
131
|
-
|
132
|
-
rack_path = nil
|
133
|
-
case rack_ver
|
134
|
-
when String, NilClass
|
135
|
-
version_map.values.find_all { |v| String === v }.sort.each do |v|
|
136
|
-
$LOAD_PATH.grep(%r{/actionpack-[\d\.]+/lib/?\z}).each do |path|
|
137
|
-
rack_path = File.join(path, "action_controller/vendor/rack-#{v}")
|
138
|
-
File.directory?(rack_path) and break
|
139
|
-
rack_path = nil
|
140
|
-
end
|
141
|
-
break if rack_path
|
142
|
-
end
|
143
|
-
rack_path or abort(
|
144
|
-
"Unable to find Rails-vendorized Rack library.\n" \
|
145
|
-
"Perhaps this script is no longer with your" \
|
146
|
-
"Rails version (#{rails_ver}).\n")
|
147
|
-
puts "vendorized Rack load path #{rack_path}"
|
148
|
-
$LOAD_PATH.unshift(rack_path)
|
149
|
-
when FalseClass
|
150
|
-
# using non-vendorized rack library (most likely via gems)
|
151
|
-
end
|
152
|
-
end # Vendorized Rack LOAD_PATH finder
|
153
|
-
|
154
|
-
# require Rack as late as possible in case $LOAD_PATH is modified
|
155
|
-
# in config.ru or command-line
|
156
|
-
require 'rack'
|
157
|
-
|
158
120
|
# return the lambda
|
159
121
|
config = ::ARGV[0] || (File.exist?('config.ru') ? 'config.ru' : nil)
|
160
122
|
case config
|
161
123
|
when nil
|
162
124
|
lambda do ||
|
163
|
-
require
|
125
|
+
require 'config/environment'
|
164
126
|
ActionController::Dispatcher.new
|
165
127
|
end
|
166
128
|
when /\.ru$/
|
@@ -208,31 +170,13 @@ if $DEBUG
|
|
208
170
|
})
|
209
171
|
end
|
210
172
|
|
211
|
-
# only daemonize if we're not inheriting file descriptors from our parent
|
212
|
-
if daemonize
|
213
|
-
options[:pid] = rails_pid
|
214
|
-
$stdin.reopen("/dev/null")
|
215
|
-
unless ENV['UNICORN_FD']
|
216
|
-
exit if fork
|
217
|
-
Process.setsid
|
218
|
-
exit if fork
|
219
|
-
end
|
220
|
-
|
221
|
-
# We don't do a lot of standard daemonization stuff:
|
222
|
-
# * $stderr/$stderr can/will be redirected separately
|
223
|
-
# * umask is whatever was set by the parent process at startup
|
224
|
-
# and can be set in config.ru and config_file, so making it
|
225
|
-
# 0000 and potentially exposing sensitive log data can be bad
|
226
|
-
# policy.
|
227
|
-
# * Don't bother to chdir here since Unicorn is designed to
|
228
|
-
# run inside APP_ROOT. Unicorn will also re-chdir() to
|
229
|
-
# the directory it was started in when being re-executed
|
230
|
-
# to pickup code changes if the original deployment directory
|
231
|
-
# is a symlink or otherwise got replaced.
|
232
|
-
end
|
233
|
-
|
234
173
|
# ensure Rails standard tmp paths exist
|
235
174
|
%w(cache pids sessions sockets).each do |dir|
|
236
175
|
FileUtils.mkdir_p("tmp/#{dir}")
|
237
176
|
end
|
177
|
+
|
178
|
+
if daemonize
|
179
|
+
options[:pid] = rails_pid
|
180
|
+
Unicorn::Launcher.daemonize!
|
181
|
+
end
|
238
182
|
Unicorn.run(app, options)
|
data/ext/unicorn/http11/http11.c
CHANGED
@@ -28,13 +28,9 @@ static VALUE global_fragment;
|
|
28
28
|
static VALUE global_query_string;
|
29
29
|
static VALUE global_http_version;
|
30
30
|
static VALUE global_content_length;
|
31
|
-
static VALUE global_http_content_length;
|
32
31
|
static VALUE global_request_path;
|
33
32
|
static VALUE global_content_type;
|
34
|
-
static VALUE global_http_content_type;
|
35
33
|
static VALUE global_http_body;
|
36
|
-
static VALUE global_gateway_interface;
|
37
|
-
static VALUE global_gateway_interface_value;
|
38
34
|
static VALUE global_server_name;
|
39
35
|
static VALUE global_server_port;
|
40
36
|
static VALUE global_server_protocol;
|
@@ -129,6 +125,7 @@ static int common_field_cmp(const void *a, const void *b)
|
|
129
125
|
}
|
130
126
|
#endif /* HAVE_QSORT_BSEARCH */
|
131
127
|
|
128
|
+
/* this function is not performance-critical */
|
132
129
|
static void init_common_fields(void)
|
133
130
|
{
|
134
131
|
int i;
|
@@ -137,8 +134,15 @@ static void init_common_fields(void)
|
|
137
134
|
memcpy(tmp, HTTP_PREFIX, HTTP_PREFIX_LEN);
|
138
135
|
|
139
136
|
for(i = 0; i < ARRAY_SIZE(common_http_fields); cf++, i++) {
|
140
|
-
|
141
|
-
|
137
|
+
/* Rack doesn't like certain headers prefixed with "HTTP_" */
|
138
|
+
if (!strcmp("CONTENT_LENGTH", cf->name) ||
|
139
|
+
!strcmp("CONTENT_TYPE", cf->name)) {
|
140
|
+
cf->value = rb_str_new(cf->name, cf->len);
|
141
|
+
} else {
|
142
|
+
memcpy(tmp + HTTP_PREFIX_LEN, cf->name, cf->len + 1);
|
143
|
+
cf->value = rb_str_new(tmp, HTTP_PREFIX_LEN + cf->len);
|
144
|
+
}
|
145
|
+
cf->value = rb_obj_freeze(cf->value);
|
142
146
|
rb_global_variable(&cf->value);
|
143
147
|
}
|
144
148
|
|
@@ -275,21 +279,8 @@ static void header_done(void *data, const char *at, size_t length)
|
|
275
279
|
{
|
276
280
|
VALUE req = (VALUE)data;
|
277
281
|
VALUE temp = Qnil;
|
278
|
-
VALUE ctype = Qnil;
|
279
|
-
VALUE clen = Qnil;
|
280
282
|
char *colon = NULL;
|
281
283
|
|
282
|
-
clen = rb_hash_aref(req, global_http_content_length);
|
283
|
-
if(clen != Qnil) {
|
284
|
-
rb_hash_aset(req, global_content_length, clen);
|
285
|
-
}
|
286
|
-
|
287
|
-
ctype = rb_hash_aref(req, global_http_content_type);
|
288
|
-
if(ctype != Qnil) {
|
289
|
-
rb_hash_aset(req, global_content_type, ctype);
|
290
|
-
}
|
291
|
-
|
292
|
-
rb_hash_aset(req, global_gateway_interface, global_gateway_interface_value);
|
293
284
|
if((temp = rb_hash_aref(req, global_http_host)) != Qnil) {
|
294
285
|
colon = memchr(RSTRING_PTR(temp), ':', RSTRING_LEN(temp));
|
295
286
|
if(colon != NULL) {
|
@@ -497,12 +488,8 @@ void Init_http11()
|
|
497
488
|
DEF_GLOBAL(http_version, "HTTP_VERSION");
|
498
489
|
DEF_GLOBAL(request_path, "REQUEST_PATH");
|
499
490
|
DEF_GLOBAL(content_length, "CONTENT_LENGTH");
|
500
|
-
DEF_GLOBAL(http_content_length, "HTTP_CONTENT_LENGTH");
|
501
491
|
DEF_GLOBAL(http_body, "HTTP_BODY");
|
502
492
|
DEF_GLOBAL(content_type, "CONTENT_TYPE");
|
503
|
-
DEF_GLOBAL(http_content_type, "HTTP_CONTENT_TYPE");
|
504
|
-
DEF_GLOBAL(gateway_interface, "GATEWAY_INTERFACE");
|
505
|
-
DEF_GLOBAL(gateway_interface_value, "CGI/1.2");
|
506
493
|
DEF_GLOBAL(server_name, "SERVER_NAME");
|
507
494
|
DEF_GLOBAL(server_port, "SERVER_PORT");
|
508
495
|
DEF_GLOBAL(server_protocol, "SERVER_PROTOCOL");
|
data/lib/unicorn.rb
CHANGED
@@ -23,7 +23,6 @@ module Unicorn
|
|
23
23
|
# forked worker children.
|
24
24
|
class HttpServer
|
25
25
|
attr_reader :logger
|
26
|
-
include Process
|
27
26
|
include ::Unicorn::SocketHelper
|
28
27
|
|
29
28
|
DEFAULT_START_CTX = {
|
@@ -160,7 +159,8 @@ module Unicorn
|
|
160
159
|
# are trapped. See trap_deferred
|
161
160
|
@rd_sig, @wr_sig = IO.pipe unless (@rd_sig && @wr_sig)
|
162
161
|
@rd_sig.nonblock = @wr_sig.nonblock = true
|
163
|
-
|
162
|
+
mode = nil
|
163
|
+
respawn = true
|
164
164
|
|
165
165
|
QUEUE_SIGS.each { |sig| trap_deferred(sig) }
|
166
166
|
trap('CHLD') { |sig_nr| awaken_master }
|
@@ -172,18 +172,30 @@ module Unicorn
|
|
172
172
|
case (mode = @sig_queue.shift)
|
173
173
|
when nil
|
174
174
|
murder_lazy_workers
|
175
|
-
spawn_missing_workers
|
175
|
+
spawn_missing_workers if respawn
|
176
|
+
master_sleep
|
176
177
|
when 'QUIT' # graceful shutdown
|
177
178
|
break
|
178
179
|
when 'TERM', 'INT' # immediate shutdown
|
179
180
|
stop(false)
|
180
181
|
break
|
181
182
|
when 'USR1' # rotate logs
|
182
|
-
|
183
|
+
logger.info "master rotating logs..."
|
183
184
|
Unicorn::Util.reopen_logs
|
185
|
+
logger.info "master done rotating logs"
|
186
|
+
kill_each_worker('USR1')
|
184
187
|
when 'USR2' # exec binary, stay alive in case something went wrong
|
185
188
|
reexec
|
189
|
+
when 'WINCH'
|
190
|
+
if Process.ppid == 1 || Process.getpgrp != $$
|
191
|
+
respawn = false
|
192
|
+
logger.info "gracefully stopping all workers"
|
193
|
+
kill_each_worker('QUIT')
|
194
|
+
else
|
195
|
+
logger.info "SIGWINCH ignored because we're not daemonized"
|
196
|
+
end
|
186
197
|
when 'HUP'
|
198
|
+
respawn = true
|
187
199
|
if @config.config_file
|
188
200
|
load_config!
|
189
201
|
redo # immediate reaping since we may have QUIT workers
|
@@ -195,18 +207,6 @@ module Unicorn
|
|
195
207
|
else
|
196
208
|
logger.error "master process in unknown mode: #{mode}"
|
197
209
|
end
|
198
|
-
reap_all_workers
|
199
|
-
|
200
|
-
ready = begin
|
201
|
-
IO.select([@rd_sig], nil, nil, 1) or next
|
202
|
-
rescue Errno::EINTR # next
|
203
|
-
end
|
204
|
-
ready[0] && ready[0][0] or next
|
205
|
-
begin
|
206
|
-
@rd_sig.sysread(1)
|
207
|
-
rescue Errno::EAGAIN, Errno::EINTR
|
208
|
-
# spurious wakeup? ignore it
|
209
|
-
end
|
210
210
|
end
|
211
211
|
rescue Errno::EINTR
|
212
212
|
retry
|
@@ -239,7 +239,8 @@ module Unicorn
|
|
239
239
|
private
|
240
240
|
|
241
241
|
# list of signals we care about and trap in master.
|
242
|
-
QUEUE_SIGS =
|
242
|
+
QUEUE_SIGS =
|
243
|
+
%w(WINCH QUIT INT TERM USR1 USR2 HUP).map { |x| x.freeze }.freeze
|
243
244
|
|
244
245
|
# defer a signal for later processing in #join (master process)
|
245
246
|
def trap_deferred(signal)
|
@@ -253,6 +254,17 @@ module Unicorn
|
|
253
254
|
end
|
254
255
|
end
|
255
256
|
|
257
|
+
# wait for a signal hander to wake us up and then consume the pipe
|
258
|
+
# Wake up every second anyways to run murder_lazy_workers
|
259
|
+
def master_sleep
|
260
|
+
begin
|
261
|
+
ready = IO.select([@rd_sig], nil, nil, 1)
|
262
|
+
ready && ready[0] && ready[0][0] or return
|
263
|
+
loop { @rd_sig.sysread(Const::CHUNK_SIZE) }
|
264
|
+
rescue Errno::EAGAIN, Errno::EINTR
|
265
|
+
end
|
266
|
+
end
|
267
|
+
|
256
268
|
def awaken_master
|
257
269
|
begin
|
258
270
|
@wr_sig.syswrite('.') # wakeup master process from IO.select
|
@@ -266,17 +278,18 @@ module Unicorn
|
|
266
278
|
def reap_all_workers
|
267
279
|
begin
|
268
280
|
loop do
|
269
|
-
pid =
|
281
|
+
pid, status = Process.waitpid2(-1, Process::WNOHANG)
|
282
|
+
pid or break
|
270
283
|
if @reexec_pid == pid
|
271
|
-
logger.error "reaped exec()-ed
|
284
|
+
logger.error "reaped #{status.inspect} exec()-ed"
|
272
285
|
@reexec_pid = 0
|
273
286
|
self.pid = @pid.chomp('.oldbin') if @pid
|
287
|
+
$0 = "unicorn master"
|
274
288
|
else
|
275
289
|
worker = @workers.delete(pid)
|
276
290
|
worker.tempfile.close rescue nil
|
277
|
-
logger.info "reaped
|
278
|
-
"worker=#{worker.nr rescue 'unknown'}
|
279
|
-
"status=#{$?.exitstatus}"
|
291
|
+
logger.info "reaped #{status.inspect} " \
|
292
|
+
"worker=#{worker.nr rescue 'unknown'}"
|
280
293
|
end
|
281
294
|
end
|
282
295
|
rescue Errno::ECHILD
|
@@ -324,6 +337,7 @@ module Unicorn
|
|
324
337
|
@before_exec.call(self) if @before_exec
|
325
338
|
exec(*cmd)
|
326
339
|
end
|
340
|
+
$0 = "unicorn master (old)"
|
327
341
|
end
|
328
342
|
|
329
343
|
# forcibly terminate all workers that haven't checked in in @timeout
|
@@ -428,7 +442,7 @@ module Unicorn
|
|
428
442
|
@listeners.each { |sock| sock.close rescue nil } # break IO.select
|
429
443
|
end
|
430
444
|
|
431
|
-
while alive && @master_pid == ppid
|
445
|
+
while alive && @master_pid == Process.ppid
|
432
446
|
# we're a goner in @timeout seconds anyways if tempfile.chmod
|
433
447
|
# breaks, so don't trap the exception. Using fchmod() since
|
434
448
|
# futimes() is not available in base Ruby and I very strongly
|
@@ -494,7 +508,7 @@ module Unicorn
|
|
494
508
|
# is no longer running.
|
495
509
|
def kill_worker(signal, pid)
|
496
510
|
begin
|
497
|
-
kill(signal, pid)
|
511
|
+
Process.kill(signal, pid)
|
498
512
|
rescue Errno::ESRCH
|
499
513
|
worker = @workers.delete(pid) and worker.tempfile.close rescue nil
|
500
514
|
end
|
@@ -516,7 +530,7 @@ module Unicorn
|
|
516
530
|
def valid_pid?(path)
|
517
531
|
if File.exist?(path) && (pid = File.read(path).to_i) > 1
|
518
532
|
begin
|
519
|
-
kill(0, pid)
|
533
|
+
Process.kill(0, pid)
|
520
534
|
return pid
|
521
535
|
rescue Errno::ESRCH
|
522
536
|
end
|
@@ -0,0 +1,150 @@
|
|
1
|
+
require 'unicorn'
|
2
|
+
require 'rack'
|
3
|
+
|
4
|
+
module Unicorn::App
|
5
|
+
|
6
|
+
# This class is highly experimental (even more so than the rest of Unicorn)
|
7
|
+
# and has never run anything other than cgit.
|
8
|
+
class ExecCgi
|
9
|
+
|
10
|
+
CHUNK_SIZE = 16384
|
11
|
+
PASS_VARS = %w(
|
12
|
+
CONTENT_LENGTH
|
13
|
+
CONTENT_TYPE
|
14
|
+
GATEWAY_INTERFACE
|
15
|
+
AUTH_TYPE
|
16
|
+
PATH_INFO
|
17
|
+
PATH_TRANSLATED
|
18
|
+
QUERY_STRING
|
19
|
+
REMOTE_ADDR
|
20
|
+
REMOTE_HOST
|
21
|
+
REMOTE_IDENT
|
22
|
+
REMOTE_USER
|
23
|
+
REQUEST_METHOD
|
24
|
+
SERVER_NAME
|
25
|
+
SERVER_PORT
|
26
|
+
SERVER_PROTOCOL
|
27
|
+
SERVER_SOFTWARE
|
28
|
+
).map { |x| x.freeze }.freeze # frozen strings are faster for Hash lookups
|
29
|
+
|
30
|
+
# Intializes the app, example of usage in a config.ru
|
31
|
+
# map "/cgit" do
|
32
|
+
# run Unicorn::App::ExecCgi.new("/path/to/cgit.cgi")
|
33
|
+
# end
|
34
|
+
def initialize(*args)
|
35
|
+
@args = args.dup
|
36
|
+
first = @args[0] or
|
37
|
+
raise ArgumentError, "need path to executable"
|
38
|
+
first[0..0] == "/" or @args[0] = ::File.expand_path(first)
|
39
|
+
File.executable?(@args[0]) or
|
40
|
+
raise ArgumentError, "#{@args[0]} is not executable"
|
41
|
+
end
|
42
|
+
|
43
|
+
# Calls the app
|
44
|
+
def call(env)
|
45
|
+
out, err = Tempfile.new(''), Tempfile.new('')
|
46
|
+
out.unlink
|
47
|
+
err.unlink
|
48
|
+
inp = force_file_input(env)
|
49
|
+
inp.sync = out.sync = err.sync = true
|
50
|
+
pid = fork { run_child(inp, out, err, env) }
|
51
|
+
inp.close
|
52
|
+
pid, status = Process.waitpid2(pid)
|
53
|
+
write_errors(env, err, status) if err.stat.size > 0
|
54
|
+
err.close
|
55
|
+
|
56
|
+
return parse_output!(out) if status.success?
|
57
|
+
out.close
|
58
|
+
[ 500, { 'Content-Length' => '0', 'Content-Type' => 'text/plain' }, [] ]
|
59
|
+
end
|
60
|
+
|
61
|
+
private
|
62
|
+
|
63
|
+
def run_child(inp, out, err, env)
|
64
|
+
PASS_VARS.each do |key|
|
65
|
+
val = env[key] or next
|
66
|
+
ENV[key] = val
|
67
|
+
end
|
68
|
+
ENV['SCRIPT_NAME'] = @args[0]
|
69
|
+
ENV['GATEWAY_INTERFACE'] = 'CGI/1.1'
|
70
|
+
env.keys.grep(/^HTTP_/) { |key| ENV[key] = env[key] }
|
71
|
+
|
72
|
+
IO.new(0).reopen(inp)
|
73
|
+
IO.new(1).reopen(out)
|
74
|
+
IO.new(2).reopen(err)
|
75
|
+
exec(*@args)
|
76
|
+
end
|
77
|
+
|
78
|
+
# Extracts headers from CGI out, will change the offset of out.
|
79
|
+
# This returns a standard Rack-compatible return value:
|
80
|
+
# [ 200, HeadersHash, body ]
|
81
|
+
def parse_output!(out)
|
82
|
+
size = out.stat.size
|
83
|
+
out.sysseek(0)
|
84
|
+
head = out.sysread(CHUNK_SIZE)
|
85
|
+
offset = 2
|
86
|
+
head, body = head.split(/\n\n/, 2)
|
87
|
+
if body.nil?
|
88
|
+
head, body = head.split(/\r\n\r\n/, 2)
|
89
|
+
offset = 4
|
90
|
+
end
|
91
|
+
offset += head.length
|
92
|
+
out.instance_variable_set('@unicorn_app_exec_cgi_offset', offset)
|
93
|
+
size -= offset
|
94
|
+
|
95
|
+
# Allows +out+ to be used as a Rack body.
|
96
|
+
def out.each
|
97
|
+
sysseek(@unicorn_app_exec_cgi_offset)
|
98
|
+
begin
|
99
|
+
loop { yield(sysread(CHUNK_SIZE)) }
|
100
|
+
rescue EOFError
|
101
|
+
end
|
102
|
+
end
|
103
|
+
|
104
|
+
prev = nil
|
105
|
+
headers = Rack::Utils::HeaderHash.new
|
106
|
+
head.split(/\r?\n/).each do |line|
|
107
|
+
case line
|
108
|
+
when /^([A-Za-z0-9-]+):\s*(.*)$/ then headers[prev = $1] = $2
|
109
|
+
when /^[ \t]/ then headers[prev] << "\n#{line}" if prev
|
110
|
+
end
|
111
|
+
end
|
112
|
+
headers['Content-Length'] = size.to_s
|
113
|
+
[ 200, headers, out ]
|
114
|
+
end
|
115
|
+
|
116
|
+
# ensures rack.input is a file handle that we can redirect stdin to
|
117
|
+
def force_file_input(env)
|
118
|
+
inp = env['rack.input']
|
119
|
+
if inp.respond_to?(:fileno) && Integer === inp.fileno
|
120
|
+
inp
|
121
|
+
elsif inp.size == 0 # inp could be a StringIO or StringIO-like object
|
122
|
+
::File.open('/dev/null')
|
123
|
+
else
|
124
|
+
tmp = Tempfile.new('')
|
125
|
+
tmp.unlink
|
126
|
+
tmp.binmode
|
127
|
+
|
128
|
+
# Rack::Lint::InputWrapper doesn't allow sysread :(
|
129
|
+
while buf = inp.read(CHUNK_SIZE)
|
130
|
+
tmp.syswrite(buf)
|
131
|
+
end
|
132
|
+
tmp.sysseek(0)
|
133
|
+
tmp
|
134
|
+
end
|
135
|
+
end
|
136
|
+
|
137
|
+
# rack.errors this may not be an IO object, so we couldn't
|
138
|
+
# just redirect the CGI executable to that earlier.
|
139
|
+
def write_errors(env, err, status)
|
140
|
+
err.seek(0)
|
141
|
+
dst = env['rack.errors']
|
142
|
+
pid = status.pid
|
143
|
+
dst.write("#{pid}: #{@args.inspect} status=#{status} stderr:\n")
|
144
|
+
err.each_line { |line| dst.write("#{pid}: #{line}") }
|
145
|
+
dst.flush
|
146
|
+
end
|
147
|
+
|
148
|
+
end
|
149
|
+
|
150
|
+
end
|
data/lib/unicorn/configurator.rb
CHANGED
@@ -173,13 +173,14 @@ module Unicorn
|
|
173
173
|
# worker processes. For per-worker listeners, see the after_fork example
|
174
174
|
def listeners(addresses)
|
175
175
|
Array === addresses or addresses = Array(addresses)
|
176
|
+
addresses.map! { |addr| expand_addr(addr) }
|
176
177
|
@set[:listeners] = addresses
|
177
178
|
end
|
178
179
|
|
179
180
|
# adds an +address+ to the existing listener set
|
180
181
|
def listen(address)
|
181
182
|
@set[:listeners] = [] unless Array === @set[:listeners]
|
182
|
-
@set[:listeners] << address
|
183
|
+
@set[:listeners] << expand_addr(address)
|
183
184
|
end
|
184
185
|
|
185
186
|
# sets the +path+ for the PID file of the unicorn master process
|
@@ -253,5 +254,17 @@ module Unicorn
|
|
253
254
|
@set[var] = my_proc
|
254
255
|
end
|
255
256
|
|
257
|
+
# expands pathnames of sockets if relative to "~" or "~username"
|
258
|
+
# expands "*:port and ":port" to "0.0.0.0:port"
|
259
|
+
def expand_addr(address) #:nodoc
|
260
|
+
return address unless String === address
|
261
|
+
if address[0..0] == '~'
|
262
|
+
return File.expand_path(address)
|
263
|
+
elsif address =~ %r{\A\*?:(\d+)\z}
|
264
|
+
return "0.0.0.0:#$1"
|
265
|
+
end
|
266
|
+
address
|
267
|
+
end
|
268
|
+
|
256
269
|
end
|
257
270
|
end
|
data/lib/unicorn/const.rb
CHANGED
data/lib/unicorn/http_request.rb
CHANGED
@@ -130,8 +130,6 @@ module Unicorn
|
|
130
130
|
raise "No REQUEST PATH" unless @params[Const::REQUEST_PATH]
|
131
131
|
|
132
132
|
@params["QUERY_STRING"] ||= ''
|
133
|
-
@params.delete "HTTP_CONTENT_TYPE"
|
134
|
-
@params.delete "HTTP_CONTENT_LENGTH"
|
135
133
|
@params.update({ "rack.version" => [0,1],
|
136
134
|
"rack.input" => @body,
|
137
135
|
"rack.errors" => $stderr,
|
@@ -155,7 +153,7 @@ module Unicorn
|
|
155
153
|
end
|
156
154
|
true # success!
|
157
155
|
rescue Object => e
|
158
|
-
logger.error "Error reading HTTP body: #{e.inspect}"
|
156
|
+
@logger.error "Error reading HTTP body: #{e.inspect}"
|
159
157
|
socket.closed? or socket.close rescue nil
|
160
158
|
|
161
159
|
# Any errors means we should delete the file, including if the file
|
@@ -21,35 +21,32 @@ module Unicorn
|
|
21
21
|
|
22
22
|
class HttpResponse
|
23
23
|
|
24
|
-
#
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
'Warning' => true,
|
29
|
-
'WWW-Authenticate' => true,
|
30
|
-
}.freeze
|
24
|
+
# Rack does not set/require a Date: header. We always override the
|
25
|
+
# Connection: and Date: headers no matter what (if anything) our
|
26
|
+
# Rack application sent us.
|
27
|
+
SKIP = { 'connection' => true, 'date' => true }.freeze
|
31
28
|
|
32
29
|
# writes the rack_response to socket as an HTTP response
|
33
30
|
def self.write(socket, rack_response)
|
34
31
|
status, headers, body = rack_response
|
32
|
+
out = [ "Date: #{Time.now.httpdate}" ]
|
35
33
|
|
36
|
-
#
|
37
|
-
#
|
38
|
-
out = [ "#{Const::DATE}: #{Time.now.httpdate}" ]
|
39
|
-
sent = { Const::CONNECTION => true, Const::DATE => true }
|
40
|
-
|
34
|
+
# Don't bother enforcing duplicate supression, it's a Hash most of
|
35
|
+
# the time anyways so just hope our app knows what it's doing
|
41
36
|
headers.each do |key, value|
|
42
|
-
if
|
43
|
-
|
44
|
-
out << "#{key}: #{value}"
|
45
|
-
end
|
37
|
+
next if SKIP.include?(key.downcase)
|
38
|
+
value.split(/\n/).each { |v| out << "#{key}: #{v}" }
|
46
39
|
end
|
47
40
|
|
41
|
+
# Rack should enforce Content-Length or chunked transfer encoding,
|
42
|
+
# so don't worry or care about them.
|
48
43
|
socket_write(socket,
|
49
44
|
"HTTP/1.1 #{status} #{HTTP_STATUS_CODES[status]}\r\n" \
|
50
45
|
"Connection: close\r\n" \
|
51
46
|
"#{out.join("\r\n")}\r\n\r\n")
|
52
47
|
body.each { |chunk| socket_write(socket, chunk) }
|
48
|
+
ensure
|
49
|
+
body.respond_to?(:close) and body.close rescue nil
|
53
50
|
end
|
54
51
|
|
55
52
|
private
|
@@ -0,0 +1,33 @@
|
|
1
|
+
$stdin.sync = $stdout.sync = $stderr.sync = true
|
2
|
+
require 'unicorn'
|
3
|
+
|
4
|
+
class Unicorn::Launcher
|
5
|
+
|
6
|
+
# We don't do a lot of standard daemonization stuff:
|
7
|
+
# * umask is whatever was set by the parent process at startup
|
8
|
+
# and can be set in config.ru and config_file, so making it
|
9
|
+
# 0000 and potentially exposing sensitive log data can be bad
|
10
|
+
# policy.
|
11
|
+
# * don't bother to chdir("/") here since unicorn is designed to
|
12
|
+
# run inside APP_ROOT. Unicorn will also re-chdir() to
|
13
|
+
# the directory it was started in when being re-executed
|
14
|
+
# to pickup code changes if the original deployment directory
|
15
|
+
# is a symlink or otherwise got replaced.
|
16
|
+
def self.daemonize!
|
17
|
+
$stdin.reopen("/dev/null")
|
18
|
+
|
19
|
+
# We only start a new process group if we're not being reexecuted
|
20
|
+
# and inheriting file descriptors from our parent
|
21
|
+
unless ENV['UNICORN_FD']
|
22
|
+
exit if fork
|
23
|
+
Process.setsid
|
24
|
+
exit if fork
|
25
|
+
|
26
|
+
# $stderr/$stderr can/will be redirected separately in the Unicorn config
|
27
|
+
$stdout.reopen("/dev/null", "a")
|
28
|
+
$stderr.reopen("/dev/null", "a")
|
29
|
+
end
|
30
|
+
$stdin.sync = $stdout.sync = $stderr.sync = true
|
31
|
+
end
|
32
|
+
|
33
|
+
end
|
data/lib/unicorn/socket.rb
CHANGED
@@ -75,7 +75,6 @@ module Unicorn
|
|
75
75
|
def bind_listen(address = '0.0.0.0:8080', backlog = 1024)
|
76
76
|
return address unless String === address
|
77
77
|
|
78
|
-
address = File.expand_path(address) if address[0..0] == "~"
|
79
78
|
domain, bind_addr = if address[0..0] == "/"
|
80
79
|
if File.exist?(address)
|
81
80
|
if File.socket?(address)
|
data/test/exec/test_exec.rb
CHANGED
@@ -300,7 +300,7 @@ end
|
|
300
300
|
sleep DEFAULT_RES
|
301
301
|
log = File.readlines(rotate.path)
|
302
302
|
end
|
303
|
-
assert_equal 4, log.grep(/rotating logs\.\.\./).size
|
303
|
+
assert_equal 4, log.grep(/worker=\d+ rotating logs\.\.\./).size
|
304
304
|
assert_equal 0, log.grep(/done rotating logs/).size
|
305
305
|
|
306
306
|
tries = DEFAULT_TRIES
|
@@ -309,7 +309,7 @@ end
|
|
309
309
|
sleep DEFAULT_RES
|
310
310
|
log = File.readlines(COMMON_TMP.path)
|
311
311
|
end
|
312
|
-
assert_equal 4, log.grep(/done rotating logs/).size
|
312
|
+
assert_equal 4, log.grep(/worker=\d+ done rotating logs/).size
|
313
313
|
assert_equal 0, log.grep(/rotating logs\.\.\./).size
|
314
314
|
assert_nothing_raised { Process.kill('QUIT', pid) }
|
315
315
|
status = nil
|
@@ -25,11 +25,10 @@ class HttpParserTest < Test::Unit::TestCase
|
|
25
25
|
assert_equal '/', req['REQUEST_PATH']
|
26
26
|
assert_equal 'HTTP/1.1', req['HTTP_VERSION']
|
27
27
|
assert_equal '/', req['REQUEST_URI']
|
28
|
-
assert_equal 'CGI/1.2', req['GATEWAY_INTERFACE']
|
29
28
|
assert_equal 'GET', req['REQUEST_METHOD']
|
30
29
|
assert_nil req['FRAGMENT']
|
31
30
|
assert_nil req['QUERY_STRING']
|
32
|
-
|
31
|
+
|
33
32
|
parser.reset
|
34
33
|
assert parser.nread == 0, "Number read after reset should be 0"
|
35
34
|
end
|
@@ -0,0 +1,82 @@
|
|
1
|
+
# Copyright (c) 2009 Eric Wong
|
2
|
+
# You can redistribute it and/or modify it under the same terms as Ruby.
|
3
|
+
|
4
|
+
if RUBY_VERSION =~ /1\.9/
|
5
|
+
warn "#$0 current broken under Ruby 1.9 with Rack"
|
6
|
+
exit 0
|
7
|
+
end
|
8
|
+
|
9
|
+
require 'test/test_helper'
|
10
|
+
begin
|
11
|
+
require 'rack'
|
12
|
+
require 'rack/lint'
|
13
|
+
rescue LoadError
|
14
|
+
warn "Unable to load rack, skipping test"
|
15
|
+
exit 0
|
16
|
+
end
|
17
|
+
|
18
|
+
include Unicorn
|
19
|
+
|
20
|
+
class RequestTest < Test::Unit::TestCase
|
21
|
+
|
22
|
+
class MockRequest < StringIO
|
23
|
+
def unicorn_peeraddr
|
24
|
+
'666.666.666.666'
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
28
|
+
def setup
|
29
|
+
@request = HttpRequest.new(Logger.new($stderr))
|
30
|
+
@app = lambda do |env|
|
31
|
+
[ 200, { 'Content-Length' => '0', 'Content-Type' => 'text/plain' }, [] ]
|
32
|
+
end
|
33
|
+
@lint = Rack::Lint.new(@app)
|
34
|
+
end
|
35
|
+
|
36
|
+
def test_rack_lint_get
|
37
|
+
client = MockRequest.new("GET / HTTP/1.1\r\nHost: foo\r\n\r\n")
|
38
|
+
res = env = nil
|
39
|
+
assert_nothing_raised { env = @request.read(client) }
|
40
|
+
assert_equal '666.666.666.666', env['REMOTE_ADDR']
|
41
|
+
assert_nothing_raised { res = @lint.call(env) }
|
42
|
+
end
|
43
|
+
|
44
|
+
def test_rack_lint_put
|
45
|
+
client = MockRequest.new(
|
46
|
+
"PUT / HTTP/1.1\r\n" \
|
47
|
+
"Host: foo\r\n" \
|
48
|
+
"Content-Length: 5\r\n" \
|
49
|
+
"\r\n" \
|
50
|
+
"abcde")
|
51
|
+
res = env = nil
|
52
|
+
assert_nothing_raised { env = @request.read(client) }
|
53
|
+
assert_nothing_raised { res = @lint.call(env) }
|
54
|
+
end
|
55
|
+
|
56
|
+
def test_rack_lint_big_put
|
57
|
+
count = 100
|
58
|
+
bs = 0x10000
|
59
|
+
buf = (' ' * bs).freeze
|
60
|
+
length = bs * count
|
61
|
+
client = Tempfile.new('big_put')
|
62
|
+
def client.unicorn_peeraddr
|
63
|
+
'1.1.1.1'
|
64
|
+
end
|
65
|
+
client.syswrite(
|
66
|
+
"PUT / HTTP/1.1\r\n" \
|
67
|
+
"Host: foo\r\n" \
|
68
|
+
"Content-Length: #{length}\r\n" \
|
69
|
+
"\r\n")
|
70
|
+
count.times { assert_equal bs, client.syswrite(buf) }
|
71
|
+
assert_equal 0, client.sysseek(0)
|
72
|
+
res = env = nil
|
73
|
+
assert_nothing_raised { env = @request.read(client) }
|
74
|
+
assert_equal length, env['rack.input'].size
|
75
|
+
count.times { assert_equal buf, env['rack.input'].read(bs) }
|
76
|
+
assert_nil env['rack.input'].read(bs)
|
77
|
+
assert_nothing_raised { env['rack.input'].rewind }
|
78
|
+
assert_nothing_raised { res = @lint.call(env) }
|
79
|
+
end
|
80
|
+
|
81
|
+
end
|
82
|
+
|
data/test/unit/test_response.rb
CHANGED
@@ -41,5 +41,12 @@ class ResponseTest < Test::Unit::TestCase
|
|
41
41
|
io.rewind
|
42
42
|
assert_match(/.* #{HTTP_STATUS_CODES[code]}$/, io.readline.chomp, "wrong default reason phrase")
|
43
43
|
end
|
44
|
+
|
45
|
+
def test_rack_multivalue_headers
|
46
|
+
out = StringIO.new
|
47
|
+
HttpResponse.write(out,[200, {"X-Whatever" => "stuff\nbleh"}, []])
|
48
|
+
assert_match(/^X-Whatever: stuff\r\nX-Whatever: bleh\r\n/, out.string)
|
49
|
+
end
|
50
|
+
|
44
51
|
end
|
45
52
|
|
data/unicorn.gemspec
CHANGED
@@ -2,17 +2,17 @@
|
|
2
2
|
|
3
3
|
Gem::Specification.new do |s|
|
4
4
|
s.name = %q{unicorn}
|
5
|
-
s.version = "0.2.
|
5
|
+
s.version = "0.2.2"
|
6
6
|
|
7
7
|
s.required_rubygems_version = Gem::Requirement.new(">= 1.2") if s.respond_to? :required_rubygems_version=
|
8
8
|
s.authors = ["Eric Wong"]
|
9
|
-
s.date = %q{2009-03-
|
9
|
+
s.date = %q{2009-03-22}
|
10
10
|
s.description = %q{A small fast HTTP library and server for Rack applications.}
|
11
11
|
s.email = %q{normalperson@yhbt.net}
|
12
12
|
s.executables = ["unicorn", "unicorn_rails"]
|
13
13
|
s.extensions = ["ext/unicorn/http11/extconf.rb"]
|
14
|
-
s.extra_rdoc_files = ["CHANGELOG", "LICENSE", "README", "TODO", "bin/unicorn", "bin/unicorn_rails", "ext/unicorn/http11/ext_help.h", "ext/unicorn/http11/extconf.rb", "ext/unicorn/http11/http11.c", "ext/unicorn/http11/http11_parser.c", "ext/unicorn/http11/http11_parser.h", "ext/unicorn/http11/http11_parser.rl", "ext/unicorn/http11/http11_parser_common.rl", "lib/unicorn.rb", "lib/unicorn/configurator.rb", "lib/unicorn/const.rb", "lib/unicorn/http_request.rb", "lib/unicorn/http_response.rb", "lib/unicorn/socket.rb", "lib/unicorn/util.rb"]
|
15
|
-
s.files = [".document", ".gitignore", "CHANGELOG", "CONTRIBUTORS", "DESIGN", "GNUmakefile", "LICENSE", "Manifest", "README", "Rakefile", "SIGNALS", "TODO", "bin/unicorn", "bin/unicorn_rails", "ext/unicorn/http11/ext_help.h", "ext/unicorn/http11/extconf.rb", "ext/unicorn/http11/http11.c", "ext/unicorn/http11/http11_parser.c", "ext/unicorn/http11/http11_parser.h", "ext/unicorn/http11/http11_parser.rl", "ext/unicorn/http11/http11_parser_common.rl", "lib/unicorn.rb", "lib/unicorn/configurator.rb", "lib/unicorn/const.rb", "lib/unicorn/http_request.rb", "lib/unicorn/http_response.rb", "lib/unicorn/socket.rb", "lib/unicorn/util.rb", "setup.rb", "test/aggregate.rb", "test/benchmark/previous.rb", "test/benchmark/simple.rb", "test/benchmark/utils.rb", "test/exec/README", "test/exec/test_exec.rb", "test/test_helper.rb", "test/tools/trickletest.rb", "test/unit/test_configurator.rb", "test/unit/test_http_parser.rb", "test/unit/test_response.rb", "test/unit/test_server.rb", "test/unit/test_upload.rb", "unicorn.gemspec"]
|
14
|
+
s.extra_rdoc_files = ["CHANGELOG", "LICENSE", "README", "TODO", "bin/unicorn", "bin/unicorn_rails", "ext/unicorn/http11/ext_help.h", "ext/unicorn/http11/extconf.rb", "ext/unicorn/http11/http11.c", "ext/unicorn/http11/http11_parser.c", "ext/unicorn/http11/http11_parser.h", "ext/unicorn/http11/http11_parser.rl", "ext/unicorn/http11/http11_parser_common.rl", "lib/unicorn.rb", "lib/unicorn/app/exec_cgi.rb", "lib/unicorn/configurator.rb", "lib/unicorn/const.rb", "lib/unicorn/http_request.rb", "lib/unicorn/http_response.rb", "lib/unicorn/launcher.rb", "lib/unicorn/socket.rb", "lib/unicorn/util.rb"]
|
15
|
+
s.files = [".document", ".gitignore", "CHANGELOG", "CONTRIBUTORS", "DESIGN", "GNUmakefile", "LICENSE", "Manifest", "README", "Rakefile", "SIGNALS", "TODO", "bin/unicorn", "bin/unicorn_rails", "ext/unicorn/http11/ext_help.h", "ext/unicorn/http11/extconf.rb", "ext/unicorn/http11/http11.c", "ext/unicorn/http11/http11_parser.c", "ext/unicorn/http11/http11_parser.h", "ext/unicorn/http11/http11_parser.rl", "ext/unicorn/http11/http11_parser_common.rl", "lib/unicorn.rb", "lib/unicorn/app/exec_cgi.rb", "lib/unicorn/configurator.rb", "lib/unicorn/const.rb", "lib/unicorn/http_request.rb", "lib/unicorn/http_response.rb", "lib/unicorn/launcher.rb", "lib/unicorn/socket.rb", "lib/unicorn/util.rb", "setup.rb", "test/aggregate.rb", "test/benchmark/previous.rb", "test/benchmark/simple.rb", "test/benchmark/utils.rb", "test/exec/README", "test/exec/test_exec.rb", "test/test_helper.rb", "test/tools/trickletest.rb", "test/unit/test_configurator.rb", "test/unit/test_http_parser.rb", "test/unit/test_request.rb", "test/unit/test_response.rb", "test/unit/test_server.rb", "test/unit/test_upload.rb", "unicorn.gemspec"]
|
16
16
|
s.has_rdoc = true
|
17
17
|
s.homepage = %q{http://unicorn.bogomips.org}
|
18
18
|
s.rdoc_options = ["--line-numbers", "--inline-source", "--title", "Unicorn", "--main", "README"]
|
@@ -20,7 +20,7 @@ Gem::Specification.new do |s|
|
|
20
20
|
s.rubyforge_project = %q{unicorn}
|
21
21
|
s.rubygems_version = %q{1.3.1}
|
22
22
|
s.summary = %q{A small fast HTTP library and server for Rack applications.}
|
23
|
-
s.test_files = ["test/unit/test_configurator.rb", "test/unit/
|
23
|
+
s.test_files = ["test/unit/test_configurator.rb", "test/unit/test_server.rb", "test/unit/test_upload.rb", "test/unit/test_response.rb", "test/unit/test_http_parser.rb", "test/unit/test_request.rb"]
|
24
24
|
|
25
25
|
if s.respond_to? :specification_version then
|
26
26
|
current_version = Gem::Specification::CURRENT_SPECIFICATION_VERSION
|
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: unicorn
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.2.
|
4
|
+
version: 0.2.2
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Eric Wong
|
@@ -9,7 +9,7 @@ autorequire:
|
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
11
|
|
12
|
-
date: 2009-03-
|
12
|
+
date: 2009-03-22 00:00:00 -07:00
|
13
13
|
default_executable:
|
14
14
|
dependencies: []
|
15
15
|
|
@@ -35,10 +35,12 @@ extra_rdoc_files:
|
|
35
35
|
- ext/unicorn/http11/http11_parser.rl
|
36
36
|
- ext/unicorn/http11/http11_parser_common.rl
|
37
37
|
- lib/unicorn.rb
|
38
|
+
- lib/unicorn/app/exec_cgi.rb
|
38
39
|
- lib/unicorn/configurator.rb
|
39
40
|
- lib/unicorn/const.rb
|
40
41
|
- lib/unicorn/http_request.rb
|
41
42
|
- lib/unicorn/http_response.rb
|
43
|
+
- lib/unicorn/launcher.rb
|
42
44
|
- lib/unicorn/socket.rb
|
43
45
|
- lib/unicorn/util.rb
|
44
46
|
files:
|
@@ -64,10 +66,12 @@ files:
|
|
64
66
|
- ext/unicorn/http11/http11_parser.rl
|
65
67
|
- ext/unicorn/http11/http11_parser_common.rl
|
66
68
|
- lib/unicorn.rb
|
69
|
+
- lib/unicorn/app/exec_cgi.rb
|
67
70
|
- lib/unicorn/configurator.rb
|
68
71
|
- lib/unicorn/const.rb
|
69
72
|
- lib/unicorn/http_request.rb
|
70
73
|
- lib/unicorn/http_response.rb
|
74
|
+
- lib/unicorn/launcher.rb
|
71
75
|
- lib/unicorn/socket.rb
|
72
76
|
- lib/unicorn/util.rb
|
73
77
|
- setup.rb
|
@@ -81,6 +85,7 @@ files:
|
|
81
85
|
- test/tools/trickletest.rb
|
82
86
|
- test/unit/test_configurator.rb
|
83
87
|
- test/unit/test_http_parser.rb
|
88
|
+
- test/unit/test_request.rb
|
84
89
|
- test/unit/test_response.rb
|
85
90
|
- test/unit/test_server.rb
|
86
91
|
- test/unit/test_upload.rb
|
@@ -119,7 +124,8 @@ specification_version: 2
|
|
119
124
|
summary: A small fast HTTP library and server for Rack applications.
|
120
125
|
test_files:
|
121
126
|
- test/unit/test_configurator.rb
|
122
|
-
- test/unit/
|
127
|
+
- test/unit/test_server.rb
|
123
128
|
- test/unit/test_upload.rb
|
129
|
+
- test/unit/test_response.rb
|
124
130
|
- test/unit/test_http_parser.rb
|
125
|
-
- test/unit/
|
131
|
+
- test/unit/test_request.rb
|