passenger 5.0.8 → 5.0.9

Sign up to get free protection for your applications and to get access to all the features.

Potentially problematic release.


This version of passenger might be problematic. Click here for more details.

Files changed (168) hide show
  1. checksums.yaml +8 -8
  2. checksums.yaml.gz.asc +7 -7
  3. data.tar.gz.asc +7 -7
  4. data/.editorconfig +20 -0
  5. data/CHANGELOG +21 -0
  6. data/bin/passenger-install-apache2-module +3 -1
  7. data/build/agents.rb +7 -5
  8. data/build/basics.rb +3 -3
  9. data/build/common_library.rb +52 -30
  10. data/build/cxx_tests.rb +20 -13
  11. data/build/misc.rb +5 -5
  12. data/doc/Design and Architecture.html +1 -1
  13. data/doc/Design and Architecture.txt +1 -1
  14. data/doc/Packaging.html +4 -4
  15. data/doc/Packaging.txt.md +4 -4
  16. data/doc/Users guide Apache.html +22 -9
  17. data/doc/Users guide Apache.idmap.txt +4 -2
  18. data/doc/Users guide Apache.txt +2 -0
  19. data/doc/Users guide Nginx.html +22 -9
  20. data/doc/Users guide Nginx.idmap.txt +4 -2
  21. data/doc/Users guide Nginx.txt +2 -0
  22. data/doc/Users guide Standalone.html +14 -9
  23. data/doc/Users guide Standalone.idmap.txt +4 -2
  24. data/doc/users_guide_snippets/installation.txt +10 -6
  25. data/ext/apache2/Hooks.cpp +13 -2
  26. data/ext/common/ApplicationPool2/Pool/Inspection.h +8 -3
  27. data/ext/common/BackgroundEventLoop.cpp +249 -67
  28. data/ext/common/BackgroundEventLoop.h +5 -5
  29. data/ext/common/Constants.h +1 -1
  30. data/ext/common/InstanceDirectory.h +8 -6
  31. data/ext/common/ServerKit/Context.h +8 -2
  32. data/ext/common/ServerKit/FileBufferedChannel.h +262 -226
  33. data/ext/common/ServerKit/HeaderTable.h +28 -3
  34. data/ext/common/ServerKit/HttpHeaderParser.h +37 -13
  35. data/ext/common/ServerKit/HttpServer.h +17 -1
  36. data/ext/common/ServerKit/Implementation.cpp +2 -0
  37. data/ext/common/ServerKit/Server.h +25 -28
  38. data/ext/common/Utils/IOUtils.cpp +11 -0
  39. data/ext/common/Utils/ProcessMetricsCollector.h +4 -0
  40. data/ext/common/Utils/StrIntUtils.cpp +11 -7
  41. data/ext/common/Utils/StrIntUtils.h +1 -1
  42. data/ext/common/Utils/StrIntUtilsNoStrictAliasing.cpp +21 -16
  43. data/ext/common/agents/Base.cpp +6 -0
  44. data/ext/common/agents/Base.h +2 -0
  45. data/ext/common/agents/HelperAgent/AdminServer.h +25 -25
  46. data/ext/common/agents/HelperAgent/Main.cpp +37 -12
  47. data/ext/common/agents/HelperAgent/RequestHandler.h +18 -20
  48. data/ext/common/agents/HelperAgent/RequestHandler/AppResponse.h +4 -0
  49. data/ext/common/agents/HelperAgent/RequestHandler/ForwardResponse.cpp +10 -6
  50. data/ext/common/agents/HelperAgent/RequestHandler/Hooks.cpp +2 -0
  51. data/ext/common/agents/HelperAgent/RequestHandler/InitRequest.cpp +1 -1
  52. data/ext/common/agents/HelperAgent/RequestHandler/SendRequest.cpp +1 -1
  53. data/ext/common/agents/HelperAgent/RequestHandler/Utils.cpp +9 -2
  54. data/ext/common/agents/HelperAgent/ResponseCache.h +11 -11
  55. data/ext/common/agents/LoggingAgent/AdminServer.h +8 -8
  56. data/ext/common/agents/LoggingAgent/Main.cpp +6 -5
  57. data/ext/common/agents/Watchdog/AdminServer.h +13 -13
  58. data/ext/common/agents/Watchdog/Main.cpp +8 -3
  59. data/ext/libuv/.gitignore +72 -0
  60. data/ext/libuv/AUTHORS +199 -0
  61. data/ext/libuv/ChangeLog +2023 -0
  62. data/ext/libuv/LICENSE +46 -0
  63. data/ext/libuv/Makefile.am +336 -0
  64. data/ext/libuv/README.md +197 -0
  65. data/ext/libuv/checksparse.sh +233 -0
  66. data/ext/libuv/common.gypi +210 -0
  67. data/ext/libuv/configure.ac +67 -0
  68. data/ext/libuv/gyp_uv.py +96 -0
  69. data/ext/libuv/include/android-ifaddrs.h +54 -0
  70. data/ext/libuv/include/pthread-fixes.h +72 -0
  71. data/ext/libuv/include/tree.h +768 -0
  72. data/ext/libuv/include/uv-aix.h +32 -0
  73. data/ext/libuv/include/uv-bsd.h +34 -0
  74. data/ext/libuv/include/uv-darwin.h +61 -0
  75. data/ext/libuv/include/uv-errno.h +418 -0
  76. data/ext/libuv/include/uv-linux.h +34 -0
  77. data/ext/libuv/include/uv-sunos.h +44 -0
  78. data/ext/libuv/include/uv-threadpool.h +37 -0
  79. data/ext/libuv/include/uv-unix.h +383 -0
  80. data/ext/libuv/include/uv-version.h +39 -0
  81. data/ext/libuv/include/uv.h +1455 -0
  82. data/ext/libuv/libuv.pc.in +11 -0
  83. data/ext/libuv/m4/.gitignore +4 -0
  84. data/ext/libuv/m4/as_case.m4 +21 -0
  85. data/ext/libuv/m4/libuv-check-flags.m4 +319 -0
  86. data/ext/libuv/src/fs-poll.c +255 -0
  87. data/ext/libuv/src/heap-inl.h +245 -0
  88. data/ext/libuv/src/inet.c +313 -0
  89. data/ext/libuv/src/queue.h +92 -0
  90. data/ext/libuv/src/threadpool.c +303 -0
  91. data/ext/libuv/src/unix/aix.c +1240 -0
  92. data/ext/libuv/src/unix/android-ifaddrs.c +703 -0
  93. data/ext/libuv/src/unix/async.c +284 -0
  94. data/ext/libuv/src/unix/atomic-ops.h +60 -0
  95. data/ext/libuv/src/unix/core.c +985 -0
  96. data/ext/libuv/src/unix/darwin-proctitle.c +206 -0
  97. data/ext/libuv/src/unix/darwin.c +331 -0
  98. data/ext/libuv/src/unix/dl.c +83 -0
  99. data/ext/libuv/src/unix/freebsd.c +435 -0
  100. data/ext/libuv/src/unix/fs.c +1189 -0
  101. data/ext/libuv/src/unix/fsevents.c +899 -0
  102. data/ext/libuv/src/unix/getaddrinfo.c +202 -0
  103. data/ext/libuv/src/unix/getnameinfo.c +120 -0
  104. data/ext/libuv/src/unix/internal.h +314 -0
  105. data/ext/libuv/src/unix/kqueue.c +418 -0
  106. data/ext/libuv/src/unix/linux-core.c +876 -0
  107. data/ext/libuv/src/unix/linux-inotify.c +257 -0
  108. data/ext/libuv/src/unix/linux-syscalls.c +471 -0
  109. data/ext/libuv/src/unix/linux-syscalls.h +158 -0
  110. data/ext/libuv/src/unix/loop-watcher.c +63 -0
  111. data/ext/libuv/src/unix/loop.c +135 -0
  112. data/ext/libuv/src/unix/netbsd.c +368 -0
  113. data/ext/libuv/src/unix/openbsd.c +384 -0
  114. data/ext/libuv/src/unix/pipe.c +288 -0
  115. data/ext/libuv/src/unix/poll.c +113 -0
  116. data/ext/libuv/src/unix/process.c +551 -0
  117. data/ext/libuv/src/unix/proctitle.c +102 -0
  118. data/ext/libuv/src/unix/pthread-fixes.c +103 -0
  119. data/ext/libuv/src/unix/signal.c +465 -0
  120. data/ext/libuv/src/unix/spinlock.h +53 -0
  121. data/ext/libuv/src/unix/stream.c +1598 -0
  122. data/ext/libuv/src/unix/sunos.c +763 -0
  123. data/ext/libuv/src/unix/tcp.c +327 -0
  124. data/ext/libuv/src/unix/thread.c +519 -0
  125. data/ext/libuv/src/unix/timer.c +172 -0
  126. data/ext/libuv/src/unix/tty.c +265 -0
  127. data/ext/libuv/src/unix/udp.c +833 -0
  128. data/ext/libuv/src/uv-common.c +544 -0
  129. data/ext/libuv/src/uv-common.h +214 -0
  130. data/ext/libuv/src/version.c +49 -0
  131. data/ext/libuv/uv.gyp +487 -0
  132. data/ext/nginx/ContentHandler.c +21 -10
  133. data/ext/nginx/ngx_http_passenger_module.c +7 -0
  134. data/ext/oxt/implementation.cpp +9 -2
  135. data/ext/oxt/initialize.hpp +5 -1
  136. data/lib/phusion_passenger.rb +3 -3
  137. data/lib/phusion_passenger/admin_tools/instance.rb +10 -6
  138. data/lib/phusion_passenger/admin_tools/instance_registry.rb +6 -2
  139. data/lib/phusion_passenger/packaging.rb +3 -4
  140. data/lib/phusion_passenger/platform_info.rb +13 -1
  141. data/lib/phusion_passenger/platform_info/apache.rb +15 -4
  142. data/lib/phusion_passenger/platform_info/apache_detector.rb +5 -1
  143. data/lib/phusion_passenger/rack/thread_handler_extension.rb +184 -99
  144. data/lib/phusion_passenger/request_handler/thread_handler.rb +13 -6
  145. data/lib/phusion_passenger/standalone/start_command.rb +2 -2
  146. data/resources/templates/apache2/apache_install_broken.txt.erb +2 -1
  147. metadata +99 -22
  148. metadata.gz.asc +7 -7
  149. data/ext/libeio/Changes +0 -76
  150. data/ext/libeio/LICENSE +0 -36
  151. data/ext/libeio/Makefile.am +0 -15
  152. data/ext/libeio/Makefile.in +0 -694
  153. data/ext/libeio/aclocal.m4 +0 -9418
  154. data/ext/libeio/autogen.sh +0 -3
  155. data/ext/libeio/config.guess +0 -1540
  156. data/ext/libeio/config.h.in +0 -136
  157. data/ext/libeio/config.sub +0 -1779
  158. data/ext/libeio/configure +0 -14822
  159. data/ext/libeio/configure.ac +0 -22
  160. data/ext/libeio/demo.c +0 -194
  161. data/ext/libeio/ecb.h +0 -714
  162. data/ext/libeio/eio.c +0 -2818
  163. data/ext/libeio/eio.h +0 -414
  164. data/ext/libeio/install-sh +0 -520
  165. data/ext/libeio/libeio.m4 +0 -195
  166. data/ext/libeio/ltmain.sh +0 -9636
  167. data/ext/libeio/missing +0 -376
  168. data/ext/libeio/xthread.h +0 -166
@@ -140,12 +140,12 @@ map_uri_to_page_cache_file(ngx_http_request_t *r, ngx_str_t *public_dir,
140
140
 
141
141
  } else if (filename[filename_len - 1] == '/') {
142
142
  /* if the filename ends with '/' check for filename + "index.html". */
143
-
143
+
144
144
  if (filename_len + sizeof("index.html") > page_cache_file->len) {
145
145
  /* Page cache filename doesn't fit in the buffer. */
146
146
  return 0;
147
147
  }
148
-
148
+
149
149
  end = ngx_copy(page_cache_file->data, filename, filename_len);
150
150
  end = ngx_copy(end, "index.html", sizeof("index.html"));
151
151
  } else {
@@ -416,16 +416,27 @@ prepare_request_buffer_construction(ngx_http_request_t *r, passenger_context_t *
416
416
  * Nginx unescapes URI's before passing them to Phusion Passenger,
417
417
  * but backend processes expect the escaped version.
418
418
  * http://code.google.com/p/phusion-passenger/issues/detail?id=404
419
+ *
420
+ * Here we check whether Nginx has rewritten the URI or not. If not,
421
+ * we can use the raw, unparsed URI as sent by the client.
419
422
  */
420
- state->escaped_uri.len =
421
- 2 * ngx_escape_uri(NULL, r->uri.data, r->uri.len, NGX_ESCAPE_URI)
422
- + r->uri.len;
423
- state->escaped_uri.data = ngx_pnalloc(r->pool, state->escaped_uri.len);
424
- if (state->escaped_uri.data == NULL) {
425
- return NGX_ERROR;
423
+ if (r->valid_unparsed_uri && r->main) {
424
+ state->escaped_uri = r->unparsed_uri;
425
+ const char *pos = memchr((const char *) r->unparsed_uri.data, '?', r->unparsed_uri.len);
426
+ if (pos != NULL) {
427
+ state->escaped_uri.len = pos - (const char *) r->unparsed_uri.data;
428
+ }
429
+ } else {
430
+ state->escaped_uri.len =
431
+ 2 * ngx_escape_uri(NULL, r->uri.data, r->uri.len, NGX_ESCAPE_URI)
432
+ + r->uri.len;
433
+ state->escaped_uri.data = ngx_pnalloc(r->pool, state->escaped_uri.len);
434
+ if (state->escaped_uri.data == NULL) {
435
+ return NGX_ERROR;
436
+ }
437
+ ngx_escape_uri(state->escaped_uri.data, r->uri.data, r->uri.len,
438
+ NGX_ESCAPE_URI);
426
439
  }
427
- ngx_escape_uri(state->escaped_uri.data, r->uri.data, r->uri.len,
428
- NGX_ESCAPE_URI);
429
440
 
430
441
  if (r->headers_in.chunked) {
431
442
  /* If the request body is chunked, then Nginx sets r->headers_in.content_length_n
@@ -302,6 +302,13 @@ start_watchdog(ngx_cycle_t *cycle) {
302
302
 
303
303
  if (passenger_main_conf.log_file.len > 0) {
304
304
  pp_variant_map_set_ngx_str(params, "log_file", &passenger_main_conf.log_file);
305
+ } else if (cycle->new_log.file == NULL) {
306
+ ngx_log_error(NGX_LOG_EMERG, cycle->log, 0, "Cannot initialize " PROGRAM_NAME
307
+ " because Nginx is not configured with an error log file."
308
+ " Please either configure Nginx with an error log file, or configure "
309
+ PROGRAM_NAME " with a `passenger_log_file`");
310
+ result = NGX_ERROR;
311
+ goto cleanup;
305
312
  } else if (cycle->new_log.file->name.len > 0) {
306
313
  pp_variant_map_set_ngx_str(params, "log_file", &cycle->new_log.file->name);
307
314
  } else if (cycle->log->file->name.len > 0) {
@@ -2,7 +2,7 @@
2
2
  * OXT - OS eXtensions for boosT
3
3
  * Provides important functionality necessary for writing robust server software.
4
4
  *
5
- * Copyright (c) 2008-2014 Phusion
5
+ * Copyright (c) 2008-2015 Phusion
6
6
  *
7
7
  * Permission is hereby granted, free of charge, to any person obtaining a copy
8
8
  * of this software and associated documentation files (the "Software"), to deal
@@ -326,7 +326,8 @@ tracable_exception::what() const throw() {
326
326
  #endif /* OXT_BACKTRACE_IS_ENABLED */
327
327
 
328
328
 
329
- void initialize() {
329
+ void
330
+ initialize() {
330
331
  global_context = new global_context_t();
331
332
  init_thread_local_context_support();
332
333
  // For some reason make_shared() crashes here when compiled with clang 3.2 on OS X.
@@ -342,6 +343,12 @@ void initialize() {
342
343
  ctx->iterator--;
343
344
  }
344
345
 
346
+ void shutdown() {
347
+ free_thread_local_context();
348
+ delete global_context;
349
+ global_context = NULL;
350
+ }
351
+
345
352
 
346
353
  global_context_t::global_context_t()
347
354
  : next_thread_number(2)
@@ -2,7 +2,7 @@
2
2
  * OXT - OS eXtensions for boosT
3
3
  * Provides important functionality necessary for writing robust server software.
4
4
  *
5
- * Copyright (c) 2012 Phusion
5
+ * Copyright (c) 2012-2015 Phusion
6
6
  *
7
7
  * Permission is hereby granted, free of charge, to any person obtaining a copy
8
8
  * of this software and associated documentation files (the "Software"), to deal
@@ -33,6 +33,10 @@ namespace oxt {
33
33
  * for that.
34
34
  */
35
35
  void initialize();
36
+ /**
37
+ * Frees resources allocated by initialize().
38
+ */
39
+ void shutdown();
36
40
 
37
41
  } // namespace oxt
38
42
 
@@ -30,10 +30,10 @@ module PhusionPassenger
30
30
 
31
31
  PACKAGE_NAME = 'passenger'
32
32
  # Run 'rake ext/common/Constants.h' after changing this number.
33
- VERSION_STRING = '5.0.8'
33
+ VERSION_STRING = '5.0.9'
34
34
 
35
- PREFERRED_NGINX_VERSION = '1.6.3'
36
- NGINX_SHA256_CHECKSUM = '0a98e95b366e4d6042f331e1fa4d70e18fd1e49d8993e589008e70e742b7e757'
35
+ PREFERRED_NGINX_VERSION = '1.8.0'
36
+ NGINX_SHA256_CHECKSUM = '23cca1239990c818d8f6da118320c4979aadf5386deda691b1b7c2c96b9df3d5'
37
37
 
38
38
  PREFERRED_PCRE_VERSION = '8.34'
39
39
  PCRE_SHA256_CHECKSUM = '1dd78994c81e44ac41cf30b2a21d4b4cc6d76ccde7fc6e77713ed51d7bddca47'
@@ -59,28 +59,32 @@ module PhusionPassenger
59
59
 
60
60
  def locked?
61
61
  if defined?(File::LOCK_EX)
62
- return !File.open("#{@path}/lock", "r") do |f|
63
- f.flock(File::LOCK_EX | File::LOCK_NB)
62
+ begin
63
+ !File.open("#{@path}/lock", "r") do |f|
64
+ f.flock(File::LOCK_EX | File::LOCK_NB)
65
+ end
66
+ rescue Errno::ENOENT
67
+ false
64
68
  end
65
69
  else
66
70
  # Solaris :-(
67
71
  # Since using fcntl locks in Ruby is a huge pain,
68
72
  # we'll fallback to checking the watchdog PID.
69
- return watchdog_alive?
73
+ watchdog_alive?
70
74
  end
71
75
  end
72
76
 
73
77
  def stale?
74
78
  stat = File.stat(@path)
75
79
  if stat.mtime < Time.now - STALE_TIMEOUT
76
- return !locked?
80
+ !locked?
77
81
  else
78
- return false
82
+ false
79
83
  end
80
84
  end
81
85
 
82
86
  def watchdog_alive?
83
- return process_is_alive?(@watchdog_pid)
87
+ process_is_alive?(@watchdog_pid)
84
88
  end
85
89
 
86
90
  def http_request(socket_path, request)
@@ -102,8 +102,12 @@ module PhusionPassenger
102
102
 
103
103
  def cleanup(path)
104
104
  puts "*** Cleaning stale instance directory #{path}"
105
- FileUtils.chmod_R(0700, path) rescue nil
106
- FileUtils.remove_entry_secure(path)
105
+ begin
106
+ FileUtils.chmod_R(0700, path) rescue nil
107
+ FileUtils.remove_entry_secure(path)
108
+ rescue SystemCallError => e
109
+ puts " Warning: #{e}"
110
+ end
107
111
  end
108
112
  end
109
113
 
@@ -100,10 +100,9 @@ module PhusionPassenger
100
100
  'ext/libev/{*.m4,autogen.sh,config.guess,config.h.in,config.sub}',
101
101
  'ext/libev/{configure,configure.ac,depcomp,install-sh,ltmain.sh,missing,mkinstalldirs}',
102
102
  'ext/libev/{*.h,*.c}',
103
- 'ext/libeio/{LICENSE,Changes,README,Makefile.am,Makefile.in}',
104
- 'ext/libeio/{*.m4,autogen.sh,config.guess,config.h.in,config.sub}',
105
- 'ext/libeio/{configure,configure.ac,install-sh,ltmain.sh,missing,mkinstalldirs}',
106
- 'ext/libeio/{*.h,*.c}',
103
+ 'ext/libuv/**/*',
104
+ 'ext/libuv/.gitignore',
105
+ 'ext/libuv/m4/.gitignore',
107
106
  'ext/oxt/*.hpp',
108
107
  'ext/oxt/*.cpp',
109
108
  'ext/oxt/*.txt',
@@ -1,6 +1,6 @@
1
1
  # encoding: binary
2
2
  # Phusion Passenger - https://www.phusionpassenger.com/
3
- # Copyright (c) 2010-2014 Phusion
3
+ # Copyright (c) 2010-2015 Phusion
4
4
  #
5
5
  # "Phusion Passenger" is a trademark of Hongli Lai & Ninh Bui.
6
6
  #
@@ -366,6 +366,18 @@ module PhusionPassenger
366
366
  ["/opt/*/bin", "/opt/*/sbin", "/usr/local/*/bin", "/usr/local/*/sbin"].each do |glob|
367
367
  search_dirs.concat(Dir[glob])
368
368
  end
369
+
370
+ # Solaris systems may have Apache installations in
371
+ # /usr/apache2/2.2/bin/sparcv9/
372
+ Dir["/usr/apache2/*/bin"].each do |bindir|
373
+ search_dirs << bindir
374
+ Dir["#{bindir}/*"].each do |binsubdir|
375
+ if File.directory?(binsubdir)
376
+ search_dirs << binsubdir
377
+ end
378
+ end
379
+ end
380
+
369
381
  search_dirs.delete("")
370
382
  search_dirs.uniq!
371
383
 
@@ -649,14 +649,25 @@ module PhusionPassenger
649
649
  # Linker flags that are necessary for linking an Apache module.
650
650
  # Already includes APR and APU linker flags.
651
651
  def self.apache2_module_cxx_ldflags
652
+ flags = ""
653
+ if !apxs2.nil?
654
+ flags << `#{apxs2} -q LDFLAGS`.strip
655
+ end
656
+
657
+ # We must include the cxxflags in the linker flags. On some multilib
658
+ # Solaris systems, `apxs -q CFLAGS` outputs a flag that tells the compiler
659
+ # which architecture to compile against, while `apxs -q LDFLAGS` doesn't.
660
+ flags << " #{apache2_module_cxxflags} #{apr_cxx_ldflags} #{apu_cxx_ldflags}"
661
+
652
662
  if cxx_is_sun_studio?
653
- flags = "-KPIC"
663
+ flags.gsub!("-fPIC", "-KPIC")
664
+ flags << " -KPIC" if !flags.include?("-KPIC")
654
665
  else
655
- flags = "-fPIC"
666
+ flags << " -fPIC" if !flags.include?("-fPIC")
656
667
  end
657
- flags << " #{apr_cxx_ldflags} #{apu_cxx_ldflags}"
668
+
658
669
  flags.strip!
659
- return flags
670
+ flags
660
671
  end
661
672
  memoize :apache2_module_cxx_ldflags
662
673
 
@@ -1,5 +1,5 @@
1
1
  # Phusion Passenger - https://www.phusionpassenger.com/
2
- # Copyright (c) 2013 Phusion
2
+ # Copyright (c) 2013-2015 Phusion
3
3
  #
4
4
  # "Phusion Passenger" is a trademark of Hongli Lai & Ninh Bui.
5
5
  #
@@ -107,6 +107,10 @@ module PhusionPassenger
107
107
  log "<banner>Looking for possible Apache installations...</banner>"
108
108
  apxses = PlatformInfo.find_all_commands("apxs2") +
109
109
  PlatformInfo.find_all_commands("apxs")
110
+ if !apxses.include?(PlatformInfo.apxs2)
111
+ PlatformInfo.send(:log, "Looking for #{PlatformInfo.apxs2}: found")
112
+ apxses << PlatformInfo.apxs2
113
+ end
110
114
  apxses = remove_symlink_duplications(apxses)
111
115
  log ""
112
116
  apxses.each do |apxs2|
@@ -39,13 +39,17 @@ module PhusionPassenger
39
39
  RACK_URL_SCHEME = "rack.url_scheme" # :nodoc:
40
40
  RACK_HIJACK_P = "rack.hijack?" # :nodoc:
41
41
  RACK_HIJACK = "rack.hijack" # :nodoc:
42
+ HTTP_VERSION = "HTTP_VERSION" # :nodoc:
43
+ HTTP_1_1 = "HTTP/1.1" # :nodoc:
42
44
  SCRIPT_NAME = "SCRIPT_NAME" # :nodoc:
43
45
  REQUEST_METHOD = "REQUEST_METHOD" # :nodoc:
44
46
  TRANSFER_ENCODING_HEADER = "Transfer-Encoding" # :nodoc:
45
47
  CONTENT_LENGTH_HEADER = "Content-Length" # :nodoc:
48
+ X_SENDFILE_HEADER = "X-Sendfile" # :nodoc:
49
+ X_ACCEL_REDIRECT_HEADER = "X-Accel-Redirect" # :nodoc:
46
50
  CONTENT_LENGTH_HEADER_AND_SEPARATOR = "Content-Length: " # :nodoc
47
- TRANSFER_ENCODING_HEADER_AND_VALUE_CRLF2 = "Transfer-Encoding: chunked\r\n\r\n" # :nodoc:
48
- CONNECTION_CLOSE_CRLF = "Connection: close\r\n" # :nodoc:
51
+ TRANSFER_ENCODING_HEADER_AND_VALUE_CRLF = "Transfer-Encoding: chunked\r\n" # :nodoc:
52
+ CONNECTION_CLOSE_CRLF2 = "Connection: close\r\n\r\n" # :nodoc:
49
53
  HEAD = "HEAD" # :nodoc:
50
54
  HTTPS = "HTTPS" # :nodoc:
51
55
  HTTPS_DOWNCASE = "https" # :nodoc:
@@ -80,6 +84,7 @@ module PhusionPassenger
80
84
  connection
81
85
  end
82
86
  end
87
+ env[HTTP_VERSION] = HTTP_1_1
83
88
 
84
89
  # Rails somehow modifies env['REQUEST_METHOD'], so we perform the comparison
85
90
  # before the Rack application object is called.
@@ -88,10 +93,7 @@ module PhusionPassenger
88
93
  begin
89
94
  status, headers, body = @app.call(env)
90
95
  rescue => e
91
- disable_keep_alive
92
- if should_reraise_app_error?(e, socket_wrapper)
93
- raise e
94
- elsif !should_swallow_app_error?(e, socket_wrapper)
96
+ if !should_swallow_app_error?(e, socket_wrapper)
95
97
  # It's a good idea to catch application exceptions here because
96
98
  # otherwise maliciously crafted responses can crash the app,
97
99
  # forcing it to be respawned, and thereby effectively DoSing it.
@@ -102,112 +104,206 @@ module PhusionPassenger
102
104
  end
103
105
 
104
106
  # Application requested a full socket hijack.
105
- return true if env[RACK_HIJACK_IO]
107
+ if env[RACK_HIJACK_IO]
108
+ # Since the app hijacked the socket, we don't know what state we're
109
+ # in and we don't know whether we can recover from it, so we don't
110
+ # catch any exception here.
111
+ #
112
+ # The Rack specification doesn't specify whether the body should
113
+ # be closed when the socket is hijacked. As of February 2 2015,
114
+ # Puma and Thin close the body, while Unicorn does not.
115
+ # However, Rack::Lock and possibly many other middlewares count
116
+ # on the body being closed, as described here:
117
+ # https://github.com/ngauthier/tubesock/issues/10#issuecomment-72539461
118
+ # So we have chosen to close the body.
119
+ body.close if body && body.respond_to?(:close)
120
+ return true
121
+ end
122
+
123
+ # Application requested a partial socket hijack.
124
+ if hijack_callback = headers[RACK_HIJACK]
125
+ # We don't catch exceptions here. EPIPE is already handled
126
+ # by ThreadHandler's #accept_and_process_next_request.
127
+ # On any other exception, we don't know what state we're
128
+ # in and we don't know whether we can recover from it.
129
+ begin
130
+ headers_output = generate_headers_array(status, headers)
131
+ headers_output << CONNECTION_CLOSE_CRLF2
132
+ connection.writev(headers_output)
133
+ connection.flush
134
+ hijacked_socket = env[RACK_HIJACK].call
135
+ hijack_callback.call(hijacked_socket)
136
+ return true
137
+ ensure
138
+ body.close if body && body.respond_to?(:close)
139
+ end
140
+ end
106
141
 
107
142
  begin
108
143
  process_body(env, connection, socket_wrapper, status.to_i, is_head_request,
109
144
  headers, body)
110
- rescue Exception => e
111
- disable_keep_alive
112
- raise
145
+ rescue => e
146
+ if !should_swallow_app_error?(e, socket_wrapper)
147
+ print_exception("Rack response body object", e)
148
+ PhusionPassenger.log_request_exception(env, e)
149
+ end
113
150
  ensure
114
- body.close if body && body.respond_to?(:close)
151
+ begin
152
+ body.close if body && body.respond_to?(:close)
153
+ rescue => e
154
+ if !should_swallow_app_error?(e, socket_wrapper)
155
+ print_exception("Rack response body object's #close method", e)
156
+ PhusionPassenger.log_request_exception(env, e)
157
+ end
158
+ end
115
159
  end
160
+ false
116
161
  ensure
117
162
  rewindable_input.close
118
163
  end
119
164
  end
120
165
 
121
166
  private
122
- # The code here is ugly, but it's necessary for performance.
123
167
  def process_body(env, connection, socket_wrapper, status, is_head_request, headers, body)
124
- if hijack_callback = headers[RACK_HIJACK]
125
- # Application requested a partial socket hijack.
126
- body = nil
127
- headers_output = generate_headers_array(status, headers)
128
- headers_output << "Connection: close\r\n"
129
- headers_output << CRLF
130
- connection.writev(headers_output)
131
- connection.flush
132
- hijacked_socket = env[RACK_HIJACK].call
133
- hijack_callback.call(hijacked_socket)
134
- true
135
- elsif body.is_a?(Array)
168
+ # Fix up incompliant body objects. Ensure that the body object
169
+ # can respond to #each.
170
+ output_body = should_output_body?(status, is_head_request)
171
+ if body.is_a?(String)
172
+ body = [body]
173
+ elsif body.nil?
174
+ body = []
175
+ elsif output_body && body.is_a?(Array)
136
176
  # The body may be an ActionController::StringCoercion::UglyBody
137
177
  # object instead of a real Array, even when #is_a? claims so.
138
- # Call #to_a just to be sure.
178
+ # Call #to_a just to be sure that connection.writev() can
179
+ # accept the body object.
139
180
  body = body.to_a
140
- output_body = should_output_body?(status, is_head_request)
141
- headers_output = generate_headers_array(status, headers)
142
- perform_keep_alive(env, headers_output)
143
- if output_body && should_add_message_length_header?(status, headers)
144
- body_size = 0
145
- body.each { |part| body_size += bytesize(part.to_s) }
146
- headers_output << CONTENT_LENGTH_HEADER_AND_SEPARATOR
147
- headers_output << body_size.to_s
148
- headers_output << CRLF
181
+ end
182
+
183
+ # Generate preliminary headers and determine whether we need to output a body.
184
+ headers_output = generate_headers_array(status, headers)
185
+
186
+
187
+ # Determine how big the body is, determine whether we should try to keep-alive
188
+ # the connection, and fix up the headers according to the situation.
189
+ #
190
+ # It may not be possible to determine the body's size (e.g. because it's streamed
191
+ # through #each). In that case we'll want to output the body with a chunked transfer
192
+ # encoding. But it matters whether the app has already chunked the body or not.
193
+ #
194
+ # Note that if the Rack response header contains "Transfer-Encoding: chunked",
195
+ # then we assume that the Rack body is already in chunked form. This is the way
196
+ # Rails streaming and Rack::Chunked::Body behave.
197
+ # The only gem that doesn't behave this way is JRuby-Rack (see
198
+ # https://blog.engineyard.com/2011/taking-stock-jruby-web-servers), but I'm not
199
+ # aware of anybody using JRuby-Rack these days.
200
+ #
201
+ # We only try to keep-alive the connection if we are able to determine ahead of
202
+ # time that the body we write out is guaranteed to match what the headers say.
203
+ # Otherwise we disable keep-alive to prevent the app from being able to mess
204
+ # up the keep-alive connection.
205
+ if header = headers[CONTENT_LENGTH_HEADER]
206
+ # Easiest case: app has a Content-Length header. The headers
207
+ # need no fixing.
208
+ message_length_type = :content_length
209
+ content_length = header.to_i
210
+ if headers.has_key?(TRANSFER_ENCODING_HEADER)
211
+ # Disallowed by the HTTP spec
212
+ raise "Response object may not contain both Content-Length and Transfer-Encoding"
149
213
  end
150
- headers_output << CRLF
151
214
  if output_body
152
- connection.writev2(headers_output, body)
153
- else
154
- connection.writev(headers_output)
215
+ if !body.is_a?(Array) || headers.has_key?(X_SENDFILE_HEADER) ||
216
+ headers.has_key?(X_ACCEL_REDIRECT_HEADER)
217
+ # If X-Sendfile or X-Accel-Redirect is set, don't check the
218
+ # body size. PassengerAgent's RequestHandler will ignore the
219
+ # body anyway. See
220
+ # ServerKit::HttpHeaderParser::processParseResult(const HttpParseResponse &)
221
+ @can_keepalive = false
222
+ else
223
+ body_size = 0
224
+ body.each { |part| body_size += bytesize(part) }
225
+ if body_size != content_length
226
+ raise "Response body size doesn't match Content-Length header: #{body_size} vs #{content_length}"
227
+ end
228
+ end
155
229
  end
156
- false
157
- elsif body.is_a?(String)
158
- output_body = should_output_body?(status, is_head_request)
159
- headers_output = generate_headers_array(status, headers)
160
- perform_keep_alive(env, headers_output)
161
- if output_body && should_add_message_length_header?(status, headers)
230
+ elsif headers.has_key?(TRANSFER_ENCODING_HEADER)
231
+ # App has a Transfer-Encoding header. We assume that the app
232
+ # has already chunked the body. The headers need no fixing.
233
+ message_length_type = :chunked_by_app
234
+ if output_body
235
+ # We have no way to determine whether the body was correct (save for
236
+ # parsing the chunking headers), so we don't keep-alive the connection
237
+ # just to be safe.
238
+ @can_keepalive = false
239
+ end
240
+ if headers.has_key?(CONTENT_LENGTH_HEADER)
241
+ # Disallowed by the HTTP spec
242
+ raise "Response object may not contain both Content-Length and Transfer-Encoding"
243
+ end
244
+ else
245
+ # The app has set neither the Content-Length nor the Transfer-Encoding
246
+ # header. This means we'll have to add one of those headers. We know exactly how
247
+ # big our body will be, so we can keep-alive the connection.
248
+ if body.is_a?(Array)
249
+ message_length_type = :content_length
250
+ content_length = 0
251
+ body.each { |part| content_length += bytesize(part.to_s) }
252
+
162
253
  headers_output << CONTENT_LENGTH_HEADER_AND_SEPARATOR
163
- headers_output << bytesize(body).to_s
254
+ headers_output << content_length.to_s
164
255
  headers_output << CRLF
256
+ else
257
+ message_length_type = :needs_chunking
258
+ headers_output << TRANSFER_ENCODING_HEADER_AND_VALUE_CRLF
165
259
  end
260
+ end
261
+
262
+ # Finalize headers data.
263
+ if @can_keepalive
166
264
  headers_output << CRLF
167
- if output_body
168
- headers_output << body
169
- end
170
- connection.writev(headers_output)
171
- false
172
265
  else
173
- output_body = should_output_body?(status, is_head_request)
174
- headers_output = generate_headers_array(status, headers)
175
- perform_keep_alive(env, headers_output)
176
- chunk = output_body && should_add_message_length_header?(status, headers)
177
- if chunk
178
- headers_output << TRANSFER_ENCODING_HEADER_AND_VALUE_CRLF2
266
+ headers_output << CONNECTION_CLOSE_CRLF2
267
+ end
268
+
269
+
270
+ # If this is a request without body, write out headers without body.
271
+ if !output_body
272
+ connection.writev(headers_output)
273
+ signal_keep_alive_allowed!
274
+ return
275
+ end
276
+
277
+ # Otherwise, write out headers and body, then verify at the end whether what
278
+ # we wrote is matches the message length. Only keep-alive connection if it
279
+ # is the case.
280
+ case message_length_type
281
+ when :content_length
282
+ if body.is_a?(Array)
283
+ connection.writev2(headers_output, body)
179
284
  else
180
- headers_output << CRLF
285
+ connection.writev(headers_output)
286
+ body.each do |part|
287
+ connection.write(part.to_s)
288
+ end
181
289
  end
290
+ when :chunked_by_app
182
291
  connection.writev(headers_output)
183
- if output_body && body
184
- begin
185
- if chunk
186
- body.each do |part|
187
- size = bytesize(part)
188
- if size != 0
189
- connection.writev(chunk_data(part, size))
190
- end
191
- end
192
- connection.write(TERMINATION_CHUNK)
193
- else
194
- body.each do |s|
195
- connection.write(s)
196
- end
197
- end
198
- rescue => e
199
- disable_keep_alive
200
- if should_reraise_app_error?(e, socket_wrapper)
201
- raise e
202
- elsif !should_swallow_app_error?(e, socket_wrapper)
203
- # Body objects can raise exceptions in #each.
204
- print_exception("Rack body object #each method", e)
205
- end
206
- false
292
+ body.each do |part|
293
+ connection.write(part.to_s)
294
+ end
295
+ when :needs_chunking
296
+ connection.writev(headers_output)
297
+ body.each do |part|
298
+ size = bytesize(part.to_s)
299
+ if size != 0
300
+ connection.writev(chunk_data(part.to_s, size))
207
301
  end
208
302
  end
209
- false
303
+ connection.write(TERMINATION_CHUNK)
210
304
  end
305
+
306
+ signal_keep_alive_allowed!
211
307
  end
212
308
 
213
309
  def generate_headers_array(status, headers)
@@ -233,33 +329,22 @@ module PhusionPassenger
233
329
  return result
234
330
  end
235
331
 
236
- def perform_keep_alive(env, headers)
237
- if @can_keepalive
238
- @keepalive_performed = true
239
- else
240
- headers << CONNECTION_CLOSE_CRLF
241
- end
242
- end
243
-
244
- def disable_keep_alive
245
- @keepalive_performed = false
246
- end
247
-
248
332
  def should_output_body?(status, is_head_request)
249
333
  return (status < 100 ||
250
334
  (status >= 200 && status != 204 && status != 205 && status != 304)) &&
251
335
  !is_head_request
252
336
  end
253
337
 
254
- def should_add_message_length_header?(status, headers)
255
- return !headers.has_key?(TRANSFER_ENCODING_HEADER) &&
256
- !headers.has_key?(CONTENT_LENGTH_HEADER)
257
- end
258
-
259
338
  def chunk_data(data, size)
260
339
  [size.to_s(16), CRLF, data, CRLF]
261
340
  end
262
341
 
342
+ # Called when body is written out successfully. Indicates that we should
343
+ # keep-alive the connection if we can.
344
+ def signal_keep_alive_allowed!
345
+ @keepalive_performed = @can_keepalive
346
+ end
347
+
263
348
  if "".respond_to?(:bytesize)
264
349
  def bytesize(str)
265
350
  str.bytesize