unicorn 5.0.1 → 6.1.0

Sign up to get free protection for your applications and to get access to all the features.
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) }