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.
Files changed (92) hide show
  1. checksums.yaml +5 -5
  2. data/.manifest +11 -5
  3. data/.olddoc.yml +16 -6
  4. data/Application_Timeouts +4 -4
  5. data/CONTRIBUTORS +6 -2
  6. data/Documentation/.gitignore +1 -3
  7. data/Documentation/unicorn.1 +222 -0
  8. data/Documentation/unicorn_rails.1 +207 -0
  9. data/FAQ +1 -1
  10. data/GIT-VERSION-FILE +1 -1
  11. data/GIT-VERSION-GEN +1 -1
  12. data/GNUmakefile +118 -58
  13. data/HACKING +2 -10
  14. data/ISSUES +40 -35
  15. data/KNOWN_ISSUES +2 -2
  16. data/LATEST +23 -28
  17. data/LICENSE +2 -2
  18. data/Links +13 -11
  19. data/NEWS +612 -0
  20. data/README +30 -29
  21. data/SIGNALS +1 -1
  22. data/Sandbox +8 -7
  23. data/TODO +0 -2
  24. data/TUNING +19 -1
  25. data/archive/slrnpull.conf +1 -1
  26. data/bin/unicorn +3 -1
  27. data/bin/unicorn_rails +2 -2
  28. data/examples/big_app_gc.rb +1 -1
  29. data/examples/init.sh +36 -8
  30. data/examples/logrotate.conf +17 -2
  31. data/examples/nginx.conf +4 -3
  32. data/examples/unicorn.conf.minimal.rb +2 -2
  33. data/examples/unicorn.conf.rb +2 -2
  34. data/examples/unicorn@.service +14 -0
  35. data/ext/unicorn_http/c_util.h +5 -13
  36. data/ext/unicorn_http/common_field_optimization.h +22 -5
  37. data/ext/unicorn_http/epollexclusive.h +124 -0
  38. data/ext/unicorn_http/ext_help.h +0 -44
  39. data/ext/unicorn_http/extconf.rb +32 -6
  40. data/ext/unicorn_http/global_variables.h +2 -2
  41. data/ext/unicorn_http/httpdate.c +2 -1
  42. data/ext/unicorn_http/unicorn_http.c +853 -498
  43. data/ext/unicorn_http/unicorn_http.rl +86 -30
  44. data/ext/unicorn_http/unicorn_http_common.rl +1 -1
  45. data/lib/unicorn/configurator.rb +93 -13
  46. data/lib/unicorn/http_request.rb +101 -11
  47. data/lib/unicorn/http_response.rb +8 -4
  48. data/lib/unicorn/http_server.rb +141 -72
  49. data/lib/unicorn/launcher.rb +1 -1
  50. data/lib/unicorn/oob_gc.rb +6 -6
  51. data/lib/unicorn/select_waiter.rb +6 -0
  52. data/lib/unicorn/socket_helper.rb +23 -7
  53. data/lib/unicorn/stream_input.rb +5 -4
  54. data/lib/unicorn/tee_input.rb +8 -10
  55. data/lib/unicorn/tmpio.rb +8 -2
  56. data/lib/unicorn/util.rb +3 -3
  57. data/lib/unicorn/version.rb +1 -1
  58. data/lib/unicorn/worker.rb +33 -8
  59. data/lib/unicorn.rb +55 -29
  60. data/man/man1/unicorn.1 +120 -118
  61. data/man/man1/unicorn_rails.1 +106 -107
  62. data/t/GNUmakefile +3 -72
  63. data/t/README +4 -4
  64. data/t/t0011-active-unix-socket.sh +1 -1
  65. data/t/t0012-reload-empty-config.sh +2 -1
  66. data/t/t0301-no-default-middleware-ignored-in-config.sh +25 -0
  67. data/t/t0301.ru +13 -0
  68. data/t/test-lib.sh +4 -3
  69. data/test/benchmark/README +14 -4
  70. data/test/benchmark/ddstream.ru +50 -0
  71. data/test/benchmark/readinput.ru +40 -0
  72. data/test/benchmark/uconnect.perl +66 -0
  73. data/test/exec/test_exec.rb +26 -24
  74. data/test/test_helper.rb +38 -30
  75. data/test/unit/test_ccc.rb +91 -0
  76. data/test/unit/test_droplet.rb +1 -1
  77. data/test/unit/test_http_parser.rb +46 -16
  78. data/test/unit/test_http_parser_ng.rb +81 -0
  79. data/test/unit/test_request.rb +10 -10
  80. data/test/unit/test_server.rb +86 -12
  81. data/test/unit/test_signals.rb +8 -8
  82. data/test/unit/test_socket_helper.rb +13 -9
  83. data/test/unit/test_upload.rb +9 -14
  84. data/test/unit/test_util.rb +31 -5
  85. data/test/unit/test_waiter.rb +34 -0
  86. data/unicorn.gemspec +21 -22
  87. metadata +21 -28
  88. data/Documentation/GNUmakefile +0 -30
  89. data/Documentation/unicorn.1.txt +0 -188
  90. data/Documentation/unicorn_rails.1.txt +0 -175
  91. data/t/hijack.ru +0 -43
  92. 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 (STR_CSTR_CASE_EQ(v, "chunked")) {
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
- Data_Get_Struct(self, struct http_parser, hp);
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@bogomips.org (no HTML mail, no subscription necessary)
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
- my_hash_clear(hp->env);
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, cHttpParser;
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
@@ -4,7 +4,7 @@
4
4
 
5
5
  #### HTTP PROTOCOL GRAMMAR
6
6
  # line endings
7
- CRLF = "\r\n";
7
+   CRLF = ("\r\n" | "\n");
8
8
 
9
9
  # character types
10
10
  CTL = (cntrl | 127);
@@ -3,11 +3,11 @@ require 'logger'
3
3
 
4
4
  # Implements a simple DSL for configuring a unicorn server.
5
5
  #
6
- # See http://unicorn.bogomips.org/examples/unicorn.conf.rb and
7
- # http://unicorn.bogomips.org/examples/unicorn.conf.minimal.rb
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
- # http://unicorn.bogomips.org/examples/nginx.conf
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, # for Rack 2.x: (Rack::VERSION[0] <= 1),
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, so this allows
469
- # intentionally violating the current Rack 1.x spec.
545
+ # \env[\"rack.input\"] to be rewindable,
546
+ # but the Rack 2.x spec does not.
470
547
  #
471
- # +rewindable_input+ defaults to +true+ when used with Rack 1.x for
472
- # Rack conformance. When Rack 2.x is finalized, this will most
473
- # likely default to +false+ while still conforming to the newer
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[0] != ?/ &&
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)
@@ -24,12 +24,11 @@ class Unicorn::HttpParser
24
24
  NULL_IO = StringIO.new("")
25
25
 
26
26
  # :stopdoc:
27
- # A frozen format for this is about 15% faster
28
- # Drop these frozen strings when Ruby 2.2 becomes more prevalent,
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 http://www.ietf.org/rfc/rfc3875:
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
- # detect if the socket is valid by writing a partial response:
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} #{Rack::Utils::HTTP_STATUS_CODES[code]}\r\n\r\n"
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
- response_start_sent=false)
24
+ req = Unicorn::HttpRequest.new)
22
25
  hijack = nil
23
26
 
24
27
  if headers
25
28
  code = status.to_i
26
- msg = Rack::Utils::HTTP_STATUS_CODES[code]
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) }