unicorn 3.2.1 → 3.3.0
Sign up to get free protection for your applications and to get access to all the features.
- data/GIT-VERSION-GEN +1 -1
- data/GNUmakefile +1 -1
- data/Rakefile +5 -2
- data/ext/unicorn_http/extconf.rb +1 -2
- data/ext/unicorn_http/httpdate.c +82 -0
- data/ext/unicorn_http/unicorn_http.rl +29 -4
- data/lib/unicorn/const.rb +2 -2
- data/lib/unicorn/http_request.rb +1 -1
- data/lib/unicorn/http_response.rb +4 -7
- data/lib/unicorn/http_server.rb +5 -7
- data/lib/unicorn/socket_helper.rb +7 -7
- data/test/unit/test_http_parser.rb +6 -6
- data/test/unit/test_http_parser_ng.rb +25 -3
- data/test/unit/test_response.rb +20 -21
- data/unicorn.gemspec +1 -1
- metadata +9 -9
data/GIT-VERSION-GEN
CHANGED
data/GNUmakefile
CHANGED
@@ -45,7 +45,7 @@ T_r_log := $(subst .r,$(log_suffix),$(T_r))
|
|
45
45
|
test_prefix = $(CURDIR)/test/$(RUBY_ENGINE)-$(RUBY_VERSION)
|
46
46
|
|
47
47
|
ext := ext/unicorn_http
|
48
|
-
c_files := $(ext)/unicorn_http.c $(wildcard $(ext)/*.h)
|
48
|
+
c_files := $(ext)/unicorn_http.c $(ext)/httpdate.c $(wildcard $(ext)/*.h)
|
49
49
|
rl_files := $(wildcard $(ext)/*.rl)
|
50
50
|
base_bins := unicorn unicorn_rails
|
51
51
|
bins := $(addprefix bin/, $(base_bins))
|
data/Rakefile
CHANGED
@@ -51,9 +51,12 @@ task :fm_update do
|
|
51
51
|
uri = URI.parse('http://freshmeat.net/projects/unicorn/releases.json')
|
52
52
|
rc = Net::Netrc.locate('unicorn-fm') or abort "~/.netrc not found"
|
53
53
|
api_token = rc.password
|
54
|
-
|
54
|
+
_, subject, body = `git cat-file tag v#{version}`.split(/\n\n/, 3)
|
55
55
|
tmp = Tempfile.new('fm-changelog')
|
56
|
-
tmp.
|
56
|
+
tmp.puts subject
|
57
|
+
tmp.puts
|
58
|
+
tmp.puts body
|
59
|
+
tmp.flush
|
57
60
|
system(ENV["VISUAL"], tmp.path) or abort "#{ENV["VISUAL"]} failed: #$?"
|
58
61
|
changelog = File.read(tmp.path).strip
|
59
62
|
|
data/ext/unicorn_http/extconf.rb
CHANGED
@@ -1,10 +1,9 @@
|
|
1
1
|
# -*- encoding: binary -*-
|
2
2
|
require 'mkmf'
|
3
3
|
|
4
|
-
dir_config("unicorn_http")
|
5
|
-
|
6
4
|
have_macro("SIZEOF_OFF_T", "ruby.h") or check_sizeof("off_t", "sys/types.h")
|
7
5
|
have_macro("SIZEOF_LONG", "ruby.h") or check_sizeof("long", "sys/types.h")
|
8
6
|
have_func("rb_str_set_len", "ruby.h")
|
7
|
+
have_func("gmtime_r", "time.h")
|
9
8
|
|
10
9
|
create_makefile("unicorn_http")
|
@@ -0,0 +1,82 @@
|
|
1
|
+
#include <ruby.h>
|
2
|
+
#include <time.h>
|
3
|
+
#include <stdio.h>
|
4
|
+
|
5
|
+
static const size_t buf_capa = sizeof("Thu, 01 Jan 1970 00:00:00 GMT");
|
6
|
+
static VALUE buf;
|
7
|
+
static char *buf_ptr;
|
8
|
+
static const char *const week[] = {
|
9
|
+
"Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"
|
10
|
+
};
|
11
|
+
static const char *const months[] = {
|
12
|
+
"Jan", "Feb", "Mar", "Apr", "May", "Jun",
|
13
|
+
"Jul", "Aug", "Sep", "Oct", "Nov", "Dec"
|
14
|
+
};
|
15
|
+
|
16
|
+
/* for people on wonky systems only */
|
17
|
+
#ifndef HAVE_GMTIME_R
|
18
|
+
static struct tm * my_gmtime_r(time_t *now, struct tm *tm)
|
19
|
+
{
|
20
|
+
struct tm *global = gmtime(now);
|
21
|
+
if (global)
|
22
|
+
*tm = *global;
|
23
|
+
return tm;
|
24
|
+
}
|
25
|
+
# define gmtime_r my_gmtime_r
|
26
|
+
#endif
|
27
|
+
|
28
|
+
|
29
|
+
/*
|
30
|
+
* Returns a string which represents the time as rfc1123-date of HTTP-date
|
31
|
+
* defined by RFC 2616:
|
32
|
+
*
|
33
|
+
* day-of-week, DD month-name CCYY hh:mm:ss GMT
|
34
|
+
*
|
35
|
+
* Note that the result is always GMT.
|
36
|
+
*
|
37
|
+
* This method is identical to Time#httpdate in the Ruby standard library,
|
38
|
+
* except it is implemented in C for performance. We always saw
|
39
|
+
* Time#httpdate at or near the top of the profiler output so we
|
40
|
+
* decided to rewrite this in C.
|
41
|
+
*
|
42
|
+
* Caveats: it relies on a Ruby implementation with the global VM lock,
|
43
|
+
* a thread-safe version will be provided when a Unix-only, GVL-free Ruby
|
44
|
+
* implementation becomes viable.
|
45
|
+
*/
|
46
|
+
static VALUE httpdate(VALUE self)
|
47
|
+
{
|
48
|
+
static time_t last;
|
49
|
+
time_t now = time(NULL); /* not a syscall on modern 64-bit systems */
|
50
|
+
struct tm tm;
|
51
|
+
|
52
|
+
if (last == now)
|
53
|
+
return buf;
|
54
|
+
last = now;
|
55
|
+
gmtime_r(&now, &tm);
|
56
|
+
|
57
|
+
/* we can make this thread-safe later if our Ruby loses the GVL */
|
58
|
+
snprintf(buf_ptr, buf_capa,
|
59
|
+
"%s, %02d %s %4d %02d:%02d:%02d GMT",
|
60
|
+
week[tm.tm_wday],
|
61
|
+
tm.tm_mday,
|
62
|
+
months[tm.tm_mon],
|
63
|
+
tm.tm_year + 1900,
|
64
|
+
tm.tm_hour,
|
65
|
+
tm.tm_min,
|
66
|
+
tm.tm_sec);
|
67
|
+
|
68
|
+
return buf;
|
69
|
+
}
|
70
|
+
|
71
|
+
void init_unicorn_httpdate(void)
|
72
|
+
{
|
73
|
+
VALUE mod = rb_const_get(rb_cObject, rb_intern("Unicorn"));
|
74
|
+
mod = rb_define_module_under(mod, "HttpResponse");
|
75
|
+
|
76
|
+
buf = rb_str_new(0, buf_capa - 1);
|
77
|
+
rb_global_variable(&buf);
|
78
|
+
buf_ptr = RSTRING_PTR(buf);
|
79
|
+
httpdate(Qnil);
|
80
|
+
|
81
|
+
rb_define_method(mod, "httpdate", httpdate, 0);
|
82
|
+
}
|
@@ -12,6 +12,8 @@
|
|
12
12
|
#include "global_variables.h"
|
13
13
|
#include "c_util.h"
|
14
14
|
|
15
|
+
void init_unicorn_httpdate(void);
|
16
|
+
|
15
17
|
#define UH_FL_CHUNKED 0x1
|
16
18
|
#define UH_FL_HASBODY 0x2
|
17
19
|
#define UH_FL_INBODY 0x4
|
@@ -570,18 +572,39 @@ static VALUE HttpParser_init(VALUE self)
|
|
570
572
|
|
571
573
|
/**
|
572
574
|
* call-seq:
|
573
|
-
* parser.
|
575
|
+
* parser.clear => parser
|
574
576
|
*
|
575
577
|
* Resets the parser to it's initial state so that you can reuse it
|
576
578
|
* rather than making new ones.
|
577
579
|
*/
|
578
|
-
static VALUE
|
580
|
+
static VALUE HttpParser_clear(VALUE self)
|
579
581
|
{
|
580
582
|
struct http_parser *hp = data_get(self);
|
581
583
|
|
582
584
|
http_parser_init(hp);
|
583
585
|
rb_funcall(hp->env, id_clear, 0);
|
584
586
|
|
587
|
+
return self;
|
588
|
+
}
|
589
|
+
|
590
|
+
/**
|
591
|
+
* call-seq:
|
592
|
+
* parser.reset => nil
|
593
|
+
*
|
594
|
+
* Resets the parser to it's initial state so that you can reuse it
|
595
|
+
* rather than making new ones.
|
596
|
+
*
|
597
|
+
* This method is deprecated and to be removed in Unicorn 4.x
|
598
|
+
*/
|
599
|
+
static VALUE HttpParser_reset(VALUE self)
|
600
|
+
{
|
601
|
+
static int warned;
|
602
|
+
|
603
|
+
if (!warned) {
|
604
|
+
rb_warn("Unicorn::HttpParser#reset is deprecated; "
|
605
|
+
"use Unicorn::HttpParser#clear instead");
|
606
|
+
}
|
607
|
+
HttpParser_clear(self);
|
585
608
|
return Qnil;
|
586
609
|
}
|
587
610
|
|
@@ -852,8 +875,9 @@ void Init_unicorn_http(void)
|
|
852
875
|
|
853
876
|
init_globals();
|
854
877
|
rb_define_alloc_func(cHttpParser, HttpParser_alloc);
|
855
|
-
rb_define_method(cHttpParser, "initialize", HttpParser_init,0);
|
856
|
-
rb_define_method(cHttpParser, "
|
878
|
+
rb_define_method(cHttpParser, "initialize", HttpParser_init, 0);
|
879
|
+
rb_define_method(cHttpParser, "clear", HttpParser_clear, 0);
|
880
|
+
rb_define_method(cHttpParser, "reset", HttpParser_reset, 0);
|
857
881
|
rb_define_method(cHttpParser, "parse", HttpParser_parse, 0);
|
858
882
|
rb_define_method(cHttpParser, "headers", HttpParser_headers, 2);
|
859
883
|
rb_define_method(cHttpParser, "trailers", HttpParser_headers, 2);
|
@@ -897,5 +921,6 @@ void Init_unicorn_http(void)
|
|
897
921
|
SET_GLOBAL(g_content_length, "CONTENT_LENGTH");
|
898
922
|
SET_GLOBAL(g_http_connection, "CONNECTION");
|
899
923
|
id_clear = rb_intern("clear");
|
924
|
+
init_unicorn_httpdate();
|
900
925
|
}
|
901
926
|
#undef SET_GLOBAL
|
data/lib/unicorn/const.rb
CHANGED
@@ -7,8 +7,8 @@
|
|
7
7
|
# improve things much compared to constants.
|
8
8
|
module Unicorn::Const
|
9
9
|
|
10
|
-
# The current version of Unicorn, currently 3.
|
11
|
-
UNICORN_VERSION = "3.
|
10
|
+
# The current version of Unicorn, currently 3.3.0
|
11
|
+
UNICORN_VERSION = "3.3.0"
|
12
12
|
|
13
13
|
# default TCP listen host address (0.0.0.0, all interfaces)
|
14
14
|
DEFAULT_HOST = "0.0.0.0"
|
data/lib/unicorn/http_request.rb
CHANGED
@@ -1,11 +1,9 @@
|
|
1
1
|
# -*- encoding: binary -*-
|
2
|
-
require 'time'
|
3
|
-
|
4
2
|
# Writes a Rack response to your client using the HTTP/1.1 specification.
|
5
3
|
# You use it by simply doing:
|
6
4
|
#
|
7
5
|
# status, headers, body = rack_app.call(env)
|
8
|
-
# http_response_write(socket,
|
6
|
+
# http_response_write(socket, status, headers, body)
|
9
7
|
#
|
10
8
|
# Most header correctness (including Content-Length and Content-Type)
|
11
9
|
# is the job of Rack, with the exception of the "Date" and "Status" header.
|
@@ -19,20 +17,19 @@ module Unicorn::HttpResponse
|
|
19
17
|
CRLF = "\r\n"
|
20
18
|
|
21
19
|
# writes the rack_response to socket as an HTTP response
|
22
|
-
def http_response_write(socket,
|
23
|
-
status, headers, body = rack_response
|
20
|
+
def http_response_write(socket, status, headers, body)
|
24
21
|
status = CODES[status.to_i] || status
|
25
22
|
|
26
23
|
if headers
|
27
24
|
buf = "HTTP/1.1 #{status}\r\n" \
|
28
|
-
"Date: #{
|
25
|
+
"Date: #{httpdate}\r\n" \
|
29
26
|
"Status: #{status}\r\n" \
|
30
27
|
"Connection: close\r\n"
|
31
28
|
headers.each do |key, value|
|
32
29
|
next if %r{\A(?:Date\z|Status\z|Connection\z)}i =~ key
|
33
30
|
if value =~ /\n/
|
34
31
|
# avoiding blank, key-only cookies with /\n+/
|
35
|
-
buf << value.split(/\n+/).map! { |v| "#{key}: #{v}\r\n" }.join
|
32
|
+
buf << value.split(/\n+/).map! { |v| "#{key}: #{v}\r\n" }.join
|
36
33
|
else
|
37
34
|
buf << "#{key}: #{value}\r\n"
|
38
35
|
end
|
data/lib/unicorn/http_server.rb
CHANGED
@@ -524,22 +524,20 @@ class Unicorn::HttpServer
|
|
524
524
|
client.kgio_trywrite(msg)
|
525
525
|
client.close
|
526
526
|
rescue
|
527
|
-
nil
|
528
527
|
end
|
529
528
|
|
530
529
|
# once a client is accepted, it is processed in its entirety here
|
531
530
|
# in 3 easy steps: read request, call app, write app response
|
532
531
|
def process_client(client)
|
533
|
-
|
532
|
+
status, headers, body = @app.call(env = @request.read(client))
|
534
533
|
|
535
|
-
if 100 ==
|
534
|
+
if 100 == status.to_i
|
536
535
|
client.write(Unicorn::Const::EXPECT_100_RESPONSE)
|
537
536
|
env.delete(Unicorn::Const::HTTP_EXPECT)
|
538
|
-
|
537
|
+
status, headers, body = @app.call(env)
|
539
538
|
end
|
540
|
-
|
541
|
-
|
542
|
-
http_response_write(client, r)
|
539
|
+
@request.headers? or headers = nil
|
540
|
+
http_response_write(client, status, headers, body)
|
543
541
|
rescue => e
|
544
542
|
handle_error(client, e)
|
545
543
|
end
|
@@ -20,6 +20,9 @@ module Unicorn
|
|
20
20
|
# FreeBSD, we need to override this to 'dataready' when we
|
21
21
|
# eventually get HTTPS support
|
22
22
|
:accept_filter => 'httpready',
|
23
|
+
|
24
|
+
# same default value as Mongrel
|
25
|
+
:backlog => 1024,
|
23
26
|
}
|
24
27
|
#:startdoc:
|
25
28
|
|
@@ -41,7 +44,6 @@ module Unicorn
|
|
41
44
|
end
|
42
45
|
|
43
46
|
def set_tcp_sockopt(sock, opt)
|
44
|
-
|
45
47
|
# highly portable, but off by default because we don't do keepalive
|
46
48
|
if defined?(TCP_NODELAY) && ! (val = opt[:tcp_nodelay]).nil?
|
47
49
|
sock.setsockopt(IPPROTO_TCP, TCP_NODELAY, val ? 1 : 0)
|
@@ -61,14 +63,12 @@ module Unicorn
|
|
61
63
|
if defined?(TCP_DEFER_ACCEPT)
|
62
64
|
# this differs from nginx, since nginx doesn't allow us to
|
63
65
|
# configure the the timeout...
|
64
|
-
|
65
|
-
seconds = tmp[:tcp_defer_accept]
|
66
|
+
seconds = opt[:tcp_defer_accept]
|
66
67
|
seconds = DEFAULTS[:tcp_defer_accept] if seconds == true
|
67
68
|
seconds = 0 unless seconds # nil/false means disable this
|
68
69
|
sock.setsockopt(SOL_TCP, TCP_DEFER_ACCEPT, seconds)
|
69
70
|
elsif respond_to?(:accf_arg)
|
70
|
-
|
71
|
-
if name = tmp[:accept_filter]
|
71
|
+
if name = opt[:accept_filter]
|
72
72
|
begin
|
73
73
|
sock.setsockopt(SOL_SOCKET, SO_ACCEPTFILTER, accf_arg(name))
|
74
74
|
rescue => e
|
@@ -80,7 +80,7 @@ module Unicorn
|
|
80
80
|
end
|
81
81
|
|
82
82
|
def set_server_sockopt(sock, opt)
|
83
|
-
opt
|
83
|
+
opt = DEFAULTS.merge(opt || {})
|
84
84
|
|
85
85
|
TCPSocket === sock and set_tcp_sockopt(sock, opt)
|
86
86
|
|
@@ -90,7 +90,7 @@ module Unicorn
|
|
90
90
|
sock.setsockopt(SOL_SOCKET, SO_SNDBUF, opt[:sndbuf]) if opt[:sndbuf]
|
91
91
|
log_buffer_sizes(sock, " after: ")
|
92
92
|
end
|
93
|
-
sock.listen(opt[:backlog]
|
93
|
+
sock.listen(opt[:backlog])
|
94
94
|
rescue => e
|
95
95
|
logger.error "error setting socket options: #{e.inspect}"
|
96
96
|
logger.error e.backtrace.join("\n")
|
@@ -28,7 +28,7 @@ class HttpParserTest < Test::Unit::TestCase
|
|
28
28
|
assert_equal '', req['QUERY_STRING']
|
29
29
|
|
30
30
|
assert parser.keepalive?
|
31
|
-
parser.
|
31
|
+
parser.clear
|
32
32
|
req.clear
|
33
33
|
|
34
34
|
http = "G"
|
@@ -326,7 +326,7 @@ class HttpParserTest < Test::Unit::TestCase
|
|
326
326
|
assert_raises(HttpParserError) { parser.headers(req, bad_http) }
|
327
327
|
|
328
328
|
# make sure we can recover
|
329
|
-
parser.
|
329
|
+
parser.clear
|
330
330
|
req.clear
|
331
331
|
assert_equal req, parser.headers(req, "GET / HTTP/1.0\r\n\r\n")
|
332
332
|
assert ! parser.keepalive?
|
@@ -569,7 +569,7 @@ class HttpParserTest < Test::Unit::TestCase
|
|
569
569
|
get = "GET /#{rand_data(10,120)} HTTP/1.1\r\nX-#{rand_data(1024, 1024+(c*1024))}: Test\r\n\r\n"
|
570
570
|
assert_raises Unicorn::HttpParserError do
|
571
571
|
parser.headers({}, get)
|
572
|
-
parser.
|
572
|
+
parser.clear
|
573
573
|
end
|
574
574
|
end
|
575
575
|
|
@@ -578,7 +578,7 @@ class HttpParserTest < Test::Unit::TestCase
|
|
578
578
|
get = "GET /#{rand_data(10,120)} HTTP/1.1\r\nX-Test: #{rand_data(1024, 1024+(c*1024), false)}\r\n\r\n"
|
579
579
|
assert_raises Unicorn::HttpParserError do
|
580
580
|
parser.headers({}, get)
|
581
|
-
parser.
|
581
|
+
parser.clear
|
582
582
|
end
|
583
583
|
end
|
584
584
|
|
@@ -587,7 +587,7 @@ class HttpParserTest < Test::Unit::TestCase
|
|
587
587
|
get << "X-Test: test\r\n" * (80 * 1024)
|
588
588
|
assert_raises Unicorn::HttpParserError do
|
589
589
|
parser.headers({}, get)
|
590
|
-
parser.
|
590
|
+
parser.clear
|
591
591
|
end
|
592
592
|
|
593
593
|
# finally just that random garbage gets blocked all the time
|
@@ -595,7 +595,7 @@ class HttpParserTest < Test::Unit::TestCase
|
|
595
595
|
get = "GET #{rand_data(1024, 1024+(c*1024), false)} #{rand_data(1024, 1024+(c*1024), false)}\r\n\r\n"
|
596
596
|
assert_raises Unicorn::HttpParserError do
|
597
597
|
parser.headers({}, get)
|
598
|
-
parser.
|
598
|
+
parser.clear
|
599
599
|
end
|
600
600
|
end
|
601
601
|
|
@@ -91,7 +91,7 @@ class HttpParserNgTest < Test::Unit::TestCase
|
|
91
91
|
@parser.parse
|
92
92
|
end
|
93
93
|
assert @parser.keepalive?
|
94
|
-
@parser.
|
94
|
+
@parser.clear
|
95
95
|
assert ! @parser.keepalive?
|
96
96
|
assert ! @parser.next?
|
97
97
|
end
|
@@ -491,7 +491,7 @@ class HttpParserNgTest < Test::Unit::TestCase
|
|
491
491
|
}.each do |uri,expect|
|
492
492
|
assert_equal req, @parser.headers(req.clear, str % [ uri ])
|
493
493
|
req = req.dup
|
494
|
-
@parser.
|
494
|
+
@parser.clear
|
495
495
|
assert_equal uri, req["REQUEST_URI"], "REQUEST_URI mismatch"
|
496
496
|
assert_equal expect[qs], req[qs], "#{qs} mismatch"
|
497
497
|
assert_equal expect[pi], req[pi], "#{pi} mismatch"
|
@@ -516,7 +516,7 @@ class HttpParserNgTest < Test::Unit::TestCase
|
|
516
516
|
}.each do |uri,expect|
|
517
517
|
assert_equal req, @parser.headers(req.clear, str % [ uri ])
|
518
518
|
req = req.dup
|
519
|
-
@parser.
|
519
|
+
@parser.clear
|
520
520
|
assert_equal uri, req["REQUEST_URI"], "REQUEST_URI mismatch"
|
521
521
|
assert_equal "example.com", req["HTTP_HOST"], "Host: mismatch"
|
522
522
|
assert_equal expect[qs], req[qs], "#{qs} mismatch"
|
@@ -608,4 +608,26 @@ class HttpParserNgTest < Test::Unit::TestCase
|
|
608
608
|
assert_equal expect, env2
|
609
609
|
assert_equal "", @parser.buf
|
610
610
|
end
|
611
|
+
|
612
|
+
def test_keepalive_requests_disabled
|
613
|
+
req = "GET / HTTP/1.1\r\nHost: example.com\r\n\r\n".freeze
|
614
|
+
expect = {
|
615
|
+
"SERVER_NAME" => "example.com",
|
616
|
+
"HTTP_HOST" => "example.com",
|
617
|
+
"rack.url_scheme" => "http",
|
618
|
+
"REQUEST_PATH" => "/",
|
619
|
+
"SERVER_PROTOCOL" => "HTTP/1.1",
|
620
|
+
"PATH_INFO" => "/",
|
621
|
+
"HTTP_VERSION" => "HTTP/1.1",
|
622
|
+
"REQUEST_URI" => "/",
|
623
|
+
"SERVER_PORT" => "80",
|
624
|
+
"REQUEST_METHOD" => "GET",
|
625
|
+
"QUERY_STRING" => ""
|
626
|
+
}.freeze
|
627
|
+
HttpParser.keepalive_requests = 0
|
628
|
+
@parser = HttpParser.new
|
629
|
+
@parser.buf << req
|
630
|
+
assert_equal expect, @parser.parse
|
631
|
+
assert ! @parser.next?
|
632
|
+
end
|
611
633
|
end
|
data/test/unit/test_response.rb
CHANGED
@@ -7,15 +7,26 @@
|
|
7
7
|
# for more information.
|
8
8
|
|
9
9
|
require 'test/test_helper'
|
10
|
+
require 'time'
|
10
11
|
|
11
12
|
include Unicorn
|
12
13
|
|
13
14
|
class ResponseTest < Test::Unit::TestCase
|
14
15
|
include Unicorn::HttpResponse
|
15
16
|
|
17
|
+
def test_httpdate
|
18
|
+
before = Time.now.to_i
|
19
|
+
str = httpdate
|
20
|
+
assert_kind_of(String, str)
|
21
|
+
middle = Time.parse(str).to_i
|
22
|
+
after = Time.now.to_i
|
23
|
+
assert before <= middle
|
24
|
+
assert middle <= after
|
25
|
+
end
|
26
|
+
|
16
27
|
def test_response_headers
|
17
28
|
out = StringIO.new
|
18
|
-
http_response_write(out,
|
29
|
+
http_response_write(out, 200, {"X-Whatever" => "stuff"}, ["cool"])
|
19
30
|
assert out.closed?
|
20
31
|
|
21
32
|
assert out.length > 0, "output didn't have data"
|
@@ -23,27 +34,15 @@ class ResponseTest < Test::Unit::TestCase
|
|
23
34
|
|
24
35
|
def test_response_string_status
|
25
36
|
out = StringIO.new
|
26
|
-
http_response_write(out,
|
37
|
+
http_response_write(out,'200', {}, [])
|
27
38
|
assert out.closed?
|
28
39
|
assert out.length > 0, "output didn't have data"
|
29
40
|
assert_equal 1, out.string.split(/\r\n/).grep(/^Status: 200 OK/).size
|
30
41
|
end
|
31
42
|
|
32
|
-
def test_response_OFS_set
|
33
|
-
old_ofs = $,
|
34
|
-
$, = "\f\v"
|
35
|
-
out = StringIO.new
|
36
|
-
http_response_write(out,[200, {"X-k" => "cd","X-y" => "z"}, ["cool"]])
|
37
|
-
assert out.closed?
|
38
|
-
resp = out.string
|
39
|
-
assert ! resp.include?("\f\v"), "output didn't use $, ($OFS)"
|
40
|
-
ensure
|
41
|
-
$, = old_ofs
|
42
|
-
end
|
43
|
-
|
44
43
|
def test_response_200
|
45
44
|
io = StringIO.new
|
46
|
-
http_response_write(io,
|
45
|
+
http_response_write(io, 200, {}, [])
|
47
46
|
assert io.closed?
|
48
47
|
assert io.length > 0, "output didn't have data"
|
49
48
|
end
|
@@ -51,7 +50,7 @@ class ResponseTest < Test::Unit::TestCase
|
|
51
50
|
def test_response_with_default_reason
|
52
51
|
code = 400
|
53
52
|
io = StringIO.new
|
54
|
-
http_response_write(io,
|
53
|
+
http_response_write(io, code, {}, [])
|
55
54
|
assert io.closed?
|
56
55
|
lines = io.string.split(/\r\n/)
|
57
56
|
assert_match(/.* Bad Request$/, lines.first,
|
@@ -60,7 +59,7 @@ class ResponseTest < Test::Unit::TestCase
|
|
60
59
|
|
61
60
|
def test_rack_multivalue_headers
|
62
61
|
out = StringIO.new
|
63
|
-
http_response_write(out,
|
62
|
+
http_response_write(out,200, {"X-Whatever" => "stuff\nbleh"}, [])
|
64
63
|
assert out.closed?
|
65
64
|
assert_match(/^X-Whatever: stuff\r\nX-Whatever: bleh\r\n/, out.string)
|
66
65
|
end
|
@@ -69,7 +68,7 @@ class ResponseTest < Test::Unit::TestCase
|
|
69
68
|
# some broken clients still rely on it
|
70
69
|
def test_status_header_added
|
71
70
|
out = StringIO.new
|
72
|
-
http_response_write(out,
|
71
|
+
http_response_write(out,200, {"X-Whatever" => "stuff"}, [])
|
73
72
|
assert out.closed?
|
74
73
|
assert_equal 1, out.string.split(/\r\n/).grep(/^Status: 200 OK/i).size
|
75
74
|
end
|
@@ -80,7 +79,7 @@ class ResponseTest < Test::Unit::TestCase
|
|
80
79
|
def test_status_header_ignores_app_hash
|
81
80
|
out = StringIO.new
|
82
81
|
header_hash = {"X-Whatever" => "stuff", 'StaTus' => "666" }
|
83
|
-
http_response_write(out,
|
82
|
+
http_response_write(out,200, header_hash, [])
|
84
83
|
assert out.closed?
|
85
84
|
assert_equal 1, out.string.split(/\r\n/).grep(/^Status: 200 OK/i).size
|
86
85
|
assert_equal 1, out.string.split(/\r\n/).grep(/^Status:/i).size
|
@@ -91,7 +90,7 @@ class ResponseTest < Test::Unit::TestCase
|
|
91
90
|
body = StringIO.new(expect_body)
|
92
91
|
body.rewind
|
93
92
|
out = StringIO.new
|
94
|
-
http_response_write(out,
|
93
|
+
http_response_write(out,200, {}, body)
|
95
94
|
assert out.closed?
|
96
95
|
assert body.closed?
|
97
96
|
assert_match(expect_body, out.string.split(/\r\n/).last)
|
@@ -99,7 +98,7 @@ class ResponseTest < Test::Unit::TestCase
|
|
99
98
|
|
100
99
|
def test_unknown_status_pass_through
|
101
100
|
out = StringIO.new
|
102
|
-
http_response_write(out,
|
101
|
+
http_response_write(out,"666 I AM THE BEAST", {}, [] )
|
103
102
|
assert out.closed?
|
104
103
|
headers = out.string.split(/\r\n\r\n/).first.split(/\r\n/)
|
105
104
|
assert %r{\AHTTP/\d\.\d 666 I AM THE BEAST\z}.match(headers[0])
|
data/unicorn.gemspec
CHANGED
@@ -38,7 +38,7 @@ Gem::Specification.new do |s|
|
|
38
38
|
s.add_dependency(%q<kgio>, '~> 2.1')
|
39
39
|
|
40
40
|
s.add_development_dependency('isolate', '~> 3.0.0')
|
41
|
-
s.add_development_dependency('wrongdoc', '~> 1.
|
41
|
+
s.add_development_dependency('wrongdoc', '~> 1.3')
|
42
42
|
|
43
43
|
# s.licenses = %w(GPLv2 Ruby) # licenses= method is not in older RubyGems
|
44
44
|
end
|
metadata
CHANGED
@@ -1,13 +1,13 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: unicorn
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
hash:
|
4
|
+
hash: 11
|
5
5
|
prerelease: false
|
6
6
|
segments:
|
7
7
|
- 3
|
8
|
-
-
|
9
|
-
-
|
10
|
-
version: 3.
|
8
|
+
- 3
|
9
|
+
- 0
|
10
|
+
version: 3.3.0
|
11
11
|
platform: ruby
|
12
12
|
authors:
|
13
13
|
- Unicorn hackers
|
@@ -15,7 +15,7 @@ autorequire:
|
|
15
15
|
bindir: bin
|
16
16
|
cert_chain: []
|
17
17
|
|
18
|
-
date:
|
18
|
+
date: 2011-01-05 00:00:00 +00:00
|
19
19
|
default_executable:
|
20
20
|
dependencies:
|
21
21
|
- !ruby/object:Gem::Dependency
|
@@ -71,12 +71,11 @@ dependencies:
|
|
71
71
|
requirements:
|
72
72
|
- - ~>
|
73
73
|
- !ruby/object:Gem::Version
|
74
|
-
hash:
|
74
|
+
hash: 9
|
75
75
|
segments:
|
76
76
|
- 1
|
77
|
-
-
|
78
|
-
|
79
|
-
version: 1.0.1
|
77
|
+
- 3
|
78
|
+
version: "1.3"
|
80
79
|
type: :development
|
81
80
|
version_requirements: *id004
|
82
81
|
description: |-
|
@@ -177,6 +176,7 @@ files:
|
|
177
176
|
- ext/unicorn_http/ext_help.h
|
178
177
|
- ext/unicorn_http/extconf.rb
|
179
178
|
- ext/unicorn_http/global_variables.h
|
179
|
+
- ext/unicorn_http/httpdate.c
|
180
180
|
- ext/unicorn_http/unicorn_http.c
|
181
181
|
- ext/unicorn_http/unicorn_http.rl
|
182
182
|
- ext/unicorn_http/unicorn_http_common.rl
|