unicorn 3.6.2 → 3.7.0
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 +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
|