unicorn 3.6.0 → 4.0.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/.manifest +13 -0
- data/ChangeLog +783 -1
- data/DESIGN +0 -8
- data/Documentation/GNUmakefile +1 -1
- data/GIT-VERSION-FILE +1 -1
- data/GIT-VERSION-GEN +1 -1
- data/GNUmakefile +2 -2
- data/HACKING +11 -0
- data/KNOWN_ISSUES +2 -2
- data/LATEST +24 -24
- data/Links +53 -0
- data/NEWS +66 -0
- data/PHILOSOPHY +49 -49
- data/Sandbox +13 -4
- data/TODO +0 -2
- data/TUNING +31 -9
- data/bin/unicorn +2 -1
- data/bin/unicorn_rails +2 -1
- data/examples/big_app_gc.rb +2 -33
- data/examples/nginx.conf +17 -4
- 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.c +357 -259
- data/ext/unicorn_http/unicorn_http.rl +148 -50
- data/lib/unicorn/configurator.rb +36 -8
- data/lib/unicorn/const.rb +5 -3
- data/lib/unicorn/http_request.rb +1 -3
- data/lib/unicorn/http_server.rb +82 -95
- data/lib/unicorn/oob_gc.rb +61 -50
- data/lib/unicorn/socket_helper.rb +23 -8
- data/lib/unicorn/worker.rb +45 -4
- data/lib/unicorn.rb +8 -6
- data/script/isolate_for_tests +4 -2
- data/t/broken-app.ru +12 -0
- data/t/heartbeat-timeout.ru +12 -0
- data/t/oob_gc.ru +21 -0
- data/t/oob_gc_path.ru +21 -0
- data/t/t0001-reload-bad-config.sh +1 -0
- data/t/t0002-parser-error.sh +64 -1
- data/t/t0004-heartbeat-timeout.sh +69 -0
- data/t/t0009-broken-app.sh +56 -0
- data/t/t0019-max_header_len.sh +49 -0
- data/t/t0020-at_exit-handler.sh +49 -0
- data/t/t9001-oob_gc.sh +47 -0
- data/t/t9002-oob_gc-path.sh +75 -0
- data/test/benchmark/stack.ru +8 -0
- data/test/unit/test_droplet.rb +28 -0
- data/test/unit/test_http_parser.rb +60 -4
- data/test/unit/test_http_parser_ng.rb +54 -0
- data/test/unit/test_response.rb +1 -1
- data/test/unit/test_server.rb +1 -1
- data/test/unit/test_signals.rb +1 -1
- data/test/unit/test_socket_helper.rb +8 -0
- data/test/unit/test_upload.rb +1 -1
- data/unicorn.gemspec +3 -2
- 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
|
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);
|
@@ -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
|
-
|
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
|
-
|
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(
|
887
|
+
* parser.filter_body(dst, src) => nil/src
|
807
888
|
*
|
808
|
-
* Takes a String of +
|
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
|
-
* +
|
811
|
-
* it may modify +
|
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 +
|
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
|
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 *
|
822
|
-
long
|
902
|
+
char *srcptr;
|
903
|
+
long srclen;
|
823
904
|
|
824
|
-
|
825
|
-
|
905
|
+
srcptr = RSTRING_PTR(src);
|
906
|
+
srclen = RSTRING_LEN(src);
|
826
907
|
|
827
|
-
StringValue(
|
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 =
|
835
|
-
hp->buf =
|
836
|
-
http_parser_execute(hp,
|
916
|
+
hp->cont = dst;
|
917
|
+
hp->buf = src;
|
918
|
+
http_parser_execute(hp, srcptr, srclen);
|
837
919
|
if (hp->cs == http_parser_error)
|
838
|
-
|
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(
|
843
|
-
rb_str_set_len(
|
924
|
+
advance_str(src, hp->offset);
|
925
|
+
rb_str_set_len(dst, hp->s.dest_offset);
|
844
926
|
|
845
|
-
if (RSTRING_LEN(
|
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
|
-
|
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(
|
856
|
-
|
857
|
-
|
858
|
-
|
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(
|
865
|
-
|
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
|
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
|
data/lib/unicorn/configurator.rb
CHANGED
@@ -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:
|
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
|
267
|
-
#
|
268
|
-
#
|
269
|
-
#
|
270
|
-
#
|
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
|
12
|
-
UNICORN_VERSION = "
|
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
|
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)
|