unicorn 5.3.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 +10 -5
- data/.olddoc.yml +15 -7
- 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 +117 -57
- data/HACKING +2 -9
- data/ISSUES +33 -32
- data/KNOWN_ISSUES +2 -2
- data/LATEST +16 -95
- data/LICENSE +2 -2
- data/Links +13 -11
- data/NEWS +239 -0
- data/README +27 -14
- data/SIGNALS +1 -1
- data/Sandbox +5 -5
- 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/logrotate.conf +3 -3
- 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 +7 -0
- data/ext/unicorn_http/c_util.h +5 -13
- data/ext/unicorn_http/common_field_optimization.h +23 -6
- data/ext/unicorn_http/epollexclusive.h +124 -0
- data/ext/unicorn_http/ext_help.h +0 -24
- data/ext/unicorn_http/extconf.rb +32 -6
- data/ext/unicorn_http/global_variables.h +3 -3
- data/ext/unicorn_http/httpdate.c +3 -2
- data/ext/unicorn_http/unicorn_http.c +277 -237
- data/ext/unicorn_http/unicorn_http.rl +67 -27
- data/lib/unicorn/configurator.rb +26 -5
- data/lib/unicorn/http_request.rb +13 -3
- data/lib/unicorn/http_response.rb +3 -2
- data/lib/unicorn/http_server.rb +76 -51
- data/lib/unicorn/launcher.rb +1 -1
- data/lib/unicorn/oob_gc.rb +5 -5
- data/lib/unicorn/select_waiter.rb +6 -0
- data/lib/unicorn/socket_helper.rb +4 -3
- 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 +16 -2
- data/lib/unicorn.rb +25 -10
- data/man/man1/unicorn.1 +88 -85
- data/man/man1/unicorn_rails.1 +79 -81
- data/t/GNUmakefile +3 -72
- data/t/README +4 -4
- data/t/t0301-no-default-middleware-ignored-in-config.sh +25 -0
- data/t/t0301.ru +13 -0
- data/t/test-lib.sh +2 -1
- 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 +20 -19
- data/test/test_helper.rb +38 -30
- data/test/unit/test_ccc.rb +5 -4
- data/test/unit/test_droplet.rb +1 -1
- data/test/unit/test_http_parser.rb +16 -0
- 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 +5 -5
- data/test/unit/test_upload.rb +9 -14
- data/test/unit/test_util.rb +29 -3
- data/test/unit/test_waiter.rb +34 -0
- data/unicorn.gemspec +8 -7
- metadata +19 -13
- data/Documentation/GNUmakefile +0 -30
- data/Documentation/unicorn.1.txt +0 -187
- data/Documentation/unicorn_rails.1.txt +0 -175
- data/t/hijack.ru +0 -43
- data/t/t0200-rack-hijack.sh +0 -30
@@ -12,8 +12,9 @@
|
|
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
|
-
void init_unicorn_httpdate(
|
17
|
+
void init_unicorn_httpdate(void);
|
17
18
|
|
18
19
|
#define UH_FL_CHUNKED 0x1
|
19
20
|
#define UH_FL_HASBODY 0x2
|
@@ -26,6 +27,7 @@ void init_unicorn_httpdate(VALUE mark_ary);
|
|
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) {
|
@@ -486,7 +513,7 @@ static void set_url_scheme(VALUE env, VALUE *server_port)
|
|
486
513
|
* and X-Forwarded-Proto handling from this parser? We've had it
|
487
514
|
* forever and nobody has said anything against it, either.
|
488
515
|
* Anyways, please send comments to our public mailing list:
|
489
|
-
* unicorn-public@
|
516
|
+
* unicorn-public@yhbt.net (no HTML mail, no subscription necessary)
|
490
517
|
*/
|
491
518
|
scheme = rb_hash_aref(env, g_http_x_forwarded_ssl);
|
492
519
|
if (!NIL_P(scheme) && STR_CSTR_EQ(scheme, "on")) {
|
@@ -607,8 +634,12 @@ static VALUE HttpParser_clear(VALUE self)
|
|
607
634
|
{
|
608
635
|
struct http_parser *hp = data_get(self);
|
609
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
|
+
|
610
641
|
http_parser_init(hp);
|
611
|
-
|
642
|
+
rb_hash_clear(hp->env);
|
612
643
|
|
613
644
|
return self;
|
614
645
|
}
|
@@ -813,6 +844,15 @@ static VALUE HttpParser_env(VALUE self)
|
|
813
844
|
return data_get(self)->env;
|
814
845
|
}
|
815
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
|
+
|
816
856
|
/**
|
817
857
|
* call-seq:
|
818
858
|
* parser.filter_body(dst, src) => nil/src
|
@@ -917,11 +957,8 @@ static VALUE HttpParser_rssget(VALUE self)
|
|
917
957
|
|
918
958
|
void Init_unicorn_http(void)
|
919
959
|
{
|
920
|
-
|
921
|
-
VALUE mUnicorn, cHttpParser;
|
960
|
+
VALUE mUnicorn;
|
922
961
|
|
923
|
-
mark_ary = rb_ary_new();
|
924
|
-
rb_global_variable(&mark_ary);
|
925
962
|
mUnicorn = rb_define_module("Unicorn");
|
926
963
|
cHttpParser = rb_define_class_under(mUnicorn, "HttpParser", rb_cObject);
|
927
964
|
eHttpParserError =
|
@@ -931,7 +968,8 @@ void Init_unicorn_http(void)
|
|
931
968
|
e414 = rb_define_class_under(mUnicorn, "RequestURITooLongError",
|
932
969
|
eHttpParserError);
|
933
970
|
|
934
|
-
|
971
|
+
id_uminus = rb_intern("-@");
|
972
|
+
init_globals();
|
935
973
|
rb_define_alloc_func(cHttpParser, HttpParser_alloc);
|
936
974
|
rb_define_method(cHttpParser, "initialize", HttpParser_init, 0);
|
937
975
|
rb_define_method(cHttpParser, "clear", HttpParser_clear, 0);
|
@@ -947,6 +985,7 @@ void Init_unicorn_http(void)
|
|
947
985
|
rb_define_method(cHttpParser, "next?", HttpParser_next, 0);
|
948
986
|
rb_define_method(cHttpParser, "buf", HttpParser_buf, 0);
|
949
987
|
rb_define_method(cHttpParser, "env", HttpParser_env, 0);
|
988
|
+
rb_define_method(cHttpParser, "hijacked!", HttpParser_hijacked_bang, 0);
|
950
989
|
rb_define_method(cHttpParser, "response_start_sent=", HttpParser_rssset, 1);
|
951
990
|
rb_define_method(cHttpParser, "response_start_sent", HttpParser_rssget, 0);
|
952
991
|
|
@@ -967,19 +1006,20 @@ void Init_unicorn_http(void)
|
|
967
1006
|
|
968
1007
|
rb_define_singleton_method(cHttpParser, "max_header_len=", set_maxhdrlen, 1);
|
969
1008
|
|
970
|
-
init_common_fields(
|
1009
|
+
init_common_fields();
|
971
1010
|
SET_GLOBAL(g_http_host, "HOST");
|
972
1011
|
SET_GLOBAL(g_http_trailer, "TRAILER");
|
973
1012
|
SET_GLOBAL(g_http_transfer_encoding, "TRANSFER_ENCODING");
|
974
1013
|
SET_GLOBAL(g_content_length, "CONTENT_LENGTH");
|
975
1014
|
SET_GLOBAL(g_http_connection, "CONNECTION");
|
976
1015
|
id_set_backtrace = rb_intern("set_backtrace");
|
977
|
-
init_unicorn_httpdate(
|
978
|
-
|
979
|
-
OBJ_FREEZE(mark_ary);
|
1016
|
+
init_unicorn_httpdate();
|
980
1017
|
|
981
1018
|
#ifndef HAVE_RB_HASH_CLEAR
|
982
1019
|
id_clear = rb_intern("clear");
|
983
1020
|
#endif
|
1021
|
+
id_is_chunked_p = rb_intern("is_chunked?");
|
1022
|
+
|
1023
|
+
init_epollexclusive(mUnicorn);
|
984
1024
|
}
|
985
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 https://
|
7
|
-
# https://
|
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
|
-
# https://
|
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
|
@@ -53,6 +53,7 @@ class Unicorn::Configurator
|
|
53
53
|
server.logger.info("worker=#{worker.nr} ready")
|
54
54
|
},
|
55
55
|
:pid => nil,
|
56
|
+
:early_hints => false,
|
56
57
|
:worker_exec => false,
|
57
58
|
:preload_app => false,
|
58
59
|
:check_client_connection => false,
|
@@ -88,6 +89,9 @@ class Unicorn::Configurator
|
|
88
89
|
RACKUP[:set_listener] and
|
89
90
|
set[:listeners] << "#{RACKUP[:host]}:#{RACKUP[:port]}"
|
90
91
|
|
92
|
+
RACKUP[:no_default_middleware] and
|
93
|
+
set[:default_middleware] = false
|
94
|
+
|
91
95
|
# unicorn_rails creates dirs here after working_directory is bound
|
92
96
|
after_reload.call if after_reload
|
93
97
|
|
@@ -235,7 +239,7 @@ class Unicorn::Configurator
|
|
235
239
|
# server 192.168.0.9:8080 fail_timeout=0;
|
236
240
|
# }
|
237
241
|
#
|
238
|
-
# See
|
242
|
+
# See https://nginx.org/en/docs/http/ngx_http_upstream_module.html
|
239
243
|
# for more details on nginx upstream configuration.
|
240
244
|
def timeout(seconds)
|
241
245
|
set_int(:timeout, seconds, 3)
|
@@ -265,6 +269,23 @@ class Unicorn::Configurator
|
|
265
269
|
set_int(:worker_processes, nr, 1)
|
266
270
|
end
|
267
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
|
+
|
268
289
|
# sets listeners to the given +addresses+, replacing or augmenting the
|
269
290
|
# current set. This is for the global listener pool shared by all
|
270
291
|
# worker processes. For per-worker listeners, see the after_fork example
|
@@ -587,7 +608,7 @@ class Unicorn::Configurator
|
|
587
608
|
# just let chdir raise errors
|
588
609
|
path = File.expand_path(path)
|
589
610
|
if config_file &&
|
590
|
-
config_file
|
611
|
+
! config_file.start_with?('/') &&
|
591
612
|
! File.readable?("#{path}/#{config_file}")
|
592
613
|
raise ArgumentError,
|
593
614
|
"config_file=#{config_file} would not be accessible in" \
|
data/lib/unicorn/http_request.rb
CHANGED
@@ -2,7 +2,6 @@
|
|
2
2
|
# :enddoc:
|
3
3
|
# no stable API here
|
4
4
|
require 'unicorn_http'
|
5
|
-
require 'raindrops'
|
6
5
|
|
7
6
|
# TODO: remove redundant names
|
8
7
|
Unicorn.const_set(:HttpRequest, Unicorn::HttpParser)
|
@@ -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
|
@@ -98,6 +96,7 @@ class Unicorn::HttpParser
|
|
98
96
|
# for rack.hijack, we respond to this method so no extra allocation
|
99
97
|
# of a proc object
|
100
98
|
def call
|
99
|
+
hijacked!
|
101
100
|
env['rack.hijack_io'] = env['unicorn.socket']
|
102
101
|
end
|
103
102
|
|
@@ -188,4 +187,15 @@ class Unicorn::HttpParser
|
|
188
187
|
HTTP_RESPONSE_START.each { |c| socket.write(c) }
|
189
188
|
end
|
190
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
|
191
201
|
end
|
@@ -21,13 +21,13 @@ module Unicorn::HttpResponse
|
|
21
21
|
|
22
22
|
# writes the rack_response to socket as an HTTP response
|
23
23
|
def http_response_write(socket, status, headers, body,
|
24
|
-
|
24
|
+
req = Unicorn::HttpRequest.new)
|
25
25
|
hijack = nil
|
26
26
|
|
27
27
|
if headers
|
28
28
|
code = status.to_i
|
29
29
|
msg = STATUS_CODES[code]
|
30
|
-
start = response_start_sent ? ''.freeze : 'HTTP/1.1 '.freeze
|
30
|
+
start = req.response_start_sent ? ''.freeze : 'HTTP/1.1 '.freeze
|
31
31
|
buf = "#{start}#{msg ? %Q(#{code} #{msg}) : status}\r\n" \
|
32
32
|
"Date: #{httpdate}\r\n" \
|
33
33
|
"Connection: close\r\n"
|
@@ -52,6 +52,7 @@ module Unicorn::HttpResponse
|
|
52
52
|
end
|
53
53
|
|
54
54
|
if hijack
|
55
|
+
req.hijacked!
|
55
56
|
hijack.call(socket)
|
56
57
|
else
|
57
58
|
body.each { |chunk| socket.write(chunk) }
|
data/lib/unicorn/http_server.rb
CHANGED
@@ -6,7 +6,7 @@
|
|
6
6
|
# forked worker children.
|
7
7
|
#
|
8
8
|
# Users do not need to know the internals of this class, but reading the
|
9
|
-
# {source}[https://
|
9
|
+
# {source}[https://yhbt.net/unicorn.git/tree/lib/unicorn/http_server.rb]
|
10
10
|
# is education for programmers wishing to learn how unicorn works.
|
11
11
|
# See Unicorn::Configurator for information on how to configure unicorn.
|
12
12
|
class Unicorn::HttpServer
|
@@ -14,7 +14,8 @@ class Unicorn::HttpServer
|
|
14
14
|
attr_accessor :app, :timeout, :worker_processes,
|
15
15
|
:before_fork, :after_fork, :before_exec,
|
16
16
|
:listener_opts, :preload_app,
|
17
|
-
:orig_app, :config, :ready_pipe, :user
|
17
|
+
:orig_app, :config, :ready_pipe, :user,
|
18
|
+
:default_middleware, :early_hints
|
18
19
|
attr_writer :after_worker_exit, :after_worker_ready, :worker_exec
|
19
20
|
|
20
21
|
attr_reader :pid, :logger
|
@@ -68,8 +69,8 @@ class Unicorn::HttpServer
|
|
68
69
|
# incoming requests on the socket.
|
69
70
|
def initialize(app, options = {})
|
70
71
|
@app = app
|
71
|
-
@request = Unicorn::HttpRequest.new
|
72
72
|
@reexec_pid = 0
|
73
|
+
@default_middleware = true
|
73
74
|
options = options.dup
|
74
75
|
@ready_pipe = options.delete(:ready_pipe)
|
75
76
|
@init_listeners = options[:listeners] ? options[:listeners].dup : []
|
@@ -82,7 +83,7 @@ class Unicorn::HttpServer
|
|
82
83
|
# * The master process never closes or reinitializes this once
|
83
84
|
# initialized. Signal handlers in the master process will write to
|
84
85
|
# it to wake up the master from IO.select in exactly the same manner
|
85
|
-
# djb describes in
|
86
|
+
# djb describes in https://cr.yp.to/docs/selfpipe.html
|
86
87
|
#
|
87
88
|
# * The workers immediately close the pipe they inherit. See the
|
88
89
|
# Unicorn::Worker class for the pipe workers use.
|
@@ -148,7 +149,7 @@ class Unicorn::HttpServer
|
|
148
149
|
def listeners=(listeners)
|
149
150
|
cur_names, dead_names = [], []
|
150
151
|
listener_names.each do |name|
|
151
|
-
if
|
152
|
+
if name.start_with?('/')
|
152
153
|
# mark unlinked sockets as dead so we can rebind them
|
153
154
|
(File.socket?(name) ? cur_names : dead_names) << name
|
154
155
|
else
|
@@ -380,7 +381,7 @@ class Unicorn::HttpServer
|
|
380
381
|
|
381
382
|
# wait for a signal hander to wake us up and then consume the pipe
|
382
383
|
def master_sleep(sec)
|
383
|
-
@self_pipe[0].
|
384
|
+
@self_pipe[0].wait(sec) or return
|
384
385
|
# 11 bytes is the maximum string length which can be embedded within
|
385
386
|
# the Ruby itself and not require a separate malloc (on 32-bit MRI 1.9+).
|
386
387
|
# Most reads are only one byte here and uncommon, so it's not worth a
|
@@ -520,9 +521,6 @@ class Unicorn::HttpServer
|
|
520
521
|
Unicorn::Configurator::RACKUP.clear
|
521
522
|
@ready_pipe = @init_listeners = @before_exec = @before_fork = nil
|
522
523
|
|
523
|
-
# http://blade.nagaokaut.ac.jp/cgi-bin/scat.rb/ruby/ruby-core/36450
|
524
|
-
srand # remove in unicorn 6
|
525
|
-
|
526
524
|
# The OpenSSL PRNG is seeded with only the pid, and apps with frequently
|
527
525
|
# dying workers can recycle pids
|
528
526
|
OpenSSL::Random.seed(rand.to_s) if defined?(OpenSSL::Random)
|
@@ -553,9 +551,9 @@ class Unicorn::HttpServer
|
|
553
551
|
@workers[pid] = worker
|
554
552
|
worker.atfork_parent
|
555
553
|
end
|
556
|
-
|
557
|
-
|
558
|
-
|
554
|
+
rescue => e
|
555
|
+
@logger.error(e) rescue nil
|
556
|
+
exit!
|
559
557
|
end
|
560
558
|
|
561
559
|
def maintain_worker_count
|
@@ -586,13 +584,32 @@ class Unicorn::HttpServer
|
|
586
584
|
client.kgio_trywrite(err_response(code, @request.response_start_sent))
|
587
585
|
end
|
588
586
|
client.close
|
589
|
-
|
587
|
+
rescue
|
588
|
+
end
|
589
|
+
|
590
|
+
def e103_response_write(client, headers)
|
591
|
+
response = if @request.response_start_sent
|
592
|
+
"103 Early Hints\r\n"
|
593
|
+
else
|
594
|
+
"HTTP/1.1 103 Early Hints\r\n"
|
595
|
+
end
|
596
|
+
|
597
|
+
headers.each_pair do |k, vs|
|
598
|
+
next if !vs || vs.empty?
|
599
|
+
values = vs.to_s.split("\n".freeze)
|
600
|
+
values.each do |v|
|
601
|
+
response << "#{k}: #{v}\r\n"
|
602
|
+
end
|
603
|
+
end
|
604
|
+
response << "\r\n".freeze
|
605
|
+
response << "HTTP/1.1 ".freeze if @request.response_start_sent
|
606
|
+
client.write(response)
|
590
607
|
end
|
591
608
|
|
592
609
|
def e100_response_write(client, env)
|
593
610
|
# We use String#freeze to avoid allocations under Ruby 2.1+
|
594
611
|
# Not many users hit this code path, so it's better to reduce the
|
595
|
-
# constant table sizes even for
|
612
|
+
# constant table sizes even for Ruby 2.0 users who'll hit extra
|
596
613
|
# allocations here.
|
597
614
|
client.write(@request.response_start_sent ?
|
598
615
|
"100 Continue\r\n\r\nHTTP/1.1 ".freeze :
|
@@ -603,7 +620,18 @@ class Unicorn::HttpServer
|
|
603
620
|
# once a client is accepted, it is processed in its entirety here
|
604
621
|
# in 3 easy steps: read request, call app, write app response
|
605
622
|
def process_client(client)
|
606
|
-
|
623
|
+
@request = Unicorn::HttpRequest.new
|
624
|
+
env = @request.read(client)
|
625
|
+
|
626
|
+
if early_hints
|
627
|
+
env["rack.early_hints"] = lambda do |headers|
|
628
|
+
e103_response_write(client, headers)
|
629
|
+
end
|
630
|
+
end
|
631
|
+
|
632
|
+
env["rack.after_reply"] = []
|
633
|
+
|
634
|
+
status, headers, body = @app.call(env)
|
607
635
|
|
608
636
|
begin
|
609
637
|
return if @request.hijacked?
|
@@ -614,8 +642,7 @@ class Unicorn::HttpServer
|
|
614
642
|
return if @request.hijacked?
|
615
643
|
end
|
616
644
|
@request.headers? or headers = nil
|
617
|
-
http_response_write(client, status, headers, body,
|
618
|
-
@request.response_start_sent)
|
645
|
+
http_response_write(client, status, headers, body, @request)
|
619
646
|
ensure
|
620
647
|
body.respond_to?(:close) and body.close
|
621
648
|
end
|
@@ -626,6 +653,8 @@ class Unicorn::HttpServer
|
|
626
653
|
end
|
627
654
|
rescue => e
|
628
655
|
handle_error(client, e)
|
656
|
+
ensure
|
657
|
+
env["rack.after_reply"].each(&:call) if env
|
629
658
|
end
|
630
659
|
|
631
660
|
def nuke_listeners!(readers)
|
@@ -656,7 +685,6 @@ class Unicorn::HttpServer
|
|
656
685
|
LISTENERS.each { |sock| sock.close_on_exec = true }
|
657
686
|
|
658
687
|
worker.user(*user) if user.kind_of?(Array) && ! worker.switched
|
659
|
-
self.timeout /= 2.0 # halve it for select()
|
660
688
|
@config = nil
|
661
689
|
build_app! unless preload_app
|
662
690
|
@after_fork = @listener_opts = @orig_app = nil
|
@@ -670,58 +698,55 @@ class Unicorn::HttpServer
|
|
670
698
|
logger.info "worker=#{worker_nr} reopening logs..."
|
671
699
|
Unicorn::Util.reopen_logs
|
672
700
|
logger.info "worker=#{worker_nr} done reopening logs"
|
673
|
-
|
674
|
-
|
675
|
-
|
701
|
+
false
|
702
|
+
rescue => e
|
703
|
+
logger.error(e) rescue nil
|
704
|
+
exit!(77) # EX_NOPERM in sysexits.h
|
705
|
+
end
|
706
|
+
|
707
|
+
def prep_readers(readers)
|
708
|
+
wtr = Unicorn::Waiter.prep_readers(readers)
|
709
|
+
@timeout *= 500 # to milliseconds for epoll, but halved
|
710
|
+
wtr
|
711
|
+
rescue
|
712
|
+
require_relative 'select_waiter'
|
713
|
+
@timeout /= 2.0 # halved for IO.select
|
714
|
+
Unicorn::SelectWaiter.new
|
676
715
|
end
|
677
716
|
|
678
717
|
# runs inside each forked worker, this sits around and waits
|
679
718
|
# for connections and doesn't die until the parent dies (or is
|
680
719
|
# given a INT, QUIT, or TERM signal)
|
681
720
|
def worker_loop(worker)
|
682
|
-
ppid = @master_pid
|
683
721
|
readers = init_worker_process(worker)
|
684
|
-
|
722
|
+
waiter = prep_readers(readers)
|
723
|
+
reopen = false
|
685
724
|
|
686
725
|
# this only works immediately if the master sent us the signal
|
687
726
|
# (which is the normal case)
|
688
|
-
trap(:USR1) {
|
727
|
+
trap(:USR1) { reopen = true }
|
689
728
|
|
690
729
|
ready = readers.dup
|
691
730
|
@after_worker_ready.call(self, worker)
|
692
731
|
|
693
732
|
begin
|
694
|
-
|
695
|
-
nr = 0
|
733
|
+
reopen = reopen_worker_logs(worker.nr) if reopen
|
696
734
|
worker.tick = time_now.to_i
|
697
|
-
|
698
|
-
while sock = tmp.shift
|
735
|
+
while sock = ready.shift
|
699
736
|
# Unicorn::Worker#kgio_tryaccept is not like accept(2) at all,
|
700
737
|
# but that will return false
|
701
738
|
if client = sock.kgio_tryaccept
|
702
739
|
process_client(client)
|
703
|
-
nr += 1
|
704
740
|
worker.tick = time_now.to_i
|
705
741
|
end
|
706
|
-
break if
|
742
|
+
break if reopen
|
707
743
|
end
|
708
744
|
|
709
|
-
#
|
710
|
-
# we're probably reasonably busy, so avoid calling select()
|
711
|
-
# and do a speculative non-blocking accept() on ready listeners
|
712
|
-
# before we sleep again in select().
|
713
|
-
unless nr == 0
|
714
|
-
tmp = ready.dup
|
715
|
-
redo
|
716
|
-
end
|
717
|
-
|
718
|
-
ppid == Process.ppid or return
|
719
|
-
|
720
|
-
# timeout used so we can detect parent death:
|
745
|
+
# timeout so we can .tick and keep parent from SIGKILL-ing us
|
721
746
|
worker.tick = time_now.to_i
|
722
|
-
|
747
|
+
waiter.get_readers(ready, readers, @timeout)
|
723
748
|
rescue => e
|
724
|
-
redo if
|
749
|
+
redo if reopen && readers[0]
|
725
750
|
Unicorn.log_error(@logger, "listen loop error", e) if readers[0]
|
726
751
|
end while readers[0]
|
727
752
|
end
|
@@ -758,11 +783,11 @@ class Unicorn::HttpServer
|
|
758
783
|
wpid <= 0 and return
|
759
784
|
Process.kill(0, wpid)
|
760
785
|
wpid
|
761
|
-
|
762
|
-
|
763
|
-
|
764
|
-
|
765
|
-
|
786
|
+
rescue Errno::EPERM
|
787
|
+
logger.info "pid=#{path} possibly stale, got EPERM signalling PID:#{wpid}"
|
788
|
+
nil
|
789
|
+
rescue Errno::ESRCH, Errno::ENOENT
|
790
|
+
# don't unlink stale pid files, racy without non-portable locking...
|
766
791
|
end
|
767
792
|
|
768
793
|
def load_config!
|
@@ -788,12 +813,12 @@ class Unicorn::HttpServer
|
|
788
813
|
end
|
789
814
|
|
790
815
|
def build_app!
|
791
|
-
if app.respond_to?(:arity) && app.arity == 0
|
816
|
+
if app.respond_to?(:arity) && (app.arity == 0 || app.arity == 2)
|
792
817
|
if defined?(Gem) && Gem.respond_to?(:refresh)
|
793
818
|
logger.info "Refreshing Gem list"
|
794
819
|
Gem.refresh
|
795
820
|
end
|
796
|
-
self.app = app.call
|
821
|
+
self.app = app.arity == 0 ? app.call : app.call(nil, self)
|
797
822
|
end
|
798
823
|
end
|
799
824
|
|
data/lib/unicorn/launcher.rb
CHANGED
data/lib/unicorn/oob_gc.rb
CHANGED
@@ -43,8 +43,8 @@
|
|
43
43
|
# use Unicorn::OobGC, 2, %r{\A/(?:expensive/foo|more_expensive/foo)}
|
44
44
|
#
|
45
45
|
# Feedback from users of early implementations of this module:
|
46
|
-
# * https://
|
47
|
-
# * https://
|
46
|
+
# * https://yhbt.net/unicorn-public/0BFC98E9-072B-47EE-9A70-05478C20141B@lukemelia.com/
|
47
|
+
# * https://yhbt.net/unicorn-public/AANLkTilUbgdyDv9W1bi-s_W6kq9sOhWfmuYkKLoKGOLj@mail.gmail.com/
|
48
48
|
|
49
49
|
module Unicorn::OobGC
|
50
50
|
|
@@ -60,7 +60,6 @@ module Unicorn::OobGC
|
|
60
60
|
self.const_set :OOBGC_INTERVAL, interval
|
61
61
|
ObjectSpace.each_object(Unicorn::HttpServer) do |s|
|
62
62
|
s.extend(self)
|
63
|
-
self.const_set :OOBGC_ENV, s.instance_variable_get(:@request).env
|
64
63
|
end
|
65
64
|
app # pretend to be Rack middleware since it was in the past
|
66
65
|
end
|
@@ -68,9 +67,10 @@ module Unicorn::OobGC
|
|
68
67
|
#:stopdoc:
|
69
68
|
def process_client(client)
|
70
69
|
super(client) # Unicorn::HttpServer#process_client
|
71
|
-
|
70
|
+
env = instance_variable_get(:@request).env
|
71
|
+
if OOBGC_PATH =~ env['PATH_INFO'] && ((@@nr -= 1) <= 0)
|
72
72
|
@@nr = OOBGC_INTERVAL
|
73
|
-
|
73
|
+
env.clear
|
74
74
|
disabled = GC.enable
|
75
75
|
GC.start
|
76
76
|
GC.disable if disabled
|
@@ -83,6 +83,7 @@ module Unicorn
|
|
83
83
|
rescue => e
|
84
84
|
logger.error("#{sock_name(sock)} " \
|
85
85
|
"failed to set accept_filter=#{name} (#{e.inspect})")
|
86
|
+
logger.error("perhaps accf_http(9) needs to be loaded".freeze)
|
86
87
|
end if arg != got
|
87
88
|
end
|
88
89
|
end
|
@@ -100,8 +101,8 @@ module Unicorn
|
|
100
101
|
log_buffer_sizes(sock, " after: ")
|
101
102
|
end
|
102
103
|
sock.listen(opt[:backlog])
|
103
|
-
|
104
|
-
|
104
|
+
rescue => e
|
105
|
+
Unicorn.log_error(logger, "#{sock_name(sock)} #{opt.inspect}", e)
|
105
106
|
end
|
106
107
|
|
107
108
|
def log_buffer_sizes(sock, pfx = '')
|
@@ -116,7 +117,7 @@ module Unicorn
|
|
116
117
|
def bind_listen(address = '0.0.0.0:8080', opt = {})
|
117
118
|
return address unless String === address
|
118
119
|
|
119
|
-
sock = if address
|
120
|
+
sock = if address.start_with?('/')
|
120
121
|
if File.exist?(address)
|
121
122
|
if File.socket?(address)
|
122
123
|
begin
|