unicorn 3.6.0 → 4.0.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (58) hide show
  1. data/.document +1 -0
  2. data/.manifest +13 -0
  3. data/ChangeLog +783 -1
  4. data/DESIGN +0 -8
  5. data/Documentation/GNUmakefile +1 -1
  6. data/GIT-VERSION-FILE +1 -1
  7. data/GIT-VERSION-GEN +1 -1
  8. data/GNUmakefile +2 -2
  9. data/HACKING +11 -0
  10. data/KNOWN_ISSUES +2 -2
  11. data/LATEST +24 -24
  12. data/Links +53 -0
  13. data/NEWS +66 -0
  14. data/PHILOSOPHY +49 -49
  15. data/Sandbox +13 -4
  16. data/TODO +0 -2
  17. data/TUNING +31 -9
  18. data/bin/unicorn +2 -1
  19. data/bin/unicorn_rails +2 -1
  20. data/examples/big_app_gc.rb +2 -33
  21. data/examples/nginx.conf +17 -4
  22. data/ext/unicorn_http/ext_help.h +16 -0
  23. data/ext/unicorn_http/extconf.rb +1 -0
  24. data/ext/unicorn_http/global_variables.h +9 -3
  25. data/ext/unicorn_http/unicorn_http.c +357 -259
  26. data/ext/unicorn_http/unicorn_http.rl +148 -50
  27. data/lib/unicorn/configurator.rb +36 -8
  28. data/lib/unicorn/const.rb +5 -3
  29. data/lib/unicorn/http_request.rb +1 -3
  30. data/lib/unicorn/http_server.rb +82 -95
  31. data/lib/unicorn/oob_gc.rb +61 -50
  32. data/lib/unicorn/socket_helper.rb +23 -8
  33. data/lib/unicorn/worker.rb +45 -4
  34. data/lib/unicorn.rb +8 -6
  35. data/script/isolate_for_tests +4 -2
  36. data/t/broken-app.ru +12 -0
  37. data/t/heartbeat-timeout.ru +12 -0
  38. data/t/oob_gc.ru +21 -0
  39. data/t/oob_gc_path.ru +21 -0
  40. data/t/t0001-reload-bad-config.sh +1 -0
  41. data/t/t0002-parser-error.sh +64 -1
  42. data/t/t0004-heartbeat-timeout.sh +69 -0
  43. data/t/t0009-broken-app.sh +56 -0
  44. data/t/t0019-max_header_len.sh +49 -0
  45. data/t/t0020-at_exit-handler.sh +49 -0
  46. data/t/t9001-oob_gc.sh +47 -0
  47. data/t/t9002-oob_gc-path.sh +75 -0
  48. data/test/benchmark/stack.ru +8 -0
  49. data/test/unit/test_droplet.rb +28 -0
  50. data/test/unit/test_http_parser.rb +60 -4
  51. data/test/unit/test_http_parser_ng.rb +54 -0
  52. data/test/unit/test_response.rb +1 -1
  53. data/test/unit/test_server.rb +1 -1
  54. data/test/unit/test_signals.rb +1 -1
  55. data/test/unit/test_socket_helper.rb +8 -0
  56. data/test/unit/test_upload.rb +1 -1
  57. data/unicorn.gemspec +3 -2
  58. metadata +44 -16
@@ -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);
@@ -598,6 +629,34 @@ static VALUE HttpParser_clear(VALUE self)
598
629
  return self;
599
630
  }
600
631
 
632
+ /**
633
+ * call-seq:
634
+ * parser.dechunk! => parser
635
+ *
636
+ * Resets the parser to a state suitable for dechunking response bodies
637
+ *
638
+ */
639
+ static VALUE HttpParser_dechunk_bang(VALUE self)
640
+ {
641
+ struct http_parser *hp = data_get(self);
642
+
643
+ http_parser_init(hp);
644
+
645
+ /*
646
+ * we don't care about trailers in dechunk-only mode,
647
+ * but if we did we'd set UH_FL_HASTRAILER and clear hp->env
648
+ */
649
+ if (0) {
650
+ rb_funcall(hp->env, id_clear, 0);
651
+ hp->flags = UH_FL_HASTRAILER;
652
+ }
653
+
654
+ hp->flags |= UH_FL_HASBODY | UH_FL_INBODY | UH_FL_CHUNKED;
655
+ hp->cs = http_parser_en_ChunkedBody;
656
+
657
+ return self;
658
+ }
659
+
601
660
  /**
602
661
  * call-seq:
603
662
  * parser.reset => nil
@@ -677,7 +736,8 @@ static VALUE HttpParser_parse(VALUE self)
677
736
  }
678
737
 
679
738
  http_parser_execute(hp, RSTRING_PTR(data), RSTRING_LEN(data));
680
- VALIDATE_MAX_LENGTH(hp->offset, HEADER);
739
+ if (hp->offset > MAX_HEADER_LEN)
740
+ parser_raise(e413, "HTTP header is too large");
681
741
 
682
742
  if (hp->cs == http_parser_first_final ||
683
743
  hp->cs == http_parser_en_ChunkedBody) {
@@ -690,11 +750,32 @@ static VALUE HttpParser_parse(VALUE self)
690
750
  }
691
751
 
692
752
  if (hp->cs == http_parser_error)
693
- parser_error("Invalid HTTP format, parsing fails.");
753
+ parser_raise(eHttpParserError, "Invalid HTTP format, parsing fails.");
694
754
 
695
755
  return Qnil;
696
756
  }
697
757
 
758
+ /**
759
+ * Document-method: parse
760
+ * call-seq:
761
+ * parser.add_parse(buffer) => env or nil
762
+ *
763
+ * adds the contents of +buffer+ to the internal buffer and attempts to
764
+ * continue parsing. Returns the +env+ Hash on success or nil if more
765
+ * data is needed.
766
+ *
767
+ * Raises HttpParserError if there are parsing errors.
768
+ */
769
+ static VALUE HttpParser_add_parse(VALUE self, VALUE buffer)
770
+ {
771
+ struct http_parser *hp = data_get(self);
772
+
773
+ Check_Type(buffer, T_STRING);
774
+ rb_str_buf_append(hp->buf, buffer);
775
+
776
+ return HttpParser_parse(self);
777
+ }
778
+
698
779
  /**
699
780
  * Document-method: trailers
700
781
  * call-seq:
@@ -803,71 +884,80 @@ static VALUE HttpParser_env(VALUE self)
803
884
 
804
885
  /**
805
886
  * call-seq:
806
- * parser.filter_body(buf, data) => nil/data
887
+ * parser.filter_body(dst, src) => nil/src
807
888
  *
808
- * Takes a String of +data+, will modify data if dechunking is done.
889
+ * Takes a String of +src+, will modify data if dechunking is done.
809
890
  * Returns +nil+ if there is more data left to process. Returns
810
- * +data+ if body processing is complete. When returning +data+,
811
- * it may modify +data+ so the start of the string points to where
891
+ * +src+ if body processing is complete. When returning +src+,
892
+ * it may modify +src+ so the start of the string points to where
812
893
  * the body ended so that trailer processing can begin.
813
894
  *
814
895
  * Raises HttpParserError if there are dechunking errors.
815
- * Basically this is a glorified memcpy(3) that copies +data+
896
+ * Basically this is a glorified memcpy(3) that copies +src+
816
897
  * into +buf+ while filtering it through the dechunker.
817
898
  */
818
- static VALUE HttpParser_filter_body(VALUE self, VALUE buf, VALUE data)
899
+ static VALUE HttpParser_filter_body(VALUE self, VALUE dst, VALUE src)
819
900
  {
820
901
  struct http_parser *hp = data_get(self);
821
- char *dptr;
822
- long dlen;
902
+ char *srcptr;
903
+ long srclen;
823
904
 
824
- dptr = RSTRING_PTR(data);
825
- dlen = RSTRING_LEN(data);
905
+ srcptr = RSTRING_PTR(src);
906
+ srclen = RSTRING_LEN(src);
826
907
 
827
- StringValue(buf);
828
- rb_str_resize(buf, dlen); /* we can never copy more than dlen bytes */
829
- OBJ_TAINT(buf); /* keep weirdo $SAFE users happy */
908
+ StringValue(dst);
830
909
 
831
910
  if (HP_FL_TEST(hp, CHUNKED)) {
832
911
  if (!chunked_eof(hp)) {
912
+ rb_str_modify(dst);
913
+ rb_str_resize(dst, srclen); /* we can never copy more than srclen bytes */
914
+
833
915
  hp->s.dest_offset = 0;
834
- hp->cont = buf;
835
- hp->buf = data;
836
- http_parser_execute(hp, dptr, dlen);
916
+ hp->cont = dst;
917
+ hp->buf = src;
918
+ http_parser_execute(hp, srcptr, srclen);
837
919
  if (hp->cs == http_parser_error)
838
- parser_error("Invalid HTTP format, parsing fails.");
920
+ parser_raise(eHttpParserError, "Invalid HTTP format, parsing fails.");
839
921
 
840
922
  assert(hp->s.dest_offset <= hp->offset &&
841
923
  "destination buffer overflow");
842
- advance_str(data, hp->offset);
843
- rb_str_set_len(buf, hp->s.dest_offset);
924
+ advance_str(src, hp->offset);
925
+ rb_str_set_len(dst, hp->s.dest_offset);
844
926
 
845
- if (RSTRING_LEN(buf) == 0 && chunked_eof(hp)) {
927
+ if (RSTRING_LEN(dst) == 0 && chunked_eof(hp)) {
846
928
  assert(hp->len.chunk == 0 && "chunk at EOF but more to parse");
847
929
  } else {
848
- data = Qnil;
930
+ src = Qnil;
849
931
  }
850
932
  }
851
933
  } else {
852
934
  /* no need to enter the Ragel machine for unchunked transfers */
853
935
  assert(hp->len.content >= 0 && "negative Content-Length");
854
936
  if (hp->len.content > 0) {
855
- long nr = MIN(dlen, hp->len.content);
856
-
857
- hp->buf = data;
858
- memcpy(RSTRING_PTR(buf), dptr, nr);
937
+ long nr = MIN(srclen, hp->len.content);
938
+
939
+ rb_str_modify(dst);
940
+ rb_str_resize(dst, nr);
941
+ /*
942
+ * using rb_str_replace() to avoid memcpy() doesn't help in
943
+ * most cases because a GC-aware programmer will pass an explicit
944
+ * buffer to env["rack.input"].read and reuse the buffer in a loop.
945
+ * This causes copy-on-write behavior to be triggered anyways
946
+ * when the +src+ buffer is modified (when reading off the socket).
947
+ */
948
+ hp->buf = src;
949
+ memcpy(RSTRING_PTR(dst), srcptr, nr);
859
950
  hp->len.content -= nr;
860
951
  if (hp->len.content == 0) {
861
952
  HP_FL_SET(hp, REQEOF);
862
953
  hp->cs = http_parser_first_final;
863
954
  }
864
- advance_str(data, nr);
865
- rb_str_set_len(buf, nr);
866
- data = Qnil;
955
+ advance_str(src, nr);
956
+ src = Qnil;
867
957
  }
868
958
  }
869
959
  hp->offset = 0; /* for trailer parsing */
870
- return data;
960
+ return src;
871
961
  }
872
962
 
873
963
  #define SET_GLOBAL(var,str) do { \
@@ -883,13 +973,19 @@ void Init_unicorn_http(void)
883
973
  cHttpParser = rb_define_class_under(mUnicorn, "HttpParser", rb_cObject);
884
974
  eHttpParserError =
885
975
  rb_define_class_under(mUnicorn, "HttpParserError", rb_eIOError);
976
+ e413 = rb_define_class_under(mUnicorn, "RequestEntityTooLargeError",
977
+ eHttpParserError);
978
+ e414 = rb_define_class_under(mUnicorn, "RequestURITooLongError",
979
+ eHttpParserError);
886
980
 
887
981
  init_globals();
888
982
  rb_define_alloc_func(cHttpParser, HttpParser_alloc);
889
983
  rb_define_method(cHttpParser, "initialize", HttpParser_init, 0);
890
984
  rb_define_method(cHttpParser, "clear", HttpParser_clear, 0);
891
985
  rb_define_method(cHttpParser, "reset", HttpParser_reset, 0);
986
+ rb_define_method(cHttpParser, "dechunk!", HttpParser_dechunk_bang, 0);
892
987
  rb_define_method(cHttpParser, "parse", HttpParser_parse, 0);
988
+ rb_define_method(cHttpParser, "add_parse", HttpParser_add_parse, 1);
893
989
  rb_define_method(cHttpParser, "headers", HttpParser_headers, 2);
894
990
  rb_define_method(cHttpParser, "trailers", HttpParser_headers, 2);
895
991
  rb_define_method(cHttpParser, "filter_body", HttpParser_filter_body, 2);
@@ -924,6 +1020,7 @@ void Init_unicorn_http(void)
924
1020
  rb_define_singleton_method(cHttpParser, "keepalive_requests=", set_ka_req, 1);
925
1021
  rb_define_singleton_method(cHttpParser, "trust_x_forwarded=", set_xftrust, 1);
926
1022
  rb_define_singleton_method(cHttpParser, "trust_x_forwarded?", xftrust, 0);
1023
+ rb_define_singleton_method(cHttpParser, "max_header_len=", set_maxhdrlen, 1);
927
1024
 
928
1025
  init_common_fields();
929
1026
  SET_GLOBAL(g_http_host, "HOST");
@@ -932,6 +1029,7 @@ void Init_unicorn_http(void)
932
1029
  SET_GLOBAL(g_content_length, "CONTENT_LENGTH");
933
1030
  SET_GLOBAL(g_http_connection, "CONNECTION");
934
1031
  id_clear = rb_intern("clear");
1032
+ id_set_backtrace = rb_intern("set_backtrace");
935
1033
  init_unicorn_httpdate();
936
1034
  }
937
1035
  #undef SET_GLOBAL
@@ -8,6 +8,8 @@ require 'logger'
8
8
  # example configuration files. An example config file for use with
9
9
  # nginx is also available at
10
10
  # http://unicorn.bogomips.org/examples/nginx.conf
11
+ #
12
+ # See the link:/TUNING.html document for more information on tuning unicorn.
11
13
  class Unicorn::Configurator
12
14
  include Unicorn
13
15
 
@@ -253,24 +255,50 @@ class Unicorn::Configurator
253
255
  #
254
256
  # [:tcp_nodelay => true or false]
255
257
  #
256
- # Disables Nagle's algorithm on TCP sockets if +true+
258
+ # Disables Nagle's algorithm on TCP sockets if +true+.
259
+ #
260
+ # Setting this to +true+ can make streaming responses in Rails 3.1
261
+ # appear more quickly at the cost of slightly higher bandwidth usage.
262
+ # The effect of this option is most visible if nginx is not used,
263
+ # but nginx remains highly recommended with \Unicorn.
257
264
  #
258
265
  # This has no effect on UNIX sockets.
259
266
  #
260
- # Default: operating system defaults (usually Nagle's algorithm enabled)
267
+ # Default: +true+ (Nagle's algorithm disabled) in \Unicorn,
268
+ # +true+ in Rainbows! This defaulted to +false+ in \Unicorn
269
+ # 3.x
261
270
  #
262
271
  # [:tcp_nopush => true or false]
263
272
  #
264
273
  # Enables/disables TCP_CORK in Linux or TCP_NOPUSH in FreeBSD
265
274
  #
266
- # This is enabled by default as of Unicorn 3.4. This prevents partial
267
- # TCP frames from being sent out and reduces wakeups in nginx if it is
268
- # on a different machine. Since Unicorn is only designed for applications
269
- # that send the response body quickly without keepalive, sockets will
270
- # always be flushed on close to prevent delays.
275
+ # This prevents partial TCP frames from being sent out and reduces
276
+ # wakeups in nginx if it is on a different machine. Since \Unicorn
277
+ # is only designed for applications that send the response body
278
+ # quickly without keepalive, sockets will always be flushed on close
279
+ # to prevent delays.
271
280
  #
272
281
  # This has no effect on UNIX sockets.
273
282
  #
283
+ # Default: +false+
284
+ # This defaulted to +true+ in \Unicorn 3.4 - 3.7
285
+ #
286
+ # [:ipv6only => true or false]
287
+ #
288
+ # This option makes IPv6-capable TCP listeners IPv6-only and unable
289
+ # to receive IPv4 queries on dual-stack systems. A separate IPv4-only
290
+ # listener is required if this is true.
291
+ #
292
+ # This option is only available for Ruby 1.9.2 and later.
293
+ #
294
+ # Enabling this option for the IPv6-only listener and having a
295
+ # separate IPv4 listener is recommended if you wish to support IPv6
296
+ # on the same TCP port. Otherwise, the value of \env[\"REMOTE_ADDR\"]
297
+ # will appear as an ugly IPv4-mapped-IPv6 address for IPv4 clients
298
+ # (e.g ":ffff:10.0.0.1" instead of just "10.0.0.1").
299
+ #
300
+ # Default: Operating-system dependent
301
+ #
274
302
  # [:tries => Integer]
275
303
  #
276
304
  # Times to retry binding a socket if it is already in use
@@ -348,7 +376,7 @@ class Unicorn::Configurator
348
376
  Integer === value or
349
377
  raise ArgumentError, "not an integer: #{key}=#{value.inspect}"
350
378
  end
351
- [ :tcp_nodelay, :tcp_nopush ].each do |key|
379
+ [ :tcp_nodelay, :tcp_nopush, :ipv6only ].each do |key|
352
380
  (value = options[key]).nil? and next
353
381
  TrueClass === value || FalseClass === value or
354
382
  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.4.0
12
- UNICORN_VERSION = "3.4.0"
11
+ # The current version of Unicorn, currently 4.0.0
12
+ UNICORN_VERSION = "4.0.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)