unicorn 0.5.1 → 0.5.2
Sign up to get free protection for your applications and to get access to all the features.
- data/CHANGELOG +1 -0
- data/GNUmakefile +7 -7
- data/ext/unicorn/http11/http11.c +22 -24
- data/lib/unicorn.rb +27 -29
- data/lib/unicorn/const.rb +40 -40
- data/lib/unicorn/http_response.rb +7 -3
- data/test/exec/test_exec.rb +6 -6
- data/test/unit/test_response.rb +21 -0
- data/unicorn.gemspec +2 -2
- metadata +2 -2
data/CHANGELOG
CHANGED
data/GNUmakefile
CHANGED
@@ -32,23 +32,23 @@ inst_deps := $(wildcard bin/*) $(wildcard lib/*.rb) \
|
|
32
32
|
|
33
33
|
ext/unicorn/http11/http11_parser.c: ext/unicorn/http11/http11_parser.rl
|
34
34
|
cd $(@D) && ragel $(<F) -C -G2 -o $(@F)
|
35
|
-
ext/unicorn/http11/Makefile: ext/unicorn/http11/extconf.rb
|
35
|
+
ext/unicorn/http11/Makefile: ext/unicorn/http11/extconf.rb $(http11_deps)
|
36
36
|
cd $(@D) && $(ruby) $(<F)
|
37
|
-
ext/unicorn/http11/http11.$(DLEXT):
|
37
|
+
ext/unicorn/http11/http11.$(DLEXT): ext/unicorn/http11/Makefile
|
38
38
|
$(MAKE) -C $(@D)
|
39
39
|
lib/unicorn/http11.$(DLEXT): ext/unicorn/http11/http11.$(DLEXT)
|
40
40
|
@mkdir -p lib
|
41
41
|
install -m644 $< $@
|
42
42
|
http11: lib/unicorn/http11.$(DLEXT)
|
43
43
|
|
44
|
-
$(test_prefix)/.stamp:
|
45
|
-
$(MAKE) clean-http11
|
46
|
-
$(MAKE) install-test
|
44
|
+
$(test_prefix)/.stamp: install-test
|
47
45
|
> $@
|
48
46
|
|
49
|
-
install-test:
|
47
|
+
install-test: $(inst_deps)
|
48
|
+
test -n "$(test_prefix)"
|
50
49
|
mkdir -p $(test_prefix)/.ccache
|
51
50
|
tar c bin ext lib GNUmakefile | (cd $(test_prefix) && tar x)
|
51
|
+
$(MAKE) -C $(test_prefix) clean
|
52
52
|
$(MAKE) -C $(test_prefix) http11 shebang
|
53
53
|
|
54
54
|
# this is only intended to be run within $(test_prefix)
|
@@ -62,7 +62,7 @@ test: $(T) $(T_n)
|
|
62
62
|
|
63
63
|
test-exec: $(wildcard test/exec/test_*.rb)
|
64
64
|
test-unit: $(wildcard test/unit/test_*.rb)
|
65
|
-
$(slow_tests):
|
65
|
+
$(slow_tests): $(test_prefix)/.stamp
|
66
66
|
@$(MAKE) $(shell $(awk_slow) $@)
|
67
67
|
|
68
68
|
TEST_OPTS = -v
|
data/ext/unicorn/http11/http11.c
CHANGED
@@ -40,6 +40,7 @@ static VALUE global_server_protocol_value;
|
|
40
40
|
static VALUE global_http_host;
|
41
41
|
static VALUE global_http_x_forwarded_proto;
|
42
42
|
static VALUE global_port_80;
|
43
|
+
static VALUE global_port_443;
|
43
44
|
static VALUE global_localhost;
|
44
45
|
static VALUE global_http;
|
45
46
|
|
@@ -243,40 +244,36 @@ static void http_version(void *data, const char *at, size_t length)
|
|
243
244
|
rb_hash_aset(req, global_http_version, val);
|
244
245
|
}
|
245
246
|
|
246
|
-
/** Finalizes the request header to have a bunch of stuff that's
|
247
|
-
needed. */
|
248
|
-
|
247
|
+
/** Finalizes the request header to have a bunch of stuff that's needed. */
|
249
248
|
static void header_done(void *data, const char *at, size_t length)
|
250
249
|
{
|
251
250
|
VALUE req = (VALUE)data;
|
252
|
-
VALUE
|
253
|
-
|
251
|
+
VALUE server_name = global_localhost;
|
252
|
+
VALUE server_port = global_port_80;
|
253
|
+
VALUE temp;
|
254
254
|
|
255
255
|
/* set rack.url_scheme to "https" or "http", no others are allowed by Rack */
|
256
|
-
temp = rb_hash_aref(req, global_http_x_forwarded_proto)
|
257
|
-
|
258
|
-
|
259
|
-
|
260
|
-
|
261
|
-
|
256
|
+
if ((temp = rb_hash_aref(req, global_http_x_forwarded_proto)) != Qnil &&
|
257
|
+
RSTRING_LEN(temp) == 5 &&
|
258
|
+
!memcmp("https", RSTRING_PTR(temp), 5))
|
259
|
+
server_port = global_port_443;
|
260
|
+
else
|
261
|
+
temp = global_http;
|
262
262
|
rb_hash_aset(req, global_rack_url_scheme, temp);
|
263
263
|
|
264
|
-
/* set the SERVER_NAME and SERVER_PORT variables */
|
265
|
-
if((temp = rb_hash_aref(req, global_http_host)) != Qnil) {
|
266
|
-
colon = memchr(RSTRING_PTR(temp), ':', RSTRING_LEN(temp));
|
267
|
-
if(colon
|
268
|
-
|
269
|
-
|
270
|
-
|
271
|
-
RSTRING_LEN(temp)));
|
264
|
+
/* parse and set the SERVER_NAME and SERVER_PORT variables */
|
265
|
+
if ((temp = rb_hash_aref(req, global_http_host)) != Qnil) {
|
266
|
+
char *colon = memchr(RSTRING_PTR(temp), ':', RSTRING_LEN(temp));
|
267
|
+
if (colon) {
|
268
|
+
server_name = rb_str_substr(temp, 0, colon - RSTRING_PTR(temp));
|
269
|
+
server_port = rb_str_substr(temp, colon - RSTRING_PTR(temp)+1,
|
270
|
+
RSTRING_LEN(temp));
|
272
271
|
} else {
|
273
|
-
|
274
|
-
rb_hash_aset(req, global_server_port, global_port_80);
|
272
|
+
server_name = temp;
|
275
273
|
}
|
276
|
-
} else {
|
277
|
-
rb_hash_aset(req, global_server_name, global_localhost);
|
278
|
-
rb_hash_aset(req, global_server_port, global_port_80);
|
279
274
|
}
|
275
|
+
rb_hash_aset(req, global_server_name, server_name);
|
276
|
+
rb_hash_aset(req, global_server_port, server_port);
|
280
277
|
|
281
278
|
/* grab the initial body and stuff it into the hash */
|
282
279
|
rb_hash_aset(req, sym_http_body, rb_str_new(at, length));
|
@@ -402,6 +399,7 @@ void Init_http11()
|
|
402
399
|
DEF_GLOBAL(http_host, "HTTP_HOST");
|
403
400
|
DEF_GLOBAL(http_x_forwarded_proto, "HTTP_X_FORWARDED_PROTO");
|
404
401
|
DEF_GLOBAL(port_80, "80");
|
402
|
+
DEF_GLOBAL(port_443, "443");
|
405
403
|
DEF_GLOBAL(localhost, "localhost");
|
406
404
|
DEF_GLOBAL(http, "http");
|
407
405
|
|
data/lib/unicorn.rb
CHANGED
@@ -52,7 +52,6 @@ module Unicorn
|
|
52
52
|
@start_ctx = DEFAULT_START_CTX.dup
|
53
53
|
@start_ctx.merge!(start_ctx) if start_ctx
|
54
54
|
@app = app
|
55
|
-
@master_pid = $$
|
56
55
|
@workers = Hash.new
|
57
56
|
@io_purgatory = [] # prevents IO objects in here from being GC-ed
|
58
57
|
@request = @rd_sig = @wr_sig = nil
|
@@ -193,9 +192,9 @@ module Unicorn
|
|
193
192
|
stop(false)
|
194
193
|
break
|
195
194
|
when :USR1 # rotate logs
|
196
|
-
logger.info "master
|
195
|
+
logger.info "master reopening logs..."
|
197
196
|
Unicorn::Util.reopen_logs
|
198
|
-
logger.info "master done
|
197
|
+
logger.info "master done reopening logs"
|
199
198
|
kill_each_worker(:USR1)
|
200
199
|
when :USR2 # exec binary, stay alive in case something went wrong
|
201
200
|
reexec
|
@@ -353,7 +352,7 @@ module Unicorn
|
|
353
352
|
io.fcntl(Fcntl::F_SETFD, Fcntl::FD_CLOEXEC)
|
354
353
|
end
|
355
354
|
logger.info "executing #{cmd.inspect} (in #{Dir.pwd})"
|
356
|
-
@before_exec.call(self)
|
355
|
+
@before_exec.call(self)
|
357
356
|
exec(*cmd)
|
358
357
|
end
|
359
358
|
proc_name 'master (old)'
|
@@ -438,7 +437,7 @@ module Unicorn
|
|
438
437
|
@start_ctx = @workers = @rd_sig = @wr_sig = nil
|
439
438
|
@listeners.each { |sock| sock.fcntl(Fcntl::F_SETFD, Fcntl::FD_CLOEXEC) }
|
440
439
|
worker.tempfile.fcntl(Fcntl::F_SETFD, Fcntl::FD_CLOEXEC)
|
441
|
-
@after_fork.call(self, worker)
|
440
|
+
@after_fork.call(self, worker) # can drop perms
|
442
441
|
@request = HttpRequest.new(logger)
|
443
442
|
build_app! unless @preload_app
|
444
443
|
end
|
@@ -447,28 +446,27 @@ module Unicorn
|
|
447
446
|
# for connections and doesn't die until the parent dies (or is
|
448
447
|
# given a INT, QUIT, or TERM signal)
|
449
448
|
def worker_loop(worker)
|
449
|
+
master_pid = Process.ppid # slightly racy, but less memory usage
|
450
450
|
init_worker_process(worker)
|
451
|
-
nr = 0
|
451
|
+
nr = 0 # this becomes negative if we need to reopen logs
|
452
452
|
tempfile = worker.tempfile
|
453
|
-
alive = true
|
454
453
|
ready = @listeners
|
455
454
|
client = nil
|
456
|
-
|
457
|
-
alive = false # graceful shutdown
|
458
|
-
@listeners.each { |sock| sock.close rescue nil } # break IO.select
|
459
|
-
end
|
460
|
-
reopen_logs, (rd, wr) = false, IO.pipe
|
455
|
+
rd, wr = IO.pipe
|
461
456
|
rd.fcntl(Fcntl::F_SETFD, Fcntl::FD_CLOEXEC)
|
462
457
|
wr.fcntl(Fcntl::F_SETFD, Fcntl::FD_CLOEXEC)
|
463
|
-
|
458
|
+
|
459
|
+
# closing anything we IO.select on will raise EBADF
|
460
|
+
trap(:USR1) { nr = -65536; rd.close rescue nil }
|
461
|
+
trap(:QUIT) { @listeners.each { |sock| sock.close rescue nil } }
|
462
|
+
[:TERM, :INT].each { |sig| trap(sig) { exit(0) } } # instant shutdown
|
464
463
|
@logger.info "worker=#{worker.nr} ready"
|
465
464
|
|
466
|
-
while
|
467
|
-
if
|
468
|
-
|
469
|
-
@logger.info "worker=#{worker.nr} rotating logs..."
|
465
|
+
while master_pid == Process.ppid
|
466
|
+
if nr < 0
|
467
|
+
@logger.info "worker=#{worker.nr} reopening logs..."
|
470
468
|
Unicorn::Util.reopen_logs
|
471
|
-
@logger.info "worker=#{worker.nr} done
|
469
|
+
@logger.info "worker=#{worker.nr} done reopening logs"
|
472
470
|
wr.close rescue nil
|
473
471
|
rd, wr = IO.pipe
|
474
472
|
rd.fcntl(Fcntl::F_SETFD, Fcntl::FD_CLOEXEC)
|
@@ -480,11 +478,11 @@ module Unicorn
|
|
480
478
|
# prefer temporary files to be unlinked for security,
|
481
479
|
# performance and reliability reasons, so utime is out. No-op
|
482
480
|
# changes with chmod doesn't update ctime on all filesystems; so
|
483
|
-
# we
|
484
|
-
|
481
|
+
# we change our counter each and every time (after process_client
|
482
|
+
# and before IO.select).
|
483
|
+
tempfile.chmod(nr = 0)
|
485
484
|
|
486
485
|
begin
|
487
|
-
accepted = false
|
488
486
|
ready.each do |sock|
|
489
487
|
begin
|
490
488
|
client = begin
|
@@ -492,22 +490,22 @@ module Unicorn
|
|
492
490
|
rescue Errno::EAGAIN
|
493
491
|
next
|
494
492
|
end
|
495
|
-
accepted = true
|
496
493
|
process_client(client)
|
497
494
|
rescue Errno::ECONNABORTED
|
498
495
|
# client closed the socket even before accept
|
499
496
|
client.close rescue nil
|
497
|
+
ensure
|
498
|
+
tempfile.chmod(nr += 1)
|
499
|
+
break if nr < 0
|
500
500
|
end
|
501
|
-
tempfile.chmod(nr += 1)
|
502
|
-
break if reopen_logs
|
503
501
|
end
|
504
502
|
client = nil
|
505
503
|
|
506
504
|
# make the following bet: if we accepted clients this round,
|
507
|
-
# we're probably reasonably busy, so avoid calling select(
|
508
|
-
# and
|
509
|
-
# before we sleep again in select
|
510
|
-
if
|
505
|
+
# we're probably reasonably busy, so avoid calling select()
|
506
|
+
# and do a speculative accept_nonblock on every listener
|
507
|
+
# before we sleep again in select().
|
508
|
+
if nr != 0 # (nr < 0) => reopen logs
|
511
509
|
ready = @listeners
|
512
510
|
else
|
513
511
|
begin
|
@@ -518,7 +516,7 @@ module Unicorn
|
|
518
516
|
rescue Errno::EINTR
|
519
517
|
ready = @listeners
|
520
518
|
rescue Errno::EBADF => e
|
521
|
-
|
519
|
+
nr < 0 or exit(@listeners[0].closed? ? 0 : 1)
|
522
520
|
end
|
523
521
|
end
|
524
522
|
rescue SignalException, SystemExit => e
|
data/lib/unicorn/const.rb
CHANGED
@@ -4,43 +4,43 @@ module Unicorn
|
|
4
4
|
# Every standard HTTP code mapped to the appropriate message. These are
|
5
5
|
# used so frequently that they are placed directly in Unicorn for easy
|
6
6
|
# access rather than Unicorn::Const itself.
|
7
|
-
HTTP_STATUS_CODES = {
|
8
|
-
100 => 'Continue',
|
9
|
-
101 => 'Switching Protocols',
|
10
|
-
200 => 'OK',
|
11
|
-
201 => 'Created',
|
12
|
-
202 => 'Accepted',
|
13
|
-
203 => 'Non-Authoritative Information',
|
14
|
-
204 => 'No Content',
|
15
|
-
205 => 'Reset Content',
|
16
|
-
206 => 'Partial Content',
|
17
|
-
300 => 'Multiple Choices',
|
18
|
-
301 => 'Moved Permanently',
|
19
|
-
302 => 'Moved Temporarily',
|
20
|
-
303 => 'See Other',
|
21
|
-
304 => 'Not Modified',
|
22
|
-
305 => 'Use Proxy',
|
23
|
-
400 => 'Bad Request',
|
24
|
-
401 => 'Unauthorized',
|
25
|
-
402 => 'Payment Required',
|
26
|
-
403 => 'Forbidden',
|
27
|
-
404 => 'Not Found',
|
28
|
-
405 => 'Method Not Allowed',
|
29
|
-
406 => 'Not Acceptable',
|
30
|
-
407 => 'Proxy Authentication Required',
|
31
|
-
408 => 'Request Time-out',
|
32
|
-
409 => 'Conflict',
|
33
|
-
410 => 'Gone',
|
34
|
-
411 => 'Length Required',
|
35
|
-
412 => 'Precondition Failed',
|
36
|
-
413 => 'Request Entity Too Large',
|
37
|
-
414 => 'Request-URI Too Large',
|
38
|
-
415 => 'Unsupported Media Type',
|
39
|
-
500 => 'Internal Server Error',
|
40
|
-
501 => 'Not Implemented',
|
41
|
-
502 => 'Bad Gateway',
|
42
|
-
503 => 'Service Unavailable',
|
43
|
-
504 => 'Gateway Time-out',
|
7
|
+
HTTP_STATUS_CODES = {
|
8
|
+
100 => 'Continue',
|
9
|
+
101 => 'Switching Protocols',
|
10
|
+
200 => 'OK',
|
11
|
+
201 => 'Created',
|
12
|
+
202 => 'Accepted',
|
13
|
+
203 => 'Non-Authoritative Information',
|
14
|
+
204 => 'No Content',
|
15
|
+
205 => 'Reset Content',
|
16
|
+
206 => 'Partial Content',
|
17
|
+
300 => 'Multiple Choices',
|
18
|
+
301 => 'Moved Permanently',
|
19
|
+
302 => 'Moved Temporarily',
|
20
|
+
303 => 'See Other',
|
21
|
+
304 => 'Not Modified',
|
22
|
+
305 => 'Use Proxy',
|
23
|
+
400 => 'Bad Request',
|
24
|
+
401 => 'Unauthorized',
|
25
|
+
402 => 'Payment Required',
|
26
|
+
403 => 'Forbidden',
|
27
|
+
404 => 'Not Found',
|
28
|
+
405 => 'Method Not Allowed',
|
29
|
+
406 => 'Not Acceptable',
|
30
|
+
407 => 'Proxy Authentication Required',
|
31
|
+
408 => 'Request Time-out',
|
32
|
+
409 => 'Conflict',
|
33
|
+
410 => 'Gone',
|
34
|
+
411 => 'Length Required',
|
35
|
+
412 => 'Precondition Failed',
|
36
|
+
413 => 'Request Entity Too Large',
|
37
|
+
414 => 'Request-URI Too Large',
|
38
|
+
415 => 'Unsupported Media Type',
|
39
|
+
500 => 'Internal Server Error',
|
40
|
+
501 => 'Not Implemented',
|
41
|
+
502 => 'Bad Gateway',
|
42
|
+
503 => 'Service Unavailable',
|
43
|
+
504 => 'Gateway Time-out',
|
44
44
|
505 => 'HTTP Version not supported'
|
45
45
|
}
|
46
46
|
|
@@ -53,12 +53,12 @@ module Unicorn
|
|
53
53
|
|
54
54
|
# This is the part of the path after the SCRIPT_NAME.
|
55
55
|
PATH_INFO="PATH_INFO".freeze
|
56
|
-
|
56
|
+
|
57
57
|
# The original URI requested by the client.
|
58
58
|
REQUEST_URI='REQUEST_URI'.freeze
|
59
59
|
REQUEST_PATH='REQUEST_PATH'.freeze
|
60
|
-
|
61
|
-
UNICORN_VERSION="0.5.
|
60
|
+
|
61
|
+
UNICORN_VERSION="0.5.2".freeze
|
62
62
|
|
63
63
|
UNICORN_TMP_BASE="unicorn".freeze
|
64
64
|
|
@@ -24,12 +24,16 @@ module Unicorn
|
|
24
24
|
# Rack does not set/require a Date: header. We always override the
|
25
25
|
# Connection: and Date: headers no matter what (if anything) our
|
26
26
|
# Rack application sent us.
|
27
|
-
SKIP = { 'connection' => true, 'date' => true }.freeze
|
27
|
+
SKIP = { 'connection' => true, 'date' => true, 'status' => true }.freeze
|
28
28
|
|
29
29
|
# writes the rack_response to socket as an HTTP response
|
30
30
|
def self.write(socket, rack_response)
|
31
31
|
status, headers, body = rack_response
|
32
|
-
|
32
|
+
status = "#{status} #{HTTP_STATUS_CODES[status]}"
|
33
|
+
|
34
|
+
# Date is required by HTTP/1.1 as long as our clock can be trusted.
|
35
|
+
# Some broken clients require a "Status" header so we accomodate them
|
36
|
+
out = [ "Date: #{Time.now.httpdate}", "Status: #{status}" ]
|
33
37
|
|
34
38
|
# Don't bother enforcing duplicate supression, it's a Hash most of
|
35
39
|
# the time anyways so just hope our app knows what it's doing
|
@@ -45,7 +49,7 @@ module Unicorn
|
|
45
49
|
# Rack should enforce Content-Length or chunked transfer encoding,
|
46
50
|
# so don't worry or care about them.
|
47
51
|
socket_write(socket,
|
48
|
-
"HTTP/1.1 #{status}
|
52
|
+
"HTTP/1.1 #{status}\r\n" \
|
49
53
|
"Connection: close\r\n" \
|
50
54
|
"#{out.join("\r\n")}\r\n\r\n")
|
51
55
|
body.each { |chunk| socket_write(socket, chunk) }
|
data/test/exec/test_exec.rb
CHANGED
@@ -370,21 +370,21 @@ end
|
|
370
370
|
tries = DEFAULT_TRIES
|
371
371
|
log = File.readlines(rotate.path)
|
372
372
|
while (tries -= 1) > 0 &&
|
373
|
-
log.grep(/
|
373
|
+
log.grep(/reopening logs\.\.\./).size < 5
|
374
374
|
sleep DEFAULT_RES
|
375
375
|
log = File.readlines(rotate.path)
|
376
376
|
end
|
377
|
-
assert_equal 5, log.grep(/
|
378
|
-
assert_equal 0, log.grep(/done
|
377
|
+
assert_equal 5, log.grep(/reopening logs\.\.\./).size
|
378
|
+
assert_equal 0, log.grep(/done reopening logs/).size
|
379
379
|
|
380
380
|
tries = DEFAULT_TRIES
|
381
381
|
log = File.readlines(COMMON_TMP.path)
|
382
|
-
while (tries -= 1) > 0 && log.grep(/done
|
382
|
+
while (tries -= 1) > 0 && log.grep(/done reopening logs/).size < 5
|
383
383
|
sleep DEFAULT_RES
|
384
384
|
log = File.readlines(COMMON_TMP.path)
|
385
385
|
end
|
386
|
-
assert_equal 5, log.grep(/done
|
387
|
-
assert_equal 0, log.grep(/
|
386
|
+
assert_equal 5, log.grep(/done reopening logs/).size
|
387
|
+
assert_equal 0, log.grep(/reopening logs\.\.\./).size
|
388
388
|
assert_nothing_raised { Process.kill(:QUIT, pid) }
|
389
389
|
status = nil
|
390
390
|
assert_nothing_raised { pid, status = Process.waitpid2(pid) }
|
data/test/unit/test_response.rb
CHANGED
@@ -54,6 +54,27 @@ class ResponseTest < Test::Unit::TestCase
|
|
54
54
|
assert_match(/^X-Whatever: stuff\r\nX-Whatever: bleh\r\n/, out.string)
|
55
55
|
end
|
56
56
|
|
57
|
+
# Even though Rack explicitly forbids "Status" in the header hash,
|
58
|
+
# some broken clients still rely on it
|
59
|
+
def test_status_header_added
|
60
|
+
out = StringIO.new
|
61
|
+
HttpResponse.write(out,[200, {"X-Whatever" => "stuff"}, []])
|
62
|
+
assert out.closed?
|
63
|
+
assert_match(/^Status: 200 OK\r\nX-Whatever: stuff\r\n/, out.string)
|
64
|
+
end
|
65
|
+
|
66
|
+
# we always favor the code returned by the application, since "Status"
|
67
|
+
# in the header hash is not allowed by Rack (but not every app is
|
68
|
+
# fully Rack-compliant).
|
69
|
+
def test_status_header_ignores_app_hash
|
70
|
+
out = StringIO.new
|
71
|
+
header_hash = {"X-Whatever" => "stuff", 'StaTus' => "666" }
|
72
|
+
HttpResponse.write(out,[200, header_hash, []])
|
73
|
+
assert out.closed?
|
74
|
+
assert_match(/^Status: 200 OK\r\nX-Whatever: stuff\r\n/, out.string)
|
75
|
+
assert_equal 1, out.string.split(/\r\n/).grep(/^Status:/i).size
|
76
|
+
end
|
77
|
+
|
57
78
|
def test_body_closed
|
58
79
|
expect_body = %w(1 2 3 4).join("\n")
|
59
80
|
body = StringIO.new(expect_body)
|
data/unicorn.gemspec
CHANGED
@@ -2,11 +2,11 @@
|
|
2
2
|
|
3
3
|
Gem::Specification.new do |s|
|
4
4
|
s.name = %q{unicorn}
|
5
|
-
s.version = "0.5.
|
5
|
+
s.version = "0.5.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-04-
|
9
|
+
s.date = %q{2009-04-16}
|
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"]
|
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.5.
|
4
|
+
version: 0.5.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-04-
|
12
|
+
date: 2009-04-16 00:00:00 -07:00
|
13
13
|
default_executable:
|
14
14
|
dependencies: []
|
15
15
|
|