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 CHANGED
@@ -17,3 +17,4 @@ unicorn_1
17
17
  unicorn_rails_1
18
18
  ISSUES
19
19
  Sandbox
20
+ Links
@@ -1,7 +1,7 @@
1
1
  all::
2
2
 
3
3
  PANDOC = pandoc
4
- PANDOC_OPTS = -f markdown --email-obfuscation=none --sanitize-html
4
+ PANDOC_OPTS = -f markdown --email-obfuscation=none
5
5
  pandoc = $(PANDOC) $(PANDOC_OPTS)
6
6
  pandoc_html = $(pandoc) --toc -t html --no-wrap
7
7
 
data/GIT-VERSION-GEN CHANGED
@@ -1,7 +1,7 @@
1
1
  #!/bin/sh
2
2
 
3
3
  GVF=GIT-VERSION-FILE
4
- DEF_VER=v3.6.2.GIT
4
+ DEF_VER=v3.7.0.GIT
5
5
 
6
6
  LF='
7
7
  '
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 Unicorn
1
+ = The Philosophy Behind unicorn
2
2
 
3
- Being a server that only runs on Unix-like platforms, Unicorn is
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, Unicorn is strictly a _backend_
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, Unicorn
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
- Unicorn uses an old-fashioned preforking worker model with blocking I/O.
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. Unicorn avoids concurrency within each worker process so you
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 Unicorn can use multiple worker processes to utilize multiple
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 Unicorn doesn't
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 Unicorn worker processes
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 Unicorn does not implement persistent
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 Unicorn to serve clients outside of your local network.
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. Unicorn deployments should avoid dealing with
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 Unicorn from slow I/O, a reverse proxy
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 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).
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 Unicorn have deployed nginx as a reverse-proxy not
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
- Unicorn is not suited for all applications. Unicorn is optimized for
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
- Unicorn is highly inefficient for Comet/reverse-HTTP/push applications
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
- Unicorn may still outweigh the drawbacks for these applications.
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 + Unicorn combination is not enough.
142
+ odd corner cases where the nginx + unicorn combination is not enough.
143
143
  While Rainbows! management/administration is largely identical to
144
- Unicorn, Rainbows! is far more ambitious and has seen little real-world
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://github.com/carlhuda/bundler] or
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
- You may need to set or reset BUNDLE_GEMFILE, GEM_HOME, GEM_PATH and PATH
51
- environment variables in the before_exec hook as illustrated by
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
 
@@ -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) */
@@ -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 parser_error(const char *));
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
- parser_error(MAX_##N##_LENGTH_ERR); \
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 parser_error(const char *msg)
121
+ static void parser_raise(VALUE klass, const char *msg)
114
122
  {
115
- VALUE exc = rb_exc_new2(eHttpParserError, msg);
123
+ VALUE exc = rb_exc_new2(klass, msg);
116
124
  VALUE bt = rb_ary_new();
117
125
 
118
- rb_funcall(exc, rb_intern("set_backtrace"), 1, bt);
119
- rb_exc_raise(exc);
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
- parser_error("invalid Trailer");
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
- parser_error("invalid continuation line");
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 (LEN(mark, p) == 0)
231
+ if (len == 0)
206
232
  return;
207
233
 
208
- if (RSTRING_LEN(hp->cont) > 0)
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
- if (RSTRING_LEN(hp->cont) > 0) {
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
- rb_str_buf_cat(hp->cont, vptr, LEN(mark, p));
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) : STR_NEW(mark, p);
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
- parser_error("invalid Content-Length");
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
- VALIDATE_MAX_LENGTH(LEN(mark, fpc), REQUEST_URI);
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
- VALIDATE_MAX_LENGTH(LEN(mark, fpc), FRAGMENT);
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
- VALIDATE_MAX_LENGTH(LEN(start.query, fpc), QUERY_STRING);
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
- VALIDATE_MAX_LENGTH(LEN(mark, fpc), REQUEST_PATH);
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
- parser_error("invalid chunk size");
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
- VALIDATE_MAX_LENGTH(hp->offset, HEADER);
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
- parser_error("Invalid HTTP format, parsing fails.");
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
- parser_error("Invalid HTTP format, parsing fails.");
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
@@ -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.6.2
12
- UNICORN_VERSION = "3.6.2"
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 of client_body_buffer_size.
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
 
@@ -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
- begin
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)
@@ -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
- # :stopdoc:
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(\d+\.\d+\.\d+\.\d+):(\d+)\z/ =~ address ||
140
- /\A\[([a-fA-F0-9:]+)\]:(\d+)\z/ =~ address
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)
@@ -35,6 +35,7 @@ File.rename("#{dst}.#$$", dst)
35
35
  opts[:path] = "tmp/isolate/rails-#{rails_ver}"
36
36
  pid = fork do
37
37
  Isolate.now!(opts) do
38
+ gem 'rake', '0.8.7'
38
39
  gem 'rails', rails_ver
39
40
  end
40
41
  end
@@ -1,6 +1,6 @@
1
1
  #!/bin/sh
2
2
  . ./test-lib.sh
3
- t_plan 5 "parser error test"
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.0.0')
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
- - 6
9
- - 2
10
- version: 3.6.2
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-04-30 00:00:00 Z
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: 7
57
+ hash: 5
58
58
  segments:
59
59
  - 3
60
- - 0
61
- - 0
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.7.2
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