unicorn 5.1.0.pre1 → 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 +15 -8
- 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 +17 -11
- data/LICENSE +2 -2
- data/Links +13 -11
- data/NEWS +522 -0
- data/README +28 -20
- 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 +830 -486
- data/ext/unicorn_http/unicorn_http.rl +63 -18
- data/ext/unicorn_http/unicorn_http_common.rl +1 -1
- data/lib/unicorn/configurator.rb +91 -12
- data/lib/unicorn/http_request.rb +101 -11
- data/lib/unicorn/http_response.rb +3 -2
- data/lib/unicorn/http_server.rb +139 -70
- 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 +25 -10
- data/man/man1/unicorn.1 +120 -116
- data/man/man1/unicorn_rails.1 +106 -106
- 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 +34 -18
- 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 +16 -17
- metadata +22 -29
- 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,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) {
|
@@ -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,7 +957,7 @@ static VALUE HttpParser_rssget(VALUE self)
|
|
917
957
|
|
918
958
|
void Init_unicorn_http(void)
|
919
959
|
{
|
920
|
-
VALUE mUnicorn
|
960
|
+
VALUE mUnicorn;
|
921
961
|
|
922
962
|
mUnicorn = rb_define_module("Unicorn");
|
923
963
|
cHttpParser = rb_define_class_under(mUnicorn, "HttpParser", rb_cObject);
|
@@ -928,6 +968,7 @@ void Init_unicorn_http(void)
|
|
928
968
|
e414 = rb_define_class_under(mUnicorn, "RequestURITooLongError",
|
929
969
|
eHttpParserError);
|
930
970
|
|
971
|
+
id_uminus = rb_intern("-@");
|
931
972
|
init_globals();
|
932
973
|
rb_define_alloc_func(cHttpParser, HttpParser_alloc);
|
933
974
|
rb_define_method(cHttpParser, "initialize", HttpParser_init, 0);
|
@@ -944,6 +985,7 @@ void Init_unicorn_http(void)
|
|
944
985
|
rb_define_method(cHttpParser, "next?", HttpParser_next, 0);
|
945
986
|
rb_define_method(cHttpParser, "buf", HttpParser_buf, 0);
|
946
987
|
rb_define_method(cHttpParser, "env", HttpParser_env, 0);
|
988
|
+
rb_define_method(cHttpParser, "hijacked!", HttpParser_hijacked_bang, 0);
|
947
989
|
rb_define_method(cHttpParser, "response_start_sent=", HttpParser_rssset, 1);
|
948
990
|
rb_define_method(cHttpParser, "response_start_sent", HttpParser_rssget, 0);
|
949
991
|
|
@@ -976,5 +1018,8 @@ void Init_unicorn_http(void)
|
|
976
1018
|
#ifndef HAVE_RB_HASH_CLEAR
|
977
1019
|
id_clear = rb_intern("clear");
|
978
1020
|
#endif
|
1021
|
+
id_is_chunked_p = rb_intern("is_chunked?");
|
1022
|
+
|
1023
|
+
init_epollexclusive(mUnicorn);
|
979
1024
|
}
|
980
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.
|
@@ -191,7 +239,7 @@ class Unicorn::Configurator
|
|
191
239
|
# server 192.168.0.9:8080 fail_timeout=0;
|
192
240
|
# }
|
193
241
|
#
|
194
|
-
# See
|
242
|
+
# See https://nginx.org/en/docs/http/ngx_http_upstream_module.html
|
195
243
|
# for more details on nginx upstream configuration.
|
196
244
|
def timeout(seconds)
|
197
245
|
set_int(:timeout, seconds, 3)
|
@@ -200,6 +248,17 @@ class Unicorn::Configurator
|
|
200
248
|
set[:timeout] = seconds > max ? max : seconds
|
201
249
|
end
|
202
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
|
+
|
203
262
|
# sets the current number of worker_processes to +nr+. Each worker
|
204
263
|
# process will serve exactly one client at a time. You can
|
205
264
|
# increment or decrement this value at runtime by sending SIGTTIN
|
@@ -210,6 +269,23 @@ class Unicorn::Configurator
|
|
210
269
|
set_int(:worker_processes, nr, 1)
|
211
270
|
end
|
212
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
|
+
|
213
289
|
# sets listeners to the given +addresses+, replacing or augmenting the
|
214
290
|
# current set. This is for the global listener pool shared by all
|
215
291
|
# worker processes. For per-worker listeners, see the after_fork example
|
@@ -466,13 +542,12 @@ class Unicorn::Configurator
|
|
466
542
|
# Disabling rewindability can improve performance by lowering
|
467
543
|
# I/O and memory usage for applications that accept uploads.
|
468
544
|
# Keep in mind that the Rack 1.x spec requires
|
469
|
-
# \env[\"rack.input\"] to be rewindable,
|
470
|
-
#
|
545
|
+
# \env[\"rack.input\"] to be rewindable,
|
546
|
+
# but the Rack 2.x spec does not.
|
471
547
|
#
|
472
|
-
# +rewindable_input+ defaults to +true+
|
473
|
-
#
|
474
|
-
#
|
475
|
-
# (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.
|
476
551
|
def rewindable_input(bool)
|
477
552
|
set_bool(:rewindable_input, bool)
|
478
553
|
end
|
@@ -533,7 +608,7 @@ class Unicorn::Configurator
|
|
533
608
|
# just let chdir raise errors
|
534
609
|
path = File.expand_path(path)
|
535
610
|
if config_file &&
|
536
|
-
config_file
|
611
|
+
! config_file.start_with?('/') &&
|
537
612
|
! File.readable?("#{path}/#{config_file}")
|
538
613
|
raise ArgumentError,
|
539
614
|
"config_file=#{config_file} would not be accessible in" \
|
@@ -548,6 +623,10 @@ class Unicorn::Configurator
|
|
548
623
|
# This switch will occur after calling the after_fork hook, and only
|
549
624
|
# if the Worker#user method is not called in the after_fork hook
|
550
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.
|
551
630
|
def user(user, group = nil)
|
552
631
|
# raises ArgumentError on invalid user/group
|
553
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
|
@@ -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) }
|