unicorn 3.6.2 → 3.7.0
Sign up to get free protection for your applications and to get access to all the features.
- data/.document +1 -0
- data/Documentation/GNUmakefile +1 -1
- data/GIT-VERSION-GEN +1 -1
- data/GNUmakefile +2 -2
- data/HACKING +11 -0
- data/Links +53 -0
- data/PHILOSOPHY +49 -49
- data/Sandbox +13 -4
- data/examples/nginx.conf +8 -0
- data/ext/unicorn_http/ext_help.h +16 -0
- data/ext/unicorn_http/extconf.rb +1 -0
- data/ext/unicorn_http/global_variables.h +9 -3
- data/ext/unicorn_http/unicorn_http.rl +83 -22
- data/lib/unicorn/configurator.rb +17 -1
- data/lib/unicorn/const.rb +5 -3
- data/lib/unicorn/http_request.rb +1 -3
- data/lib/unicorn/http_server.rb +4 -3
- data/lib/unicorn/socket_helper.rb +19 -3
- data/script/isolate_for_tests +1 -0
- data/t/t0002-parser-error.sh +64 -1
- data/t/t0019-max_header_len.sh +49 -0
- data/test/unit/test_http_parser.rb +56 -0
- data/test/unit/test_socket_helper.rb +8 -0
- data/unicorn.gemspec +1 -1
- metadata +11 -9
data/.document
CHANGED
data/Documentation/GNUmakefile
CHANGED
data/GIT-VERSION-GEN
CHANGED
data/GNUmakefile
CHANGED
@@ -164,7 +164,7 @@ pkg_extra := GIT-VERSION-FILE ChangeLog LATEST NEWS \
|
|
164
164
|
ChangeLog: GIT-VERSION-FILE .wrongdoc.yml
|
165
165
|
wrongdoc prepare
|
166
166
|
|
167
|
-
.manifest: ChangeLog $(ext)/unicorn_http.c
|
167
|
+
.manifest: ChangeLog $(ext)/unicorn_http.c man
|
168
168
|
(git ls-files && for i in $@ $(pkg_extra); do echo $$i; done) | \
|
169
169
|
LC_ALL=C sort > $@+
|
170
170
|
cmp $@+ $@ || mv $@+ $@
|
@@ -176,7 +176,7 @@ doc: .document $(ext)/unicorn_http.c man html .wrongdoc.yml
|
|
176
176
|
$(RM) -r doc
|
177
177
|
wrongdoc all
|
178
178
|
install -m644 COPYING doc/COPYING
|
179
|
-
install -m644 $(shell grep '^[A-Z]' .document) doc/
|
179
|
+
install -m644 $(shell LC_ALL=C grep '^[A-Z]' .document) doc/
|
180
180
|
install -m644 $(man1_paths) doc/
|
181
181
|
tar cf - $$(git ls-files examples/) | (cd doc && tar xf -)
|
182
182
|
$(RM) $(man1_rdoc)
|
data/HACKING
CHANGED
@@ -107,6 +107,17 @@ git itself. See the Documentation/SubmittingPatches document
|
|
107
107
|
distributed with git on on patch submission guidelines to follow. Just
|
108
108
|
don't email the git mailing list or maintainer with Unicorn patches :)
|
109
109
|
|
110
|
+
== Building a Gem
|
111
|
+
|
112
|
+
In order to build the gem, you must install the following components:
|
113
|
+
|
114
|
+
* wrongdoc
|
115
|
+
* pandoc
|
116
|
+
|
117
|
+
You can build the Unicorn gem with the following command:
|
118
|
+
|
119
|
+
gmake gem
|
120
|
+
|
110
121
|
== Running Development Versions
|
111
122
|
|
112
123
|
It is easy to install the contents of your git working directory:
|
data/Links
ADDED
@@ -0,0 +1,53 @@
|
|
1
|
+
= Related Projects
|
2
|
+
|
3
|
+
If you're interested in \Unicorn, you may be interested in some of the projects
|
4
|
+
listed below. If you have any links to add/change/remove, please tell us at
|
5
|
+
mailto:mongrel-unicorn@rubyforge.org!
|
6
|
+
|
7
|
+
== Disclaimer
|
8
|
+
|
9
|
+
The \Unicorn project is not responsible for the content in these links.
|
10
|
+
Furthermore, the \Unicorn project has never, does not and will never endorse:
|
11
|
+
|
12
|
+
* any for-profit entities or services
|
13
|
+
* any non-{Free Software}[http://www.gnu.org/philosophy/free-sw.html]
|
14
|
+
|
15
|
+
The existence of these links does not imply endorsement of any entities
|
16
|
+
or services behind them.
|
17
|
+
|
18
|
+
=== For use with \Unicorn
|
19
|
+
|
20
|
+
* {Bluepill}[https://github.com/arya/bluepill] -
|
21
|
+
a simple process monitoring tool written in Ruby
|
22
|
+
|
23
|
+
* {golden_brindle}[https://github.com/simonoff/golden_brindle] - tool to
|
24
|
+
manage multiple \Unicorn instances/applications on a single server
|
25
|
+
|
26
|
+
* {raindrops}[http://raindrops.bogomips.org/] - real-time stats for
|
27
|
+
preforking Rack servers
|
28
|
+
|
29
|
+
=== \Unicorn is written to work with
|
30
|
+
|
31
|
+
* {Rack}[http://rack.rubyforge.org/] - a minimal interface between webservers
|
32
|
+
supporting Ruby and Ruby frameworks
|
33
|
+
|
34
|
+
* {Ruby}[http://ruby-lang.org/] - the programming language of Rack and \Unicorn
|
35
|
+
|
36
|
+
* {nginx}[http://nginx.org/] - the reverse proxy for use with \Unicorn
|
37
|
+
|
38
|
+
* {kgio}[http://bogomips.org/kgio/] - the I/O library written for \Unicorn
|
39
|
+
|
40
|
+
=== Derivatives
|
41
|
+
|
42
|
+
* {Green Unicorn}[http://gunicorn.org/] - a Python version of \Unicorn
|
43
|
+
|
44
|
+
* {Rainbows!}[http://rainbows.rubyforge.org/] - \Unicorn for sleepy
|
45
|
+
apps and slow clients.
|
46
|
+
|
47
|
+
=== Prior Work
|
48
|
+
|
49
|
+
* {Mongrel}[http://mongrel.rubyforge.org/] - the awesome webserver \Unicorn is
|
50
|
+
based on
|
51
|
+
|
52
|
+
* {david}[http://bogomips.org/david.git] - a tool to explain why you need
|
53
|
+
nginx in front of \Unicorn
|
data/PHILOSOPHY
CHANGED
@@ -1,17 +1,17 @@
|
|
1
|
-
= The Philosophy Behind
|
1
|
+
= The Philosophy Behind unicorn
|
2
2
|
|
3
|
-
Being a server that only runs on Unix-like platforms,
|
3
|
+
Being a server that only runs on Unix-like platforms, unicorn is
|
4
4
|
strongly tied to the Unix philosophy of doing one thing and (hopefully)
|
5
|
-
doing it well. Despite using HTTP,
|
5
|
+
doing it well. Despite using HTTP, unicorn is strictly a _backend_
|
6
6
|
application server for running Rack-based Ruby applications.
|
7
7
|
|
8
8
|
== Avoid Complexity
|
9
9
|
|
10
|
-
Instead of attempting to be efficient at serving slow clients,
|
10
|
+
Instead of attempting to be efficient at serving slow clients, unicorn
|
11
11
|
relies on a buffering reverse proxy to efficiently deal with slow
|
12
12
|
clients.
|
13
13
|
|
14
|
-
|
14
|
+
unicorn uses an old-fashioned preforking worker model with blocking I/O.
|
15
15
|
Our processing model is the antithesis of more modern (and theoretically
|
16
16
|
more efficient) server processing models using threads or non-blocking
|
17
17
|
I/O with events.
|
@@ -19,14 +19,14 @@ I/O with events.
|
|
19
19
|
=== Threads and Events Are Hard
|
20
20
|
|
21
21
|
...to many developers. Reasons for this is beyond the scope of this
|
22
|
-
document.
|
22
|
+
document. unicorn avoids concurrency within each worker process so you
|
23
23
|
have fewer things to worry about when developing your application. Of
|
24
|
-
course
|
24
|
+
course unicorn can use multiple worker processes to utilize multiple
|
25
25
|
CPUs or spindles. Applications can still use threads internally, however.
|
26
26
|
|
27
27
|
== Slow Clients Are Problematic
|
28
28
|
|
29
|
-
Most benchmarks we've seen don't tell you this, and
|
29
|
+
Most benchmarks we've seen don't tell you this, and unicorn doesn't
|
30
30
|
care about slow clients... but <i>you</i> should.
|
31
31
|
|
32
32
|
A "slow client" can be any client outside of your datacenter. Network
|
@@ -37,71 +37,71 @@ Persistent connections were introduced in HTTP/1.1 reduce latency from
|
|
37
37
|
connection establishment and TCP slow start. They also waste server
|
38
38
|
resources when clients are idle.
|
39
39
|
|
40
|
-
Persistent connections mean one of the
|
40
|
+
Persistent connections mean one of the unicorn worker processes
|
41
41
|
(depending on your application, it can be very memory hungry) would
|
42
42
|
spend a significant amount of its time idle keeping the connection alive
|
43
43
|
<i>and not doing anything else</i>. Being single-threaded and using
|
44
44
|
blocking I/O, a worker cannot serve other clients while keeping a
|
45
|
-
connection alive. Thus
|
45
|
+
connection alive. Thus unicorn does not implement persistent
|
46
46
|
connections.
|
47
47
|
|
48
48
|
If your application responses are larger than the socket buffer or if
|
49
49
|
you're handling large requests (uploads), worker processes will also be
|
50
50
|
bottlenecked by the speed of the *client* connection. You should
|
51
|
-
not allow
|
51
|
+
not allow unicorn to serve clients outside of your local network.
|
52
52
|
|
53
53
|
== Application Concurrency != Network Concurrency
|
54
54
|
|
55
55
|
Performance is asymmetric across the different subsystems of the machine
|
56
56
|
and parts of the network. CPUs and main memory can process gigabytes of
|
57
57
|
data in a second; clients on the Internet are usually only capable of a
|
58
|
-
tiny fraction of that.
|
58
|
+
tiny fraction of that. unicorn deployments should avoid dealing with
|
59
59
|
slow clients directly and instead rely on a reverse proxy to shield it
|
60
60
|
from the effects of slow I/O.
|
61
61
|
|
62
62
|
== Improved Performance Through Reverse Proxying
|
63
63
|
|
64
|
-
By acting as a buffer to shield
|
64
|
+
By acting as a buffer to shield unicorn from slow I/O, a reverse proxy
|
65
65
|
will inevitably incur overhead in the form of extra data copies.
|
66
66
|
However, as I/O within a local network is fast (and faster still
|
67
67
|
with local sockets), this overhead is neglible for the vast majority
|
68
68
|
of HTTP requests and responses.
|
69
69
|
|
70
|
-
The ideal reverse proxy complements the weaknesses of
|
71
|
-
A reverse proxy for
|
72
|
-
|
73
|
-
|
74
|
-
|
75
|
-
|
76
|
-
|
77
|
-
|
78
|
-
|
79
|
-
|
80
|
-
|
81
|
-
|
82
|
-
|
83
|
-
|
84
|
-
|
85
|
-
|
86
|
-
|
87
|
-
|
88
|
-
|
89
|
-
|
90
|
-
|
91
|
-
|
92
|
-
|
93
|
-
|
94
|
-
|
95
|
-
|
96
|
-
|
97
|
-
|
98
|
-
|
99
|
-
|
70
|
+
The ideal reverse proxy complements the weaknesses of unicorn.
|
71
|
+
A reverse proxy for unicorn should meet the following requirements:
|
72
|
+
|
73
|
+
1. It should fully buffer all HTTP requests (and large responses).
|
74
|
+
Each request should be "corked" in the reverse proxy and sent
|
75
|
+
as fast as possible to the backend unicorn processes. This is
|
76
|
+
the most important feature to look for when choosing a
|
77
|
+
reverse proxy for unicorn.
|
78
|
+
|
79
|
+
2. It should spend minimal time in userspace. Network (and disk) I/O
|
80
|
+
are system-level tasks and usually managed by the kernel.
|
81
|
+
This may change if userspace TCP stacks become more popular in the
|
82
|
+
future; but the reverse proxy should not waste time with
|
83
|
+
application-level logic. These concerns should be separated
|
84
|
+
|
85
|
+
3. It should avoid context switches and CPU scheduling overhead.
|
86
|
+
In many (most?) cases, network devices and their interrupts are
|
87
|
+
only be handled by one CPU at a time. It should avoid contention
|
88
|
+
within the system by serializing all network I/O into one (or few)
|
89
|
+
userspace procceses. Network I/O is not a CPU-intensive task and
|
90
|
+
it is not helpful to use multiple CPU cores (at least not for GigE).
|
91
|
+
|
92
|
+
4. It should efficiently manage persistent connections (and
|
93
|
+
pipelining) to slow clients. If you care to serve slow clients
|
94
|
+
outside your network, then these features of HTTP/1.1 will help.
|
95
|
+
|
96
|
+
5. It should (optionally) serve static files. If you have static
|
97
|
+
files on your site (especially large ones), they are far more
|
98
|
+
efficiently served with as few data copies as possible (e.g. with
|
99
|
+
sendfile() to completely avoid copying the data to userspace).
|
100
100
|
|
101
101
|
nginx is the only (Free) solution we know of that meets the above
|
102
102
|
requirements.
|
103
103
|
|
104
|
-
Indeed, the folks behind
|
104
|
+
Indeed, the folks behind unicorn have deployed nginx as a reverse-proxy not
|
105
105
|
only for Ruby applications, but also for production applications running
|
106
106
|
Apache/mod_perl, Apache/mod_php and Apache Tomcat. In every single
|
107
107
|
case, performance improved because application servers were able to use
|
@@ -129,17 +129,17 @@ that is not the Unix way.
|
|
129
129
|
|
130
130
|
== Just Worse in Some Cases
|
131
131
|
|
132
|
-
|
132
|
+
unicorn is not suited for all applications. unicorn is optimized for
|
133
133
|
applications that are CPU/memory/disk intensive and spend little time
|
134
134
|
waiting on external resources (e.g. a database server or external API).
|
135
135
|
|
136
|
-
|
136
|
+
unicorn is highly inefficient for Comet/reverse-HTTP/push applications
|
137
137
|
where the HTTP connection spends a large amount of time idle.
|
138
138
|
Nevertheless, the ease of troubleshooting, debugging, and management of
|
139
|
-
|
139
|
+
unicorn may still outweigh the drawbacks for these applications.
|
140
140
|
|
141
141
|
The {Rainbows!}[http://rainbows.rubyforge.org/] aims to fill the gap for
|
142
|
-
odd corner cases where the nginx +
|
142
|
+
odd corner cases where the nginx + unicorn combination is not enough.
|
143
143
|
While Rainbows! management/administration is largely identical to
|
144
|
-
|
144
|
+
unicorn, Rainbows! is far more ambitious and has seen little real-world
|
145
145
|
usage.
|
data/Sandbox
CHANGED
@@ -3,7 +3,7 @@
|
|
3
3
|
Since unicorn includes executables and is usually used to start a Ruby
|
4
4
|
process, there are certain caveats to using it with tools that sandbox
|
5
5
|
RubyGems installations such as
|
6
|
-
{Bundler}[http://
|
6
|
+
{Bundler}[http://gembundler.com/] or
|
7
7
|
{Isolate}[http://github.com/jbarnette/isolate].
|
8
8
|
|
9
9
|
== General deployment
|
@@ -45,11 +45,20 @@ This is no longer be an issue as of bundler 0.9.17
|
|
45
45
|
|
46
46
|
ref: http://mid.gmane.org/8FC34B23-5994-41CC-B5AF-7198EF06909E@tramchase.com
|
47
47
|
|
48
|
+
=== BUNDLE_GEMFILE for Capistrano users
|
49
|
+
|
50
|
+
You may need to set or reset the BUNDLE_GEMFILE environment variable in
|
51
|
+
the before_exec hook:
|
52
|
+
|
53
|
+
before_exec do |server|
|
54
|
+
ENV["BUNDLE_GEMFILE"] = "/path/to/app/current/Gemfile"
|
55
|
+
end
|
56
|
+
|
48
57
|
=== Other ENV pollution issues
|
49
58
|
|
50
|
-
|
51
|
-
|
52
|
-
http://gist.github.com/534668
|
59
|
+
If you're using an older Bundler version (0.9.x), you may need to set or
|
60
|
+
reset GEM_HOME, GEM_PATH and PATH environment variables in the
|
61
|
+
before_exec hook as illustrated by http://gist.github.com/534668
|
53
62
|
|
54
63
|
== Isolate
|
55
64
|
|
data/examples/nginx.conf
CHANGED
@@ -87,6 +87,14 @@ http {
|
|
87
87
|
# listen 80 default deferred; # for Linux
|
88
88
|
# listen 80 default accept_filter=httpready; # for FreeBSD
|
89
89
|
|
90
|
+
# If you have IPv6, you'll likely want to have two separate listeners.
|
91
|
+
# One on IPv4 only (the default), and another on IPv6 only instead
|
92
|
+
# of a single dual-stack listener. A dual-stack listener will make
|
93
|
+
# for ugly IPv4 addresses in $remote_addr (e.g ":ffff:10.0.0.1"
|
94
|
+
# instead of just "10.0.0.1") and potentially trigger bugs in
|
95
|
+
# some software.
|
96
|
+
# listen [::]:80 ipv6only=on; # deferred or accept_filter recommended
|
97
|
+
|
90
98
|
client_max_body_size 4G;
|
91
99
|
server_name _;
|
92
100
|
|
data/ext/unicorn_http/ext_help.h
CHANGED
@@ -36,6 +36,22 @@ static void rb_18_str_set_len(VALUE str, long len)
|
|
36
36
|
# endif
|
37
37
|
#endif /* ! defined(OFFT2NUM) */
|
38
38
|
|
39
|
+
#if !defined(SIZET2NUM)
|
40
|
+
# if SIZEOF_SIZE_T == SIZEOF_LONG
|
41
|
+
# define SIZET2NUM(n) ULONG2NUM(n)
|
42
|
+
# else
|
43
|
+
# define SIZET2NUM(n) ULL2NUM(n)
|
44
|
+
# endif
|
45
|
+
#endif /* ! defined(SIZET2NUM) */
|
46
|
+
|
47
|
+
#if !defined(NUM2SIZET)
|
48
|
+
# if SIZEOF_SIZE_T == SIZEOF_LONG
|
49
|
+
# define NUM2SIZET(n) ((size_t)NUM2ULONG(n))
|
50
|
+
# else
|
51
|
+
# define NUM2SIZET(n) ((size_t)NUM2ULL(n))
|
52
|
+
# endif
|
53
|
+
#endif /* ! defined(NUM2SIZET) */
|
54
|
+
|
39
55
|
#ifndef HAVE_RB_STR_MODIFY
|
40
56
|
# define rb_str_modify(x) do {} while (0)
|
41
57
|
#endif /* ! defined(HAVE_RB_STR_MODIFY) */
|
data/ext/unicorn_http/extconf.rb
CHANGED
@@ -2,6 +2,7 @@
|
|
2
2
|
require 'mkmf'
|
3
3
|
|
4
4
|
have_macro("SIZEOF_OFF_T", "ruby.h") or check_sizeof("off_t", "sys/types.h")
|
5
|
+
have_macro("SIZEOF_SIZE_T", "ruby.h") or check_sizeof("size_t", "sys/types.h")
|
5
6
|
have_macro("SIZEOF_LONG", "ruby.h") or check_sizeof("long", "sys/types.h")
|
6
7
|
have_func("rb_str_set_len", "ruby.h")
|
7
8
|
have_func("gmtime_r", "time.h")
|
@@ -1,6 +1,8 @@
|
|
1
1
|
#ifndef global_variables_h
|
2
2
|
#define global_variables_h
|
3
3
|
static VALUE eHttpParserError;
|
4
|
+
static VALUE e413;
|
5
|
+
static VALUE e414;
|
4
6
|
|
5
7
|
static VALUE g_rack_url_scheme;
|
6
8
|
static VALUE g_request_method;
|
@@ -35,7 +37,7 @@ static VALUE g_http_11;
|
|
35
37
|
static const char * const MAX_##N##_LENGTH_ERR = \
|
36
38
|
"HTTP element " # N " is longer than the " # length " allowed length."
|
37
39
|
|
38
|
-
NORETURN(static void
|
40
|
+
NORETURN(static void parser_raise(VALUE klass, const char *));
|
39
41
|
|
40
42
|
/**
|
41
43
|
* Validates the max length of given input and throws an HttpParserError
|
@@ -43,7 +45,12 @@ NORETURN(static void parser_error(const char *));
|
|
43
45
|
*/
|
44
46
|
#define VALIDATE_MAX_LENGTH(len, N) do { \
|
45
47
|
if (len > MAX_##N##_LENGTH) \
|
46
|
-
|
48
|
+
parser_raise(eHttpParserError, MAX_##N##_LENGTH_ERR); \
|
49
|
+
} while (0)
|
50
|
+
|
51
|
+
#define VALIDATE_MAX_URI_LENGTH(len, N) do { \
|
52
|
+
if (len > MAX_##N##_LENGTH) \
|
53
|
+
parser_raise(e414, MAX_##N##_LENGTH_ERR); \
|
47
54
|
} while (0)
|
48
55
|
|
49
56
|
/** Defines global strings in the init method. */
|
@@ -59,7 +66,6 @@ DEF_MAX_LENGTH(REQUEST_URI, 1024 * 12);
|
|
59
66
|
DEF_MAX_LENGTH(FRAGMENT, 1024); /* Don't know if this length is specified somewhere or not */
|
60
67
|
DEF_MAX_LENGTH(REQUEST_PATH, 1024);
|
61
68
|
DEF_MAX_LENGTH(QUERY_STRING, (1024 * 10));
|
62
|
-
DEF_MAX_LENGTH(HEADER, (1024 * (80 + 32)));
|
63
69
|
|
64
70
|
static void init_globals(void)
|
65
71
|
{
|
@@ -82,6 +82,14 @@ static VALUE xftrust(VALUE self)
|
|
82
82
|
return trust_x_forward;
|
83
83
|
}
|
84
84
|
|
85
|
+
static size_t MAX_HEADER_LEN = 1024 * (80 + 32); /* same as Mongrel */
|
86
|
+
|
87
|
+
/* this is only intended for use with Rainbows! */
|
88
|
+
static VALUE set_maxhdrlen(VALUE self, VALUE len)
|
89
|
+
{
|
90
|
+
return SIZET2NUM(MAX_HEADER_LEN = NUM2SIZET(len));
|
91
|
+
}
|
92
|
+
|
85
93
|
/* keep this small for Rainbows! since every client has one */
|
86
94
|
struct http_parser {
|
87
95
|
int cs; /* Ragel internal state */
|
@@ -106,17 +114,17 @@ struct http_parser {
|
|
106
114
|
} len;
|
107
115
|
};
|
108
116
|
|
109
|
-
static ID id_clear;
|
117
|
+
static ID id_clear, id_set_backtrace;
|
110
118
|
|
111
119
|
static void finalize_header(struct http_parser *hp);
|
112
120
|
|
113
|
-
static void
|
121
|
+
static void parser_raise(VALUE klass, const char *msg)
|
114
122
|
{
|
115
|
-
VALUE exc = rb_exc_new2(
|
123
|
+
VALUE exc = rb_exc_new2(klass, msg);
|
116
124
|
VALUE bt = rb_ary_new();
|
117
125
|
|
118
|
-
|
119
|
-
|
126
|
+
rb_funcall(exc, id_set_backtrace, 1, bt);
|
127
|
+
rb_exc_raise(exc);
|
120
128
|
}
|
121
129
|
|
122
130
|
#define REMAINING (unsigned long)(pe - p)
|
@@ -124,12 +132,27 @@ static void parser_error(const char *msg)
|
|
124
132
|
#define MARK(M,FPC) (hp->M = (FPC) - buffer)
|
125
133
|
#define PTR_TO(F) (buffer + hp->F)
|
126
134
|
#define STR_NEW(M,FPC) rb_str_new(PTR_TO(M), LEN(M, FPC))
|
135
|
+
#define STRIPPED_STR_NEW(M,FPC) stripped_str_new(PTR_TO(M), LEN(M, FPC))
|
127
136
|
|
128
137
|
#define HP_FL_TEST(hp,fl) ((hp)->flags & (UH_FL_##fl))
|
129
138
|
#define HP_FL_SET(hp,fl) ((hp)->flags |= (UH_FL_##fl))
|
130
139
|
#define HP_FL_UNSET(hp,fl) ((hp)->flags &= ~(UH_FL_##fl))
|
131
140
|
#define HP_FL_ALL(hp,fl) (HP_FL_TEST(hp, fl) == (UH_FL_##fl))
|
132
141
|
|
142
|
+
static int is_lws(char c)
|
143
|
+
{
|
144
|
+
return (c == ' ' || c == '\t');
|
145
|
+
}
|
146
|
+
|
147
|
+
static VALUE stripped_str_new(const char *str, long len)
|
148
|
+
{
|
149
|
+
long end;
|
150
|
+
|
151
|
+
for (end = len - 1; end >= 0 && is_lws(str[end]); end--);
|
152
|
+
|
153
|
+
return rb_str_new(str, end + 1);
|
154
|
+
}
|
155
|
+
|
133
156
|
/*
|
134
157
|
* handles values of the "Connection:" header, keepalive is implied
|
135
158
|
* for HTTP/1.1 but needs to be explicitly enabled with HTTP/1.0
|
@@ -186,35 +209,43 @@ http_version(struct http_parser *hp, const char *ptr, size_t len)
|
|
186
209
|
static inline void hp_invalid_if_trailer(struct http_parser *hp)
|
187
210
|
{
|
188
211
|
if (HP_FL_TEST(hp, INTRAILER))
|
189
|
-
|
212
|
+
parser_raise(eHttpParserError, "invalid Trailer");
|
190
213
|
}
|
191
214
|
|
192
215
|
static void write_cont_value(struct http_parser *hp,
|
193
216
|
char *buffer, const char *p)
|
194
217
|
{
|
195
218
|
char *vptr;
|
219
|
+
long end;
|
220
|
+
long len = LEN(mark, p);
|
221
|
+
long cont_len;
|
196
222
|
|
197
223
|
if (hp->cont == Qfalse)
|
198
|
-
|
224
|
+
parser_raise(eHttpParserError, "invalid continuation line");
|
199
225
|
if (NIL_P(hp->cont))
|
200
226
|
return; /* we're ignoring this header (probably Host:) */
|
201
227
|
|
202
228
|
assert(TYPE(hp->cont) == T_STRING && "continuation line is not a string");
|
203
229
|
assert(hp->mark > 0 && "impossible continuation line offset");
|
204
230
|
|
205
|
-
if (
|
231
|
+
if (len == 0)
|
206
232
|
return;
|
207
233
|
|
208
|
-
|
234
|
+
cont_len = RSTRING_LEN(hp->cont);
|
235
|
+
if (cont_len > 0) {
|
209
236
|
--hp->mark;
|
210
|
-
|
237
|
+
len = LEN(mark, p);
|
238
|
+
}
|
211
239
|
vptr = PTR_TO(mark);
|
212
240
|
|
213
|
-
|
241
|
+
/* normalize tab to space */
|
242
|
+
if (cont_len > 0) {
|
214
243
|
assert((' ' == *vptr || '\t' == *vptr) && "invalid leading white space");
|
215
244
|
*vptr = ' ';
|
216
245
|
}
|
217
|
-
|
246
|
+
|
247
|
+
for (end = len - 1; end >= 0 && is_lws(vptr[end]); end--);
|
248
|
+
rb_str_buf_cat(hp->cont, vptr, end + 1);
|
218
249
|
}
|
219
250
|
|
220
251
|
static void write_value(struct http_parser *hp,
|
@@ -225,7 +256,7 @@ static void write_value(struct http_parser *hp,
|
|
225
256
|
VALUE e;
|
226
257
|
|
227
258
|
VALIDATE_MAX_LENGTH(LEN(mark, p), FIELD_VALUE);
|
228
|
-
v = LEN(mark, p) == 0 ? rb_str_buf_new(128) :
|
259
|
+
v = LEN(mark, p) == 0 ? rb_str_buf_new(128) : STRIPPED_STR_NEW(mark, p);
|
229
260
|
if (NIL_P(f)) {
|
230
261
|
const char *field = PTR_TO(start.field);
|
231
262
|
size_t flen = hp->s.field_len;
|
@@ -246,7 +277,7 @@ static void write_value(struct http_parser *hp,
|
|
246
277
|
} else if (f == g_content_length) {
|
247
278
|
hp->len.content = parse_length(RSTRING_PTR(v), RSTRING_LEN(v));
|
248
279
|
if (hp->len.content < 0)
|
249
|
-
|
280
|
+
parser_raise(eHttpParserError, "invalid Content-Length");
|
250
281
|
if (hp->len.content != 0)
|
251
282
|
HP_FL_SET(hp, HASBODY);
|
252
283
|
hp_invalid_if_trailer(hp);
|
@@ -301,7 +332,7 @@ static void write_value(struct http_parser *hp,
|
|
301
332
|
action request_uri {
|
302
333
|
VALUE str;
|
303
334
|
|
304
|
-
|
335
|
+
VALIDATE_MAX_URI_LENGTH(LEN(mark, fpc), REQUEST_URI);
|
305
336
|
str = rb_hash_aset(hp->env, g_request_uri, STR_NEW(mark, fpc));
|
306
337
|
/*
|
307
338
|
* "OPTIONS * HTTP/1.1\r\n" is a valid request, but we can't have '*'
|
@@ -314,19 +345,19 @@ static void write_value(struct http_parser *hp,
|
|
314
345
|
}
|
315
346
|
}
|
316
347
|
action fragment {
|
317
|
-
|
348
|
+
VALIDATE_MAX_URI_LENGTH(LEN(mark, fpc), FRAGMENT);
|
318
349
|
rb_hash_aset(hp->env, g_fragment, STR_NEW(mark, fpc));
|
319
350
|
}
|
320
351
|
action start_query {MARK(start.query, fpc); }
|
321
352
|
action query_string {
|
322
|
-
|
353
|
+
VALIDATE_MAX_URI_LENGTH(LEN(start.query, fpc), QUERY_STRING);
|
323
354
|
rb_hash_aset(hp->env, g_query_string, STR_NEW(start.query, fpc));
|
324
355
|
}
|
325
356
|
action http_version { http_version(hp, PTR_TO(mark), LEN(mark, fpc)); }
|
326
357
|
action request_path {
|
327
358
|
VALUE val;
|
328
359
|
|
329
|
-
|
360
|
+
VALIDATE_MAX_URI_LENGTH(LEN(mark, fpc), REQUEST_PATH);
|
330
361
|
val = rb_hash_aset(hp->env, g_request_path, STR_NEW(mark, fpc));
|
331
362
|
|
332
363
|
/* rack says PATH_INFO must start with "/" or be empty */
|
@@ -336,7 +367,7 @@ static void write_value(struct http_parser *hp,
|
|
336
367
|
action add_to_chunk_size {
|
337
368
|
hp->len.chunk = step_incr(hp->len.chunk, fc, 16);
|
338
369
|
if (hp->len.chunk < 0)
|
339
|
-
|
370
|
+
parser_raise(eHttpParserError, "invalid chunk size");
|
340
371
|
}
|
341
372
|
action header_done {
|
342
373
|
finalize_header(hp);
|
@@ -677,7 +708,8 @@ static VALUE HttpParser_parse(VALUE self)
|
|
677
708
|
}
|
678
709
|
|
679
710
|
http_parser_execute(hp, RSTRING_PTR(data), RSTRING_LEN(data));
|
680
|
-
|
711
|
+
if (hp->offset > MAX_HEADER_LEN)
|
712
|
+
parser_raise(e413, "HTTP header is too large");
|
681
713
|
|
682
714
|
if (hp->cs == http_parser_first_final ||
|
683
715
|
hp->cs == http_parser_en_ChunkedBody) {
|
@@ -690,11 +722,32 @@ static VALUE HttpParser_parse(VALUE self)
|
|
690
722
|
}
|
691
723
|
|
692
724
|
if (hp->cs == http_parser_error)
|
693
|
-
|
725
|
+
parser_raise(eHttpParserError, "Invalid HTTP format, parsing fails.");
|
694
726
|
|
695
727
|
return Qnil;
|
696
728
|
}
|
697
729
|
|
730
|
+
/**
|
731
|
+
* Document-method: parse
|
732
|
+
* call-seq:
|
733
|
+
* parser.add_parse(buffer) => env or nil
|
734
|
+
*
|
735
|
+
* adds the contents of +buffer+ to the internal buffer and attempts to
|
736
|
+
* continue parsing. Returns the +env+ Hash on success or nil if more
|
737
|
+
* data is needed.
|
738
|
+
*
|
739
|
+
* Raises HttpParserError if there are parsing errors.
|
740
|
+
*/
|
741
|
+
static VALUE HttpParser_add_parse(VALUE self, VALUE buffer)
|
742
|
+
{
|
743
|
+
struct http_parser *hp = data_get(self);
|
744
|
+
|
745
|
+
Check_Type(buffer, T_STRING);
|
746
|
+
rb_str_buf_append(hp->buf, buffer);
|
747
|
+
|
748
|
+
return HttpParser_parse(self);
|
749
|
+
}
|
750
|
+
|
698
751
|
/**
|
699
752
|
* Document-method: trailers
|
700
753
|
* call-seq:
|
@@ -825,6 +878,7 @@ static VALUE HttpParser_filter_body(VALUE self, VALUE buf, VALUE data)
|
|
825
878
|
dlen = RSTRING_LEN(data);
|
826
879
|
|
827
880
|
StringValue(buf);
|
881
|
+
rb_str_modify(buf);
|
828
882
|
rb_str_resize(buf, dlen); /* we can never copy more than dlen bytes */
|
829
883
|
OBJ_TAINT(buf); /* keep weirdo $SAFE users happy */
|
830
884
|
|
@@ -835,7 +889,7 @@ static VALUE HttpParser_filter_body(VALUE self, VALUE buf, VALUE data)
|
|
835
889
|
hp->buf = data;
|
836
890
|
http_parser_execute(hp, dptr, dlen);
|
837
891
|
if (hp->cs == http_parser_error)
|
838
|
-
|
892
|
+
parser_raise(eHttpParserError, "Invalid HTTP format, parsing fails.");
|
839
893
|
|
840
894
|
assert(hp->s.dest_offset <= hp->offset &&
|
841
895
|
"destination buffer overflow");
|
@@ -883,6 +937,10 @@ void Init_unicorn_http(void)
|
|
883
937
|
cHttpParser = rb_define_class_under(mUnicorn, "HttpParser", rb_cObject);
|
884
938
|
eHttpParserError =
|
885
939
|
rb_define_class_under(mUnicorn, "HttpParserError", rb_eIOError);
|
940
|
+
e413 = rb_define_class_under(mUnicorn, "RequestEntityTooLargeError",
|
941
|
+
eHttpParserError);
|
942
|
+
e414 = rb_define_class_under(mUnicorn, "RequestURITooLongError",
|
943
|
+
eHttpParserError);
|
886
944
|
|
887
945
|
init_globals();
|
888
946
|
rb_define_alloc_func(cHttpParser, HttpParser_alloc);
|
@@ -890,6 +948,7 @@ void Init_unicorn_http(void)
|
|
890
948
|
rb_define_method(cHttpParser, "clear", HttpParser_clear, 0);
|
891
949
|
rb_define_method(cHttpParser, "reset", HttpParser_reset, 0);
|
892
950
|
rb_define_method(cHttpParser, "parse", HttpParser_parse, 0);
|
951
|
+
rb_define_method(cHttpParser, "add_parse", HttpParser_add_parse, 1);
|
893
952
|
rb_define_method(cHttpParser, "headers", HttpParser_headers, 2);
|
894
953
|
rb_define_method(cHttpParser, "trailers", HttpParser_headers, 2);
|
895
954
|
rb_define_method(cHttpParser, "filter_body", HttpParser_filter_body, 2);
|
@@ -924,6 +983,7 @@ void Init_unicorn_http(void)
|
|
924
983
|
rb_define_singleton_method(cHttpParser, "keepalive_requests=", set_ka_req, 1);
|
925
984
|
rb_define_singleton_method(cHttpParser, "trust_x_forwarded=", set_xftrust, 1);
|
926
985
|
rb_define_singleton_method(cHttpParser, "trust_x_forwarded?", xftrust, 0);
|
986
|
+
rb_define_singleton_method(cHttpParser, "max_header_len=", set_maxhdrlen, 1);
|
927
987
|
|
928
988
|
init_common_fields();
|
929
989
|
SET_GLOBAL(g_http_host, "HOST");
|
@@ -932,6 +992,7 @@ void Init_unicorn_http(void)
|
|
932
992
|
SET_GLOBAL(g_content_length, "CONTENT_LENGTH");
|
933
993
|
SET_GLOBAL(g_http_connection, "CONNECTION");
|
934
994
|
id_clear = rb_intern("clear");
|
995
|
+
id_set_backtrace = rb_intern("set_backtrace");
|
935
996
|
init_unicorn_httpdate();
|
936
997
|
}
|
937
998
|
#undef SET_GLOBAL
|
data/lib/unicorn/configurator.rb
CHANGED
@@ -281,6 +281,22 @@ class Unicorn::Configurator
|
|
281
281
|
#
|
282
282
|
# Default: +true+ in \Unicorn 3.4+, +false+ in Rainbows!
|
283
283
|
#
|
284
|
+
# [:ipv6only => true or false]
|
285
|
+
#
|
286
|
+
# This option makes IPv6-capable TCP listeners IPv6-only and unable
|
287
|
+
# to receive IPv4 queries on dual-stack systems. A separate IPv4-only
|
288
|
+
# listener is required if this is true.
|
289
|
+
#
|
290
|
+
# This option is only available for Ruby 1.9.2 and later.
|
291
|
+
#
|
292
|
+
# Enabling this option for the IPv6-only listener and having a
|
293
|
+
# separate IPv4 listener is recommended if you wish to support IPv6
|
294
|
+
# on the same TCP port. Otherwise, the value of \env[\"REMOTE_ADDR\"]
|
295
|
+
# will appear as an ugly IPv4-mapped-IPv6 address for IPv4 clients
|
296
|
+
# (e.g ":ffff:10.0.0.1" instead of just "10.0.0.1").
|
297
|
+
#
|
298
|
+
# Default: Operating-system dependent
|
299
|
+
#
|
284
300
|
# [:tries => Integer]
|
285
301
|
#
|
286
302
|
# Times to retry binding a socket if it is already in use
|
@@ -358,7 +374,7 @@ class Unicorn::Configurator
|
|
358
374
|
Integer === value or
|
359
375
|
raise ArgumentError, "not an integer: #{key}=#{value.inspect}"
|
360
376
|
end
|
361
|
-
[ :tcp_nodelay, :tcp_nopush ].each do |key|
|
377
|
+
[ :tcp_nodelay, :tcp_nopush, :ipv6only ].each do |key|
|
362
378
|
(value = options[key]).nil? and next
|
363
379
|
TrueClass === value || FalseClass === value or
|
364
380
|
raise ArgumentError, "not boolean: #{key}=#{value.inspect}"
|
data/lib/unicorn/const.rb
CHANGED
@@ -8,8 +8,8 @@
|
|
8
8
|
# improve things much compared to constants.
|
9
9
|
module Unicorn::Const
|
10
10
|
|
11
|
-
# The current version of Unicorn, currently 3.
|
12
|
-
UNICORN_VERSION = "3.
|
11
|
+
# The current version of Unicorn, currently 3.7.0
|
12
|
+
UNICORN_VERSION = "3.7.0"
|
13
13
|
|
14
14
|
# default TCP listen host address (0.0.0.0, all interfaces)
|
15
15
|
DEFAULT_HOST = "0.0.0.0"
|
@@ -25,12 +25,14 @@ module Unicorn::Const
|
|
25
25
|
|
26
26
|
# Maximum request body size before it is moved out of memory and into a
|
27
27
|
# temporary file for reading (112 kilobytes). This is the default
|
28
|
-
# value of
|
28
|
+
# value of client_body_buffer_size.
|
29
29
|
MAX_BODY = 1024 * 112
|
30
30
|
|
31
31
|
# :stopdoc:
|
32
32
|
# common errors we'll send back
|
33
33
|
ERROR_400_RESPONSE = "HTTP/1.1 400 Bad Request\r\n\r\n"
|
34
|
+
ERROR_414_RESPONSE = "HTTP/1.1 414 Request-URI Too Long\r\n\r\n"
|
35
|
+
ERROR_413_RESPONSE = "HTTP/1.1 413 Request Entity Too Large\r\n\r\n"
|
34
36
|
ERROR_500_RESPONSE = "HTTP/1.1 500 Internal Server Error\r\n\r\n"
|
35
37
|
EXPECT_100_RESPONSE = "HTTP/1.1 100 Continue\r\n\r\n"
|
36
38
|
|
data/lib/unicorn/http_request.rb
CHANGED
@@ -68,9 +68,7 @@ class Unicorn::HttpParser
|
|
68
68
|
if parse.nil?
|
69
69
|
# Parser is not done, queue up more data to read and continue parsing
|
70
70
|
# an Exception thrown from the parser will throw us out of the loop
|
71
|
-
|
72
|
-
buf << socket.kgio_read!(16384)
|
73
|
-
end while parse.nil?
|
71
|
+
false until add_parse(socket.kgio_read!(16384))
|
74
72
|
end
|
75
73
|
e[RACK_INPUT] = 0 == content_length ?
|
76
74
|
NULL_IO : @@input_class.new(socket, self)
|
data/lib/unicorn/http_server.rb
CHANGED
@@ -23,9 +23,6 @@ class Unicorn::HttpServer
|
|
23
23
|
# backwards compatibility with 1.x
|
24
24
|
Worker = Unicorn::Worker
|
25
25
|
|
26
|
-
# prevents IO objects in here from being GC-ed
|
27
|
-
IO_PURGATORY = []
|
28
|
-
|
29
26
|
# all bound listener sockets
|
30
27
|
LISTENERS = []
|
31
28
|
|
@@ -527,6 +524,10 @@ class Unicorn::HttpServer
|
|
527
524
|
msg = case e
|
528
525
|
when EOFError,Errno::ECONNRESET,Errno::EPIPE,Errno::EINVAL,Errno::EBADF
|
529
526
|
Unicorn::Const::ERROR_500_RESPONSE
|
527
|
+
when Unicorn::RequestURITooLongError
|
528
|
+
Unicorn::Const::ERROR_414_RESPONSE
|
529
|
+
when Unicorn::RequestEntityTooLargeError
|
530
|
+
Unicorn::Const::ERROR_413_RESPONSE
|
530
531
|
when Unicorn::HttpParserError # try to tell the client they're bad
|
531
532
|
Unicorn::Const::ERROR_400_RESPONSE
|
532
533
|
else
|
@@ -4,9 +4,12 @@ require 'socket'
|
|
4
4
|
|
5
5
|
module Unicorn
|
6
6
|
module SocketHelper
|
7
|
+
# :stopdoc:
|
7
8
|
include Socket::Constants
|
8
9
|
|
9
|
-
#
|
10
|
+
# prevents IO objects in here from being GC-ed
|
11
|
+
IO_PURGATORY = []
|
12
|
+
|
10
13
|
# internal interface, only used by Rainbows!/Zbatery
|
11
14
|
DEFAULTS = {
|
12
15
|
# The semantics for TCP_DEFER_ACCEPT changed in Linux 2.6.32+
|
@@ -136,8 +139,9 @@ module Unicorn
|
|
136
139
|
ensure
|
137
140
|
File.umask(old_umask)
|
138
141
|
end
|
139
|
-
elsif /\A(
|
140
|
-
|
142
|
+
elsif /\A\[([a-fA-F0-9:]+)\]:(\d+)\z/ =~ address
|
143
|
+
new_ipv6_server($1, $2.to_i, opt)
|
144
|
+
elsif /\A(\d+\.\d+\.\d+\.\d+):(\d+)\z/ =~ address
|
141
145
|
Kgio::TCPServer.new($1, $2.to_i)
|
142
146
|
else
|
143
147
|
raise ArgumentError, "Don't know how to bind: #{address}"
|
@@ -146,6 +150,18 @@ module Unicorn
|
|
146
150
|
sock
|
147
151
|
end
|
148
152
|
|
153
|
+
def new_ipv6_server(addr, port, opt)
|
154
|
+
opt.key?(:ipv6only) or return Kgio::TCPServer.new(addr, port)
|
155
|
+
defined?(IPV6_V6ONLY) or
|
156
|
+
abort "Socket::IPV6_V6ONLY not defined, upgrade Ruby and/or your OS"
|
157
|
+
sock = Socket.new(AF_INET6, SOCK_STREAM, 0)
|
158
|
+
sock.setsockopt(IPPROTO_IPV6, IPV6_V6ONLY, opt[:ipv6only] ? 1 : 0)
|
159
|
+
sock.setsockopt(SOL_SOCKET, SO_REUSEADDR, 1)
|
160
|
+
sock.bind(Socket.pack_sockaddr_in(port, addr))
|
161
|
+
IO_PURGATORY << sock
|
162
|
+
Kgio::TCPServer.for_fd(sock.fileno)
|
163
|
+
end
|
164
|
+
|
149
165
|
# returns rfc2732-style (e.g. "[::1]:666") addresses for IPv6
|
150
166
|
def tcp_name(sock)
|
151
167
|
port, addr = Socket.unpack_sockaddr_in(sock.getsockname)
|
data/script/isolate_for_tests
CHANGED
data/t/t0002-parser-error.sh
CHANGED
@@ -1,6 +1,6 @@
|
|
1
1
|
#!/bin/sh
|
2
2
|
. ./test-lib.sh
|
3
|
-
t_plan
|
3
|
+
t_plan 11 "parser error test"
|
4
4
|
|
5
5
|
t_begin "setup and startup" && {
|
6
6
|
unicorn_setup
|
@@ -24,6 +24,69 @@ t_begin "response should be a 400" && {
|
|
24
24
|
grep -F 'HTTP/1.1 400 Bad Request' $tmp
|
25
25
|
}
|
26
26
|
|
27
|
+
t_begin "send a huge Request URI (REQUEST_PATH > (12 * 1024))" && {
|
28
|
+
rm -f $tmp
|
29
|
+
cat $fifo > $tmp &
|
30
|
+
(
|
31
|
+
set -e
|
32
|
+
trap 'echo ok > $ok' EXIT
|
33
|
+
printf 'GET /'
|
34
|
+
for i in $(awk </dev/null 'BEGIN{for(i=0;i<1024;i++) print i}')
|
35
|
+
do
|
36
|
+
printf '0123456789ab'
|
37
|
+
done
|
38
|
+
printf ' HTTP/1.1\r\nHost: example.com\r\n\r\n'
|
39
|
+
) | socat - TCP:$listen > $fifo || :
|
40
|
+
test xok = x$(cat $ok)
|
41
|
+
wait
|
42
|
+
}
|
43
|
+
|
44
|
+
t_begin "response should be a 414 (REQUEST_PATH)" && {
|
45
|
+
grep -F 'HTTP/1.1 414 Request-URI Too Long' $tmp
|
46
|
+
}
|
47
|
+
|
48
|
+
t_begin "send a huge Request URI (QUERY_STRING > (10 * 1024))" && {
|
49
|
+
rm -f $tmp
|
50
|
+
cat $fifo > $tmp &
|
51
|
+
(
|
52
|
+
set -e
|
53
|
+
trap 'echo ok > $ok' EXIT
|
54
|
+
printf 'GET /hello-world?a'
|
55
|
+
for i in $(awk </dev/null 'BEGIN{for(i=0;i<1024;i++) print i}')
|
56
|
+
do
|
57
|
+
printf '0123456789'
|
58
|
+
done
|
59
|
+
printf ' HTTP/1.1\r\nHost: example.com\r\n\r\n'
|
60
|
+
) | socat - TCP:$listen > $fifo || :
|
61
|
+
test xok = x$(cat $ok)
|
62
|
+
wait
|
63
|
+
}
|
64
|
+
|
65
|
+
t_begin "response should be a 414 (QUERY_STRING)" && {
|
66
|
+
grep -F 'HTTP/1.1 414 Request-URI Too Long' $tmp
|
67
|
+
}
|
68
|
+
|
69
|
+
t_begin "send a huge Request URI (FRAGMENT > 1024)" && {
|
70
|
+
rm -f $tmp
|
71
|
+
cat $fifo > $tmp &
|
72
|
+
(
|
73
|
+
set -e
|
74
|
+
trap 'echo ok > $ok' EXIT
|
75
|
+
printf 'GET /hello-world#a'
|
76
|
+
for i in $(awk </dev/null 'BEGIN{for(i=0;i<64;i++) print i}')
|
77
|
+
do
|
78
|
+
printf '0123456789abcdef'
|
79
|
+
done
|
80
|
+
printf ' HTTP/1.1\r\nHost: example.com\r\n\r\n'
|
81
|
+
) | socat - TCP:$listen > $fifo || :
|
82
|
+
test xok = x$(cat $ok)
|
83
|
+
wait
|
84
|
+
}
|
85
|
+
|
86
|
+
t_begin "response should be a 414 (FRAGMENT)" && {
|
87
|
+
grep -F 'HTTP/1.1 414 Request-URI Too Long' $tmp
|
88
|
+
}
|
89
|
+
|
27
90
|
t_begin "server stderr should be clean" && check_stderr
|
28
91
|
|
29
92
|
t_begin "term signal sent" && kill $unicorn_pid
|
@@ -0,0 +1,49 @@
|
|
1
|
+
#!/bin/sh
|
2
|
+
. ./test-lib.sh
|
3
|
+
t_plan 5 "max_header_len setting (only intended for Rainbows!)"
|
4
|
+
|
5
|
+
t_begin "setup and start" && {
|
6
|
+
unicorn_setup
|
7
|
+
req='GET / HTTP/1.0\r\n\r\n'
|
8
|
+
len=$(printf "$req" | wc -c)
|
9
|
+
echo Unicorn::HttpParser.max_header_len = $len >> $unicorn_config
|
10
|
+
unicorn -D -c $unicorn_config env.ru
|
11
|
+
unicorn_wait_start
|
12
|
+
}
|
13
|
+
|
14
|
+
t_begin "minimal request succeeds" && {
|
15
|
+
rm -f $tmp
|
16
|
+
(
|
17
|
+
cat $fifo > $tmp &
|
18
|
+
printf "$req"
|
19
|
+
wait
|
20
|
+
echo ok > $ok
|
21
|
+
) | socat - TCP:$listen > $fifo
|
22
|
+
test xok = x$(cat $ok)
|
23
|
+
|
24
|
+
fgrep "HTTP/1.1 200 OK" $tmp
|
25
|
+
}
|
26
|
+
|
27
|
+
t_begin "big request fails" && {
|
28
|
+
rm -f $tmp
|
29
|
+
(
|
30
|
+
cat $fifo > $tmp &
|
31
|
+
printf 'GET /xxxxxx HTTP/1.0\r\n\r\n'
|
32
|
+
wait
|
33
|
+
echo ok > $ok
|
34
|
+
) | socat - TCP:$listen > $fifo
|
35
|
+
test xok = x$(cat $ok)
|
36
|
+
fgrep "HTTP/1.1 413" $tmp
|
37
|
+
}
|
38
|
+
|
39
|
+
dbgcat tmp
|
40
|
+
|
41
|
+
t_begin "killing succeeds" && {
|
42
|
+
kill $unicorn_pid
|
43
|
+
}
|
44
|
+
|
45
|
+
t_begin "check stderr" && {
|
46
|
+
check_stderr
|
47
|
+
}
|
48
|
+
|
49
|
+
t_done
|
@@ -258,6 +258,20 @@ class HttpParserTest < Test::Unit::TestCase
|
|
258
258
|
assert_equal 'hi y x ASDF', req['HTTP_X_ASDF']
|
259
259
|
end
|
260
260
|
|
261
|
+
def test_continuation_eats_trailing_spaces
|
262
|
+
parser = HttpParser.new
|
263
|
+
header = "GET / HTTP/1.1\r\n" \
|
264
|
+
"X-ASDF: \r\n" \
|
265
|
+
"\t\r\n" \
|
266
|
+
" b \r\n" \
|
267
|
+
" ASDF\r\n\r\n"
|
268
|
+
parser.buf << header
|
269
|
+
req = parser.env
|
270
|
+
assert_equal req, parser.parse
|
271
|
+
assert_equal '', parser.buf
|
272
|
+
assert_equal 'b ASDF', req['HTTP_X_ASDF']
|
273
|
+
end
|
274
|
+
|
261
275
|
def test_continuation_with_absolute_uri_and_ignored_host_header
|
262
276
|
parser = HttpParser.new
|
263
277
|
header = "GET http://example.com/ HTTP/1.1\r\n" \
|
@@ -764,6 +778,48 @@ class HttpParserTest < Test::Unit::TestCase
|
|
764
778
|
|
765
779
|
end
|
766
780
|
|
781
|
+
def test_leading_tab
|
782
|
+
parser = HttpParser.new
|
783
|
+
get = "GET / HTTP/1.1\r\nHost:\texample.com\r\n\r\n"
|
784
|
+
assert parser.add_parse(get)
|
785
|
+
assert_equal 'example.com', parser.env['HTTP_HOST']
|
786
|
+
end
|
787
|
+
|
788
|
+
def test_trailing_whitespace
|
789
|
+
parser = HttpParser.new
|
790
|
+
get = "GET / HTTP/1.1\r\nHost: example.com \r\n\r\n"
|
791
|
+
assert parser.add_parse(get)
|
792
|
+
assert_equal 'example.com', parser.env['HTTP_HOST']
|
793
|
+
end
|
794
|
+
|
795
|
+
def test_trailing_tab
|
796
|
+
parser = HttpParser.new
|
797
|
+
get = "GET / HTTP/1.1\r\nHost: example.com\t\r\n\r\n"
|
798
|
+
assert parser.add_parse(get)
|
799
|
+
assert_equal 'example.com', parser.env['HTTP_HOST']
|
800
|
+
end
|
801
|
+
|
802
|
+
def test_trailing_multiple_linear_whitespace
|
803
|
+
parser = HttpParser.new
|
804
|
+
get = "GET / HTTP/1.1\r\nHost: example.com\t \t \t\r\n\r\n"
|
805
|
+
assert parser.add_parse(get)
|
806
|
+
assert_equal 'example.com', parser.env['HTTP_HOST']
|
807
|
+
end
|
808
|
+
|
809
|
+
def test_embedded_linear_whitespace_ok
|
810
|
+
parser = HttpParser.new
|
811
|
+
get = "GET / HTTP/1.1\r\nX-Space: hello\t world\t \r\n\r\n"
|
812
|
+
assert parser.add_parse(get)
|
813
|
+
assert_equal "hello\t world", parser.env["HTTP_X_SPACE"]
|
814
|
+
end
|
815
|
+
|
816
|
+
def test_empty_header
|
817
|
+
parser = HttpParser.new
|
818
|
+
get = "GET / HTTP/1.1\r\nHost: \r\n\r\n"
|
819
|
+
assert parser.add_parse(get)
|
820
|
+
assert_equal '', parser.env['HTTP_HOST']
|
821
|
+
end
|
822
|
+
|
767
823
|
# so we don't care about the portability of this test
|
768
824
|
# if it doesn't leak on Linux, it won't leak anywhere else
|
769
825
|
# unless your C compiler or platform is otherwise broken
|
@@ -12,6 +12,7 @@ class TestSocketHelper < Test::Unit::TestCase
|
|
12
12
|
@log_tmp = Tempfile.new 'logger'
|
13
13
|
@logger = Logger.new(@log_tmp.path)
|
14
14
|
@test_addr = ENV['UNICORN_TEST_ADDR'] || '127.0.0.1'
|
15
|
+
@test6_addr = ENV['UNICORN_TEST6_ADDR'] || '::1'
|
15
16
|
GC.disable
|
16
17
|
end
|
17
18
|
|
@@ -177,4 +178,11 @@ class TestSocketHelper < Test::Unit::TestCase
|
|
177
178
|
assert cur > 1
|
178
179
|
end if defined?(TCP_DEFER_ACCEPT)
|
179
180
|
|
181
|
+
def test_ipv6only
|
182
|
+
port = unused_port "#@test6_addr"
|
183
|
+
sock = bind_listen "[#@test6_addr]:#{port}", :ipv6only => true
|
184
|
+
cur = sock.getsockopt(:IPPROTO_IPV6, :IPV6_V6ONLY).unpack('i')[0]
|
185
|
+
assert_equal 1, cur
|
186
|
+
rescue Errno::EAFNOSUPPORT
|
187
|
+
end if RUBY_VERSION >= "1.9.2"
|
180
188
|
end
|
data/unicorn.gemspec
CHANGED
@@ -36,7 +36,7 @@ Gem::Specification.new do |s|
|
|
36
36
|
s.add_dependency(%q<rack>)
|
37
37
|
s.add_dependency(%q<kgio>, '~> 2.3')
|
38
38
|
|
39
|
-
s.add_development_dependency('isolate', '~> 3.
|
39
|
+
s.add_development_dependency('isolate', '~> 3.1')
|
40
40
|
s.add_development_dependency('wrongdoc', '~> 1.5')
|
41
41
|
|
42
42
|
# s.licenses = %w(GPLv2 Ruby) # licenses= method is not in older RubyGems
|
metadata
CHANGED
@@ -5,9 +5,9 @@ version: !ruby/object:Gem::Version
|
|
5
5
|
prerelease:
|
6
6
|
segments:
|
7
7
|
- 3
|
8
|
-
-
|
9
|
-
-
|
10
|
-
version: 3.
|
8
|
+
- 7
|
9
|
+
- 0
|
10
|
+
version: 3.7.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: 2011-
|
18
|
+
date: 2011-06-09 00:00:00 Z
|
19
19
|
dependencies:
|
20
20
|
- !ruby/object:Gem::Dependency
|
21
21
|
name: rack
|
@@ -54,12 +54,11 @@ dependencies:
|
|
54
54
|
requirements:
|
55
55
|
- - ~>
|
56
56
|
- !ruby/object:Gem::Version
|
57
|
-
hash:
|
57
|
+
hash: 5
|
58
58
|
segments:
|
59
59
|
- 3
|
60
|
-
-
|
61
|
-
|
62
|
-
version: 3.0.0
|
60
|
+
- 1
|
61
|
+
version: "3.1"
|
63
62
|
type: :development
|
64
63
|
version_requirements: *id003
|
65
64
|
- !ruby/object:Gem::Dependency
|
@@ -126,6 +125,7 @@ extra_rdoc_files:
|
|
126
125
|
- lib/unicorn/worker.rb
|
127
126
|
- ISSUES
|
128
127
|
- Sandbox
|
128
|
+
- Links
|
129
129
|
files:
|
130
130
|
- .CHANGELOG.old
|
131
131
|
- .document
|
@@ -150,6 +150,7 @@ files:
|
|
150
150
|
- KNOWN_ISSUES
|
151
151
|
- LATEST
|
152
152
|
- LICENSE
|
153
|
+
- Links
|
153
154
|
- NEWS
|
154
155
|
- PHILOSOPHY
|
155
156
|
- README
|
@@ -272,6 +273,7 @@ files:
|
|
272
273
|
- t/t0016-trust-x-forwarded-false.sh
|
273
274
|
- t/t0017-trust-x-forwarded-true.sh
|
274
275
|
- t/t0018-write-on-close.sh
|
276
|
+
- t/t0019-max_header_len.sh
|
275
277
|
- t/t0100-rack-input-tests.sh
|
276
278
|
- t/t0116-client_body_buffer_size.sh
|
277
279
|
- t/t0116.ru
|
@@ -399,7 +401,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
399
401
|
requirements: []
|
400
402
|
|
401
403
|
rubyforge_project: mongrel
|
402
|
-
rubygems_version: 1.
|
404
|
+
rubygems_version: 1.8.5
|
403
405
|
signing_key:
|
404
406
|
specification_version: 3
|
405
407
|
summary: Rack HTTP server for fast clients and Unix
|