unicorn 0.2.1 → 0.2.2
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/.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
|