unicorn 5.0.1 → 6.1.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +5 -5
- data/.manifest +11 -5
- data/.olddoc.yml +16 -6
- data/Application_Timeouts +4 -4
- data/CONTRIBUTORS +6 -2
- data/Documentation/.gitignore +1 -3
- data/Documentation/unicorn.1 +222 -0
- data/Documentation/unicorn_rails.1 +207 -0
- data/FAQ +1 -1
- data/GIT-VERSION-FILE +1 -1
- data/GIT-VERSION-GEN +1 -1
- data/GNUmakefile +118 -58
- data/HACKING +2 -10
- data/ISSUES +40 -35
- data/KNOWN_ISSUES +2 -2
- data/LATEST +23 -28
- data/LICENSE +2 -2
- data/Links +13 -11
- data/NEWS +612 -0
- data/README +30 -29
- data/SIGNALS +1 -1
- data/Sandbox +8 -7
- data/TODO +0 -2
- data/TUNING +19 -1
- data/archive/slrnpull.conf +1 -1
- data/bin/unicorn +3 -1
- data/bin/unicorn_rails +2 -2
- data/examples/big_app_gc.rb +1 -1
- data/examples/init.sh +36 -8
- data/examples/logrotate.conf +17 -2
- data/examples/nginx.conf +4 -3
- data/examples/unicorn.conf.minimal.rb +2 -2
- data/examples/unicorn.conf.rb +2 -2
- data/examples/unicorn@.service +14 -0
- data/ext/unicorn_http/c_util.h +5 -13
- data/ext/unicorn_http/common_field_optimization.h +22 -5
- data/ext/unicorn_http/epollexclusive.h +124 -0
- data/ext/unicorn_http/ext_help.h +0 -44
- data/ext/unicorn_http/extconf.rb +32 -6
- data/ext/unicorn_http/global_variables.h +2 -2
- data/ext/unicorn_http/httpdate.c +2 -1
- data/ext/unicorn_http/unicorn_http.c +853 -498
- data/ext/unicorn_http/unicorn_http.rl +86 -30
- data/ext/unicorn_http/unicorn_http_common.rl +1 -1
- data/lib/unicorn/configurator.rb +93 -13
- data/lib/unicorn/http_request.rb +101 -11
- data/lib/unicorn/http_response.rb +8 -4
- data/lib/unicorn/http_server.rb +141 -72
- data/lib/unicorn/launcher.rb +1 -1
- data/lib/unicorn/oob_gc.rb +6 -6
- data/lib/unicorn/select_waiter.rb +6 -0
- data/lib/unicorn/socket_helper.rb +23 -7
- data/lib/unicorn/stream_input.rb +5 -4
- data/lib/unicorn/tee_input.rb +8 -10
- data/lib/unicorn/tmpio.rb +8 -2
- data/lib/unicorn/util.rb +3 -3
- data/lib/unicorn/version.rb +1 -1
- data/lib/unicorn/worker.rb +33 -8
- data/lib/unicorn.rb +55 -29
- data/man/man1/unicorn.1 +120 -118
- data/man/man1/unicorn_rails.1 +106 -107
- data/t/GNUmakefile +3 -72
- data/t/README +4 -4
- data/t/t0011-active-unix-socket.sh +1 -1
- data/t/t0012-reload-empty-config.sh +2 -1
- data/t/t0301-no-default-middleware-ignored-in-config.sh +25 -0
- data/t/t0301.ru +13 -0
- data/t/test-lib.sh +4 -3
- data/test/benchmark/README +14 -4
- data/test/benchmark/ddstream.ru +50 -0
- data/test/benchmark/readinput.ru +40 -0
- data/test/benchmark/uconnect.perl +66 -0
- data/test/exec/test_exec.rb +26 -24
- data/test/test_helper.rb +38 -30
- data/test/unit/test_ccc.rb +91 -0
- data/test/unit/test_droplet.rb +1 -1
- data/test/unit/test_http_parser.rb +46 -16
- data/test/unit/test_http_parser_ng.rb +81 -0
- data/test/unit/test_request.rb +10 -10
- data/test/unit/test_server.rb +86 -12
- data/test/unit/test_signals.rb +8 -8
- data/test/unit/test_socket_helper.rb +13 -9
- data/test/unit/test_upload.rb +9 -14
- data/test/unit/test_util.rb +31 -5
- data/test/unit/test_waiter.rb +34 -0
- data/unicorn.gemspec +21 -22
- metadata +21 -28
- data/Documentation/GNUmakefile +0 -30
- data/Documentation/unicorn.1.txt +0 -188
- data/Documentation/unicorn_rails.1.txt +0 -175
- data/t/hijack.ru +0 -43
- data/t/t0200-rack-hijack.sh +0 -30
@@ -12,6 +12,7 @@
|
|
12
12
|
#include "common_field_optimization.h"
|
13
13
|
#include "global_variables.h"
|
14
14
|
#include "c_util.h"
|
15
|
+
#include "epollexclusive.h"
|
15
16
|
|
16
17
|
void init_unicorn_httpdate(void);
|
17
18
|
|
@@ -26,6 +27,7 @@ void init_unicorn_httpdate(void);
|
|
26
27
|
#define UH_FL_HASHEADER 0x100
|
27
28
|
#define UH_FL_TO_CLEAR 0x200
|
28
29
|
#define UH_FL_RESSTART 0x400 /* for check_client_connection */
|
30
|
+
#define UH_FL_HIJACK 0x800
|
29
31
|
|
30
32
|
/* all of these flags need to be set for keepalive to be supported */
|
31
33
|
#define UH_FL_KEEPALIVE (UH_FL_KAVERSION | UH_FL_REQEOF | UH_FL_HASHEADER)
|
@@ -61,19 +63,8 @@ struct http_parser {
|
|
61
63
|
} len;
|
62
64
|
};
|
63
65
|
|
64
|
-
static ID id_set_backtrace;
|
65
|
-
|
66
|
-
#ifdef HAVE_RB_HASH_CLEAR /* Ruby >= 2.0 */
|
67
|
-
# define my_hash_clear(h) (void)rb_hash_clear(h)
|
68
|
-
#else /* !HAVE_RB_HASH_CLEAR - Ruby <= 1.9.3 */
|
69
|
-
|
70
|
-
static ID id_clear;
|
71
|
-
|
72
|
-
static void my_hash_clear(VALUE h)
|
73
|
-
{
|
74
|
-
rb_funcall(h, id_clear, 0);
|
75
|
-
}
|
76
|
-
#endif /* HAVE_RB_HASH_CLEAR */
|
66
|
+
static ID id_set_backtrace, id_is_chunked_p;
|
67
|
+
static VALUE cHttpParser;
|
77
68
|
|
78
69
|
static void finalize_header(struct http_parser *hp);
|
79
70
|
|
@@ -219,6 +210,19 @@ static void write_cont_value(struct http_parser *hp,
|
|
219
210
|
rb_str_buf_cat(hp->cont, vptr, end + 1);
|
220
211
|
}
|
221
212
|
|
213
|
+
static int is_chunked(VALUE v)
|
214
|
+
{
|
215
|
+
/* common case first */
|
216
|
+
if (STR_CSTR_CASE_EQ(v, "chunked"))
|
217
|
+
return 1;
|
218
|
+
|
219
|
+
/*
|
220
|
+
* call Ruby function in unicorn/http_request.rb to deal with unlikely
|
221
|
+
* comma-delimited case
|
222
|
+
*/
|
223
|
+
return rb_funcall(cHttpParser, id_is_chunked_p, 1, v) != Qfalse;
|
224
|
+
}
|
225
|
+
|
222
226
|
static void write_value(struct http_parser *hp,
|
223
227
|
const char *buffer, const char *p)
|
224
228
|
{
|
@@ -245,7 +249,9 @@ static void write_value(struct http_parser *hp,
|
|
245
249
|
f = uncommon_field(field, flen);
|
246
250
|
} else if (f == g_http_connection) {
|
247
251
|
hp_keepalive_connection(hp, v);
|
248
|
-
} else if (f == g_content_length) {
|
252
|
+
} else if (f == g_content_length && !HP_FL_TEST(hp, CHUNKED)) {
|
253
|
+
if (hp->len.content)
|
254
|
+
parser_raise(eHttpParserError, "Content-Length already set");
|
249
255
|
hp->len.content = parse_length(RSTRING_PTR(v), RSTRING_LEN(v));
|
250
256
|
if (hp->len.content < 0)
|
251
257
|
parser_raise(eHttpParserError, "invalid Content-Length");
|
@@ -253,9 +259,30 @@ static void write_value(struct http_parser *hp,
|
|
253
259
|
HP_FL_SET(hp, HASBODY);
|
254
260
|
hp_invalid_if_trailer(hp);
|
255
261
|
} else if (f == g_http_transfer_encoding) {
|
256
|
-
if (
|
262
|
+
if (is_chunked(v)) {
|
263
|
+
if (HP_FL_TEST(hp, CHUNKED))
|
264
|
+
/*
|
265
|
+
* RFC 7230 3.3.1:
|
266
|
+
* A sender MUST NOT apply chunked more than once to a message body
|
267
|
+
* (i.e., chunking an already chunked message is not allowed).
|
268
|
+
*/
|
269
|
+
parser_raise(eHttpParserError, "Transfer-Encoding double chunked");
|
270
|
+
|
257
271
|
HP_FL_SET(hp, CHUNKED);
|
258
272
|
HP_FL_SET(hp, HASBODY);
|
273
|
+
|
274
|
+
/* RFC 7230 3.3.3, 3: favor chunked if Content-Length exists */
|
275
|
+
hp->len.content = 0;
|
276
|
+
} else if (HP_FL_TEST(hp, CHUNKED)) {
|
277
|
+
/*
|
278
|
+
* RFC 7230 3.3.3, point 3 states:
|
279
|
+
* If a Transfer-Encoding header field is present in a request and
|
280
|
+
* the chunked transfer coding is not the final encoding, the
|
281
|
+
* message body length cannot be determined reliably; the server
|
282
|
+
* MUST respond with the 400 (Bad Request) status code and then
|
283
|
+
* close the connection.
|
284
|
+
*/
|
285
|
+
parser_raise(eHttpParserError, "invalid Transfer-Encoding");
|
259
286
|
}
|
260
287
|
hp_invalid_if_trailer(hp);
|
261
288
|
} else if (f == g_http_trailer) {
|
@@ -442,11 +469,31 @@ post_exec: /* "_out:" also goes here */
|
|
442
469
|
assert(hp->offset <= len && "offset longer than length");
|
443
470
|
}
|
444
471
|
|
472
|
+
static void hp_mark(void *ptr)
|
473
|
+
{
|
474
|
+
struct http_parser *hp = ptr;
|
475
|
+
|
476
|
+
rb_gc_mark(hp->buf);
|
477
|
+
rb_gc_mark(hp->env);
|
478
|
+
rb_gc_mark(hp->cont);
|
479
|
+
}
|
480
|
+
|
481
|
+
static size_t hp_memsize(const void *ptr)
|
482
|
+
{
|
483
|
+
return sizeof(struct http_parser);
|
484
|
+
}
|
485
|
+
|
486
|
+
static const rb_data_type_t hp_type = {
|
487
|
+
"unicorn_http",
|
488
|
+
{ hp_mark, RUBY_TYPED_DEFAULT_FREE, hp_memsize, /* reserved */ },
|
489
|
+
/* parent, data, [ flags ] */
|
490
|
+
};
|
491
|
+
|
445
492
|
static struct http_parser *data_get(VALUE self)
|
446
493
|
{
|
447
494
|
struct http_parser *hp;
|
448
495
|
|
449
|
-
|
496
|
+
TypedData_Get_Struct(self, struct http_parser, &hp_type, hp);
|
450
497
|
assert(hp && "failed to extract http_parser struct");
|
451
498
|
return hp;
|
452
499
|
}
|
@@ -466,7 +513,7 @@ static void set_url_scheme(VALUE env, VALUE *server_port)
|
|
466
513
|
* and X-Forwarded-Proto handling from this parser? We've had it
|
467
514
|
* forever and nobody has said anything against it, either.
|
468
515
|
* Anyways, please send comments to our public mailing list:
|
469
|
-
* unicorn-public@
|
516
|
+
* unicorn-public@yhbt.net (no HTML mail, no subscription necessary)
|
470
517
|
*/
|
471
518
|
scheme = rb_hash_aref(env, g_http_x_forwarded_ssl);
|
472
519
|
if (!NIL_P(scheme) && STR_CSTR_EQ(scheme, "on")) {
|
@@ -552,21 +599,12 @@ static void finalize_header(struct http_parser *hp)
|
|
552
599
|
rb_hash_aset(hp->env, g_query_string, rb_str_new(NULL, 0));
|
553
600
|
}
|
554
601
|
|
555
|
-
static void hp_mark(void *ptr)
|
556
|
-
{
|
557
|
-
struct http_parser *hp = ptr;
|
558
|
-
|
559
|
-
rb_gc_mark(hp->buf);
|
560
|
-
rb_gc_mark(hp->env);
|
561
|
-
rb_gc_mark(hp->cont);
|
562
|
-
}
|
563
|
-
|
564
602
|
static VALUE HttpParser_alloc(VALUE klass)
|
565
603
|
{
|
566
604
|
struct http_parser *hp;
|
567
|
-
return Data_Make_Struct(klass, struct http_parser, hp_mark, -1, hp);
|
568
|
-
}
|
569
605
|
|
606
|
+
return TypedData_Make_Struct(klass, struct http_parser, &hp_type, hp);
|
607
|
+
}
|
570
608
|
|
571
609
|
/**
|
572
610
|
* call-seq:
|
@@ -596,8 +634,12 @@ static VALUE HttpParser_clear(VALUE self)
|
|
596
634
|
{
|
597
635
|
struct http_parser *hp = data_get(self);
|
598
636
|
|
637
|
+
/* we can't safely reuse .buf and .env if hijacked */
|
638
|
+
if (HP_FL_TEST(hp, HIJACK))
|
639
|
+
return HttpParser_init(self);
|
640
|
+
|
599
641
|
http_parser_init(hp);
|
600
|
-
|
642
|
+
rb_hash_clear(hp->env);
|
601
643
|
|
602
644
|
return self;
|
603
645
|
}
|
@@ -802,6 +844,15 @@ static VALUE HttpParser_env(VALUE self)
|
|
802
844
|
return data_get(self)->env;
|
803
845
|
}
|
804
846
|
|
847
|
+
static VALUE HttpParser_hijacked_bang(VALUE self)
|
848
|
+
{
|
849
|
+
struct http_parser *hp = data_get(self);
|
850
|
+
|
851
|
+
HP_FL_SET(hp, HIJACK);
|
852
|
+
|
853
|
+
return self;
|
854
|
+
}
|
855
|
+
|
805
856
|
/**
|
806
857
|
* call-seq:
|
807
858
|
* parser.filter_body(dst, src) => nil/src
|
@@ -906,7 +957,7 @@ static VALUE HttpParser_rssget(VALUE self)
|
|
906
957
|
|
907
958
|
void Init_unicorn_http(void)
|
908
959
|
{
|
909
|
-
VALUE mUnicorn
|
960
|
+
VALUE mUnicorn;
|
910
961
|
|
911
962
|
mUnicorn = rb_define_module("Unicorn");
|
912
963
|
cHttpParser = rb_define_class_under(mUnicorn, "HttpParser", rb_cObject);
|
@@ -917,6 +968,7 @@ void Init_unicorn_http(void)
|
|
917
968
|
e414 = rb_define_class_under(mUnicorn, "RequestURITooLongError",
|
918
969
|
eHttpParserError);
|
919
970
|
|
971
|
+
id_uminus = rb_intern("-@");
|
920
972
|
init_globals();
|
921
973
|
rb_define_alloc_func(cHttpParser, HttpParser_alloc);
|
922
974
|
rb_define_method(cHttpParser, "initialize", HttpParser_init, 0);
|
@@ -933,6 +985,7 @@ void Init_unicorn_http(void)
|
|
933
985
|
rb_define_method(cHttpParser, "next?", HttpParser_next, 0);
|
934
986
|
rb_define_method(cHttpParser, "buf", HttpParser_buf, 0);
|
935
987
|
rb_define_method(cHttpParser, "env", HttpParser_env, 0);
|
988
|
+
rb_define_method(cHttpParser, "hijacked!", HttpParser_hijacked_bang, 0);
|
936
989
|
rb_define_method(cHttpParser, "response_start_sent=", HttpParser_rssset, 1);
|
937
990
|
rb_define_method(cHttpParser, "response_start_sent", HttpParser_rssget, 0);
|
938
991
|
|
@@ -965,5 +1018,8 @@ void Init_unicorn_http(void)
|
|
965
1018
|
#ifndef HAVE_RB_HASH_CLEAR
|
966
1019
|
id_clear = rb_intern("clear");
|
967
1020
|
#endif
|
1021
|
+
id_is_chunked_p = rb_intern("is_chunked?");
|
1022
|
+
|
1023
|
+
init_epollexclusive(mUnicorn);
|
968
1024
|
}
|
969
1025
|
#undef SET_GLOBAL
|
data/lib/unicorn/configurator.rb
CHANGED
@@ -3,11 +3,11 @@ require 'logger'
|
|
3
3
|
|
4
4
|
# Implements a simple DSL for configuring a unicorn server.
|
5
5
|
#
|
6
|
-
# See
|
7
|
-
#
|
6
|
+
# See https://yhbt.net/unicorn/examples/unicorn.conf.rb and
|
7
|
+
# https://yhbt.net/unicorn/examples/unicorn.conf.minimal.rb
|
8
8
|
# example configuration files. An example config file for use with
|
9
9
|
# nginx is also available at
|
10
|
-
#
|
10
|
+
# https://yhbt.net/unicorn/examples/nginx.conf
|
11
11
|
#
|
12
12
|
# See the link:/TUNING.html document for more information on tuning unicorn.
|
13
13
|
class Unicorn::Configurator
|
@@ -41,10 +41,23 @@ class Unicorn::Configurator
|
|
41
41
|
:before_exec => lambda { |server|
|
42
42
|
server.logger.info("forked child re-executing...")
|
43
43
|
},
|
44
|
+
:after_worker_exit => lambda { |server, worker, status|
|
45
|
+
m = "reaped #{status.inspect} worker=#{worker.nr rescue 'unknown'}"
|
46
|
+
if status.success?
|
47
|
+
server.logger.info(m)
|
48
|
+
else
|
49
|
+
server.logger.error(m)
|
50
|
+
end
|
51
|
+
},
|
52
|
+
:after_worker_ready => lambda { |server, worker|
|
53
|
+
server.logger.info("worker=#{worker.nr} ready")
|
54
|
+
},
|
44
55
|
:pid => nil,
|
56
|
+
:early_hints => false,
|
57
|
+
:worker_exec => false,
|
45
58
|
:preload_app => false,
|
46
59
|
:check_client_connection => false,
|
47
|
-
:rewindable_input => true,
|
60
|
+
:rewindable_input => true,
|
48
61
|
:client_body_buffer_size => Unicorn::Const::MAX_BODY,
|
49
62
|
}
|
50
63
|
#:startdoc:
|
@@ -76,6 +89,9 @@ class Unicorn::Configurator
|
|
76
89
|
RACKUP[:set_listener] and
|
77
90
|
set[:listeners] << "#{RACKUP[:host]}:#{RACKUP[:port]}"
|
78
91
|
|
92
|
+
RACKUP[:no_default_middleware] and
|
93
|
+
set[:default_middleware] = false
|
94
|
+
|
79
95
|
# unicorn_rails creates dirs here after working_directory is bound
|
80
96
|
after_reload.call if after_reload
|
81
97
|
|
@@ -151,6 +167,38 @@ class Unicorn::Configurator
|
|
151
167
|
set_hook(:after_fork, block_given? ? block : args[0])
|
152
168
|
end
|
153
169
|
|
170
|
+
# sets after_worker_exit hook to a given block. This block will be called
|
171
|
+
# by the master process after a worker exits:
|
172
|
+
#
|
173
|
+
# after_worker_exit do |server,worker,status|
|
174
|
+
# # status is a Process::Status instance for the exited worker process
|
175
|
+
# unless status.success?
|
176
|
+
# server.logger.error("worker process failure: #{status.inspect}")
|
177
|
+
# end
|
178
|
+
# end
|
179
|
+
#
|
180
|
+
# after_worker_exit is only available in unicorn 5.3.0+
|
181
|
+
def after_worker_exit(*args, &block)
|
182
|
+
set_hook(:after_worker_exit, block_given? ? block : args[0], 3)
|
183
|
+
end
|
184
|
+
|
185
|
+
# sets after_worker_ready hook to a given block. This block will be called
|
186
|
+
# by a worker process after it has been fully loaded, directly before it
|
187
|
+
# starts responding to requests:
|
188
|
+
#
|
189
|
+
# after_worker_ready do |server,worker|
|
190
|
+
# server.logger.info("worker #{worker.nr} ready, dropping privileges")
|
191
|
+
# worker.user('username', 'groupname')
|
192
|
+
# end
|
193
|
+
#
|
194
|
+
# Do not use Configurator#user if you rely on changing users in the
|
195
|
+
# after_worker_ready hook.
|
196
|
+
#
|
197
|
+
# after_worker_ready is only available in unicorn 5.3.0+
|
198
|
+
def after_worker_ready(*args, &block)
|
199
|
+
set_hook(:after_worker_ready, block_given? ? block : args[0])
|
200
|
+
end
|
201
|
+
|
154
202
|
# sets before_fork got be a given Proc object. This Proc
|
155
203
|
# object will be called by the master process before forking
|
156
204
|
# each worker.
|
@@ -181,8 +229,6 @@ class Unicorn::Configurator
|
|
181
229
|
# to have nginx always retry backends that may have had workers
|
182
230
|
# SIGKILL-ed due to timeouts.
|
183
231
|
#
|
184
|
-
# # See http://wiki.nginx.org/NginxHttpUpstreamModule for more details
|
185
|
-
# # on nginx upstream configuration:
|
186
232
|
# upstream unicorn_backend {
|
187
233
|
# # for UNIX domain socket setups:
|
188
234
|
# server unix:/path/to/.unicorn.sock fail_timeout=0;
|
@@ -192,6 +238,9 @@ class Unicorn::Configurator
|
|
192
238
|
# server 192.168.0.8:8080 fail_timeout=0;
|
193
239
|
# server 192.168.0.9:8080 fail_timeout=0;
|
194
240
|
# }
|
241
|
+
#
|
242
|
+
# See https://nginx.org/en/docs/http/ngx_http_upstream_module.html
|
243
|
+
# for more details on nginx upstream configuration.
|
195
244
|
def timeout(seconds)
|
196
245
|
set_int(:timeout, seconds, 3)
|
197
246
|
# POSIX says 31 days is the smallest allowed maximum timeout for select()
|
@@ -199,6 +248,17 @@ class Unicorn::Configurator
|
|
199
248
|
set[:timeout] = seconds > max ? max : seconds
|
200
249
|
end
|
201
250
|
|
251
|
+
# Whether to exec in each worker process after forking. This changes the
|
252
|
+
# memory layout of each worker process, which is a security feature designed
|
253
|
+
# to defeat possible address space discovery attacks. Note that using
|
254
|
+
# worker_exec only makes sense if you are not preloading the application,
|
255
|
+
# and will result in higher memory usage.
|
256
|
+
#
|
257
|
+
# worker_exec is only available in unicorn 5.3.0+
|
258
|
+
def worker_exec(bool)
|
259
|
+
set_bool(:worker_exec, bool)
|
260
|
+
end
|
261
|
+
|
202
262
|
# sets the current number of worker_processes to +nr+. Each worker
|
203
263
|
# process will serve exactly one client at a time. You can
|
204
264
|
# increment or decrement this value at runtime by sending SIGTTIN
|
@@ -209,6 +269,23 @@ class Unicorn::Configurator
|
|
209
269
|
set_int(:worker_processes, nr, 1)
|
210
270
|
end
|
211
271
|
|
272
|
+
# sets whether to add default middleware in the development and
|
273
|
+
# deployment RACK_ENVs.
|
274
|
+
#
|
275
|
+
# default_middleware is only available in unicorn 5.5.0+
|
276
|
+
def default_middleware(bool)
|
277
|
+
set_bool(:default_middleware, bool)
|
278
|
+
end
|
279
|
+
|
280
|
+
# sets whether to enable the proposed early hints Rack API.
|
281
|
+
# If enabled, Rails 5.2+ will automatically send a 103 Early Hint
|
282
|
+
# for all the `javascript_include_tag` and `stylesheet_link_tag`
|
283
|
+
# in your response. See: https://api.rubyonrails.org/v5.2/classes/ActionDispatch/Request.html#method-i-send_early_hints
|
284
|
+
# See also https://tools.ietf.org/html/rfc8297
|
285
|
+
def early_hints(bool)
|
286
|
+
set_bool(:early_hints, bool)
|
287
|
+
end
|
288
|
+
|
212
289
|
# sets listeners to the given +addresses+, replacing or augmenting the
|
213
290
|
# current set. This is for the global listener pool shared by all
|
214
291
|
# worker processes. For per-worker listeners, see the after_fork example
|
@@ -465,13 +542,12 @@ class Unicorn::Configurator
|
|
465
542
|
# Disabling rewindability can improve performance by lowering
|
466
543
|
# I/O and memory usage for applications that accept uploads.
|
467
544
|
# Keep in mind that the Rack 1.x spec requires
|
468
|
-
# \env[\"rack.input\"] to be rewindable,
|
469
|
-
#
|
545
|
+
# \env[\"rack.input\"] to be rewindable,
|
546
|
+
# but the Rack 2.x spec does not.
|
470
547
|
#
|
471
|
-
# +rewindable_input+ defaults to +true+
|
472
|
-
#
|
473
|
-
#
|
474
|
-
# (less demanding) spec.
|
548
|
+
# +rewindable_input+ defaults to +true+ for compatibility.
|
549
|
+
# Setting it to +false+ may be safe for applications and
|
550
|
+
# frameworks developed for Rack 2.x and later.
|
475
551
|
def rewindable_input(bool)
|
476
552
|
set_bool(:rewindable_input, bool)
|
477
553
|
end
|
@@ -532,7 +608,7 @@ class Unicorn::Configurator
|
|
532
608
|
# just let chdir raise errors
|
533
609
|
path = File.expand_path(path)
|
534
610
|
if config_file &&
|
535
|
-
config_file
|
611
|
+
! config_file.start_with?('/') &&
|
536
612
|
! File.readable?("#{path}/#{config_file}")
|
537
613
|
raise ArgumentError,
|
538
614
|
"config_file=#{config_file} would not be accessible in" \
|
@@ -547,6 +623,10 @@ class Unicorn::Configurator
|
|
547
623
|
# This switch will occur after calling the after_fork hook, and only
|
548
624
|
# if the Worker#user method is not called in the after_fork hook
|
549
625
|
# +group+ is optional and will not change if unspecified.
|
626
|
+
#
|
627
|
+
# Do not use Configurator#user if you rely on changing users in the
|
628
|
+
# after_worker_ready hook. Instead, you need to call Worker#user
|
629
|
+
# directly in after_worker_ready.
|
550
630
|
def user(user, group = nil)
|
551
631
|
# raises ArgumentError on invalid user/group
|
552
632
|
Etc.getpwnam(user)
|
data/lib/unicorn/http_request.rb
CHANGED
@@ -24,12 +24,11 @@ class Unicorn::HttpParser
|
|
24
24
|
NULL_IO = StringIO.new("")
|
25
25
|
|
26
26
|
# :stopdoc:
|
27
|
-
|
28
|
-
|
29
|
-
# 2.2+ optimizes hash assignments when used with literal string keys
|
30
|
-
HTTP_RESPONSE_START = [ 'HTTP', '/1.1 ']
|
27
|
+
HTTP_RESPONSE_START = [ 'HTTP'.freeze, '/1.1 '.freeze ]
|
28
|
+
EMPTY_ARRAY = [].freeze
|
31
29
|
@@input_class = Unicorn::TeeInput
|
32
30
|
@@check_client_connection = false
|
31
|
+
@@tcpi_inspect_ok = Socket.const_defined?(:TCP_INFO)
|
33
32
|
|
34
33
|
def self.input_class
|
35
34
|
@@input_class
|
@@ -63,10 +62,9 @@ class Unicorn::HttpParser
|
|
63
62
|
# This does minimal exception trapping and it is up to the caller
|
64
63
|
# to handle any socket errors (e.g. user aborted upload).
|
65
64
|
def read(socket)
|
66
|
-
clear
|
67
65
|
e = env
|
68
66
|
|
69
|
-
# From
|
67
|
+
# From https://www.ietf.org/rfc/rfc3875:
|
70
68
|
# "Script authors should be aware that the REMOTE_ADDR and
|
71
69
|
# REMOTE_HOST meta-variables (see sections 4.1.8 and 4.1.9)
|
72
70
|
# may not identify the ultimate source of the request. They
|
@@ -83,11 +81,7 @@ class Unicorn::HttpParser
|
|
83
81
|
false until add_parse(socket.kgio_read!(16384))
|
84
82
|
end
|
85
83
|
|
86
|
-
|
87
|
-
if @@check_client_connection && headers?
|
88
|
-
self.response_start_sent = true
|
89
|
-
HTTP_RESPONSE_START.each { |c| socket.write(c) }
|
90
|
-
end
|
84
|
+
check_client_connection(socket) if @@check_client_connection
|
91
85
|
|
92
86
|
e['rack.input'] = 0 == content_length ?
|
93
87
|
NULL_IO : @@input_class.new(socket, self)
|
@@ -102,10 +96,106 @@ class Unicorn::HttpParser
|
|
102
96
|
# for rack.hijack, we respond to this method so no extra allocation
|
103
97
|
# of a proc object
|
104
98
|
def call
|
99
|
+
hijacked!
|
105
100
|
env['rack.hijack_io'] = env['unicorn.socket']
|
106
101
|
end
|
107
102
|
|
108
103
|
def hijacked?
|
109
104
|
env.include?('rack.hijack_io'.freeze)
|
110
105
|
end
|
106
|
+
|
107
|
+
if Raindrops.const_defined?(:TCP_Info)
|
108
|
+
TCPI = Raindrops::TCP_Info.allocate
|
109
|
+
|
110
|
+
def check_client_connection(socket) # :nodoc:
|
111
|
+
if Unicorn::TCPClient === socket
|
112
|
+
# Raindrops::TCP_Info#get!, #state (reads struct tcp_info#tcpi_state)
|
113
|
+
raise Errno::EPIPE, "client closed connection".freeze,
|
114
|
+
EMPTY_ARRAY if closed_state?(TCPI.get!(socket).state)
|
115
|
+
else
|
116
|
+
write_http_header(socket)
|
117
|
+
end
|
118
|
+
end
|
119
|
+
|
120
|
+
if Raindrops.const_defined?(:TCP)
|
121
|
+
# raindrops 0.18.0+ supports FreeBSD + Linux using the same names
|
122
|
+
# Evaluate these hash lookups at load time so we can
|
123
|
+
# generate an opt_case_dispatch instruction
|
124
|
+
eval <<-EOS
|
125
|
+
def closed_state?(state) # :nodoc:
|
126
|
+
case state
|
127
|
+
when #{Raindrops::TCP[:ESTABLISHED]}
|
128
|
+
false
|
129
|
+
when #{Raindrops::TCP.values_at(
|
130
|
+
:CLOSE_WAIT, :TIME_WAIT, :CLOSE, :LAST_ACK, :CLOSING).join(',')}
|
131
|
+
true
|
132
|
+
else
|
133
|
+
false
|
134
|
+
end
|
135
|
+
end
|
136
|
+
EOS
|
137
|
+
else
|
138
|
+
# raindrops before 0.18 only supported TCP_INFO under Linux
|
139
|
+
def closed_state?(state) # :nodoc:
|
140
|
+
case state
|
141
|
+
when 1 # ESTABLISHED
|
142
|
+
false
|
143
|
+
when 8, 6, 7, 9, 11 # CLOSE_WAIT, TIME_WAIT, CLOSE, LAST_ACK, CLOSING
|
144
|
+
true
|
145
|
+
else
|
146
|
+
false
|
147
|
+
end
|
148
|
+
end
|
149
|
+
end
|
150
|
+
else
|
151
|
+
|
152
|
+
# Ruby 2.2+ can show struct tcp_info as a string Socket::Option#inspect.
|
153
|
+
# Not that efficient, but probably still better than doing unnecessary
|
154
|
+
# work after a client gives up.
|
155
|
+
def check_client_connection(socket) # :nodoc:
|
156
|
+
if Unicorn::TCPClient === socket && @@tcpi_inspect_ok
|
157
|
+
opt = socket.getsockopt(Socket::IPPROTO_TCP, Socket::TCP_INFO).inspect
|
158
|
+
if opt =~ /\bstate=(\S+)/
|
159
|
+
raise Errno::EPIPE, "client closed connection".freeze,
|
160
|
+
EMPTY_ARRAY if closed_state_str?($1)
|
161
|
+
else
|
162
|
+
@@tcpi_inspect_ok = false
|
163
|
+
write_http_header(socket)
|
164
|
+
end
|
165
|
+
opt.clear
|
166
|
+
else
|
167
|
+
write_http_header(socket)
|
168
|
+
end
|
169
|
+
end
|
170
|
+
|
171
|
+
def closed_state_str?(state)
|
172
|
+
case state
|
173
|
+
when 'ESTABLISHED'
|
174
|
+
false
|
175
|
+
# not a typo, ruby maps TCP_CLOSE (no 'D') to state=CLOSED (w/ 'D')
|
176
|
+
when 'CLOSE_WAIT', 'TIME_WAIT', 'CLOSED', 'LAST_ACK', 'CLOSING'
|
177
|
+
true
|
178
|
+
else
|
179
|
+
false
|
180
|
+
end
|
181
|
+
end
|
182
|
+
end
|
183
|
+
|
184
|
+
def write_http_header(socket) # :nodoc:
|
185
|
+
if headers?
|
186
|
+
self.response_start_sent = true
|
187
|
+
HTTP_RESPONSE_START.each { |c| socket.write(c) }
|
188
|
+
end
|
189
|
+
end
|
190
|
+
|
191
|
+
# called by ext/unicorn_http/unicorn_http.rl via rb_funcall
|
192
|
+
def self.is_chunked?(v) # :nodoc:
|
193
|
+
vals = v.split(/[ \t]*,[ \t]*/).map!(&:downcase)
|
194
|
+
if vals.pop == 'chunked'.freeze
|
195
|
+
return true unless vals.include?('chunked'.freeze)
|
196
|
+
raise Unicorn::HttpParserError, 'double chunked', []
|
197
|
+
end
|
198
|
+
return false unless vals.include?('chunked'.freeze)
|
199
|
+
raise Unicorn::HttpParserError, 'chunked not last', []
|
200
|
+
end
|
111
201
|
end
|
@@ -10,21 +10,24 @@
|
|
10
10
|
# is the job of Rack, with the exception of the "Date" and "Status" header.
|
11
11
|
module Unicorn::HttpResponse
|
12
12
|
|
13
|
+
STATUS_CODES = defined?(Rack::Utils::HTTP_STATUS_CODES) ?
|
14
|
+
Rack::Utils::HTTP_STATUS_CODES : {}
|
15
|
+
|
13
16
|
# internal API, code will always be common-enough-for-even-old-Rack
|
14
17
|
def err_response(code, response_start_sent)
|
15
18
|
"#{response_start_sent ? '' : 'HTTP/1.1 '}" \
|
16
|
-
"#{code} #{
|
19
|
+
"#{code} #{STATUS_CODES[code]}\r\n\r\n"
|
17
20
|
end
|
18
21
|
|
19
22
|
# writes the rack_response to socket as an HTTP response
|
20
23
|
def http_response_write(socket, status, headers, body,
|
21
|
-
|
24
|
+
req = Unicorn::HttpRequest.new)
|
22
25
|
hijack = nil
|
23
26
|
|
24
27
|
if headers
|
25
28
|
code = status.to_i
|
26
|
-
msg =
|
27
|
-
start = response_start_sent ? ''.freeze : 'HTTP/1.1 '.freeze
|
29
|
+
msg = STATUS_CODES[code]
|
30
|
+
start = req.response_start_sent ? ''.freeze : 'HTTP/1.1 '.freeze
|
28
31
|
buf = "#{start}#{msg ? %Q(#{code} #{msg}) : status}\r\n" \
|
29
32
|
"Date: #{httpdate}\r\n" \
|
30
33
|
"Connection: close\r\n"
|
@@ -49,6 +52,7 @@ module Unicorn::HttpResponse
|
|
49
52
|
end
|
50
53
|
|
51
54
|
if hijack
|
55
|
+
req.hijacked!
|
52
56
|
hijack.call(socket)
|
53
57
|
else
|
54
58
|
body.each { |chunk| socket.write(chunk) }
|