unicorn 5.0.1 → 6.1.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.
- 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) }
|