passenger 4.0.38 → 4.0.39

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 (39) hide show
  1. checksums.yaml +8 -8
  2. checksums.yaml.gz.asc +7 -7
  3. data.tar.gz.asc +7 -7
  4. data/CHANGELOG +24 -0
  5. data/bin/passenger +5 -0
  6. data/bin/passenger-install-apache2-module +1 -1
  7. data/bin/passenger-install-nginx-module +1 -1
  8. data/build/apache2.rb +2 -6
  9. data/build/cxx_tests.rb +5 -0
  10. data/build/packaging.rb +1 -0
  11. data/dev/list_tests.rb +36 -0
  12. data/doc/Users guide Standalone.txt +20 -5
  13. data/doc/users_guide_snippets/analysis_and_system_maintenance.txt +6 -13
  14. data/ext/common/Constants.h +1 -1
  15. data/ext/common/MessageClient.h +7 -7
  16. data/ext/common/Utils.cpp +6 -2
  17. data/ext/common/Utils/MessageIO.h +6 -11
  18. data/ext/common/agents/HelperAgent/RequestHandler.cpp +6 -3
  19. data/ext/common/agents/HelperAgent/RequestHandler.h +146 -24
  20. data/ext/ruby/extconf.rb +12 -0
  21. data/ext/ruby/passenger_native_support.c +18 -13
  22. data/helper-scripts/wsgi-loader.py +25 -2
  23. data/lib/phusion_passenger.rb +1 -1
  24. data/lib/phusion_passenger/config/about_command.rb +3 -0
  25. data/lib/phusion_passenger/rack/thread_handler_extension.rb +1 -4
  26. data/lib/phusion_passenger/request_handler/thread_handler.rb +0 -21
  27. data/lib/phusion_passenger/standalone/command.rb +10 -3
  28. data/lib/phusion_passenger/standalone/start_command.rb +4 -0
  29. data/lib/phusion_passenger/utils/tee_input.rb +82 -14
  30. data/lib/phusion_passenger/utils/terminal_choice_menu.rb +17 -2
  31. data/lib/phusion_passenger/utils/unseekable_socket.rb +0 -30
  32. data/resources/templates/error_layout.html.template +1 -1
  33. data/resources/templates/standalone/config.erb +18 -5
  34. data/test/cxx/RequestHandlerTest.cpp +410 -194
  35. data/test/integration_tests/native_packaging_spec.rb +5 -1
  36. data/test/ruby/request_handler_spec.rb +4 -71
  37. data/test/ruby/utils/tee_input_spec.rb +235 -0
  38. metadata +5 -3
  39. metadata.gz.asc +7 -7
checksums.yaml CHANGED
@@ -1,15 +1,15 @@
1
1
  ---
2
2
  !binary "U0hBMQ==":
3
3
  metadata.gz: !binary |-
4
- NWY2M2JhYzAwOWI1NGJiNDQ0YmNhYjFkZjk3ZjUxMGUxZDQxYjM2MQ==
4
+ NGY1MTk2N2EzOGQwZTdiMGFjZTNjNWE2MmEwZDQ2Yjk3NzA5YThmNQ==
5
5
  data.tar.gz: !binary |-
6
- M2FiN2YwZTg5MDYzZjRkM2ZmYjE4YWIyNTk4MDM5YWY0NmM0NWE1MA==
6
+ OTJiNzQ2OGI0ZTI5MGFjZTdlMTQxM2RhNmZlNmJmNzJjNWVhOWYzYQ==
7
7
  SHA512:
8
8
  metadata.gz: !binary |-
9
- Y2E0ZTUwNWQxZTkyZjAyYWI5MTgyMDg5MzYxNzQ2OGEwYjlmYjNiMjMyZjJm
10
- YmYyNTVlNmFlYzVlNjRkZDQxYTk2YTFjMzAzZmQyNTY5ZTJmZTg3ZmYzZmMw
11
- YmU4OGIyZGI5YjAxZjYwOTI0NmJkNjQxNGIxODI4NWJmMTdjNjc=
9
+ ZjJhNGRlMzkzZDA2ZjU3ZDI0ZjA4ZjNlZTAwOTNiZDllOWZkMzY3NGIwZjJj
10
+ ZWYzZWVhZTQzMmM2NjAyZmUyZTU2YTllYjQ4NzQ1ZGIzNGY3MGQxMjQ1Y2My
11
+ NGU3ZjlhZTc5YTljNzA1NTQ1NDdmZTkxNjc0NzAxMDdkNzRjYjk=
12
12
  data.tar.gz: !binary |-
13
- NTUwMTlmOWJjZTFhMzdhMDEwNTRkY2RjY2MxOTllOThiMGUzYjJjNGVkZDRh
14
- N2Q4MDY0ODQ4MmRhYzkwZjE4NWI5OGM0YTkzOTYwMmFiZGRlMDEwOTVkN2I2
15
- OGQxMjZkN2MzM2RjNzE0NmJmZjM4MjAyYzU1NTgwYmEwZDg4YTg=
13
+ ZGUxZWI2MGU1MGQ3Mzc4MjE0MWEyNzI2OGEyYzdlMWY5MGY2NzhiZjkxM2E4
14
+ MTRmMTM1NzY0YTcwNmNmM2ZiZTlkNDY3MTBiMWM1ZGU3OTBmNTdkMmYxZTdj
15
+ ZTU0ZWJiMTlkMmU3ZTdkOGQ3MTZiODI3YWMxMDc3NjEyMTc2OTA=
@@ -2,11 +2,11 @@
2
2
  Version: GnuPG/MacGPG2 v2.0.17 (Darwin)
3
3
  Comment: GPGTools - http://gpgtools.org
4
4
 
5
- iQEcBAABAgAGBQJTHZ8EAAoJECrHRaUKISqMsBMH+gK3TAtT8EgtFuBgW/FTpWA+
6
- GQYAdza0FIpBDcLrXuvzU/SYot8j+/FZdwYEqi1xp/IYyX6wjNsfmB9rnUKCnADK
7
- UsotDRNQ9QNitAcwy3/HYpq2qMArMrQAVUg9uQ6whL+h9UCKmIt2Behn0QL8YITY
8
- OcUuoy+0CkCcGiJuni3TEkPOpjrRZk7E2+7igTyEkN1cMseMOo06q+hCneCDJ9Uy
9
- PSxQ/BZReTBWbVHKJwwAEe3yEfZPKnjawxkvgtTsO4iHZQj2FbeS/Nnl0/WVKxzp
10
- CysaDlfon1bTDktijbN+ZhsSqavFtIVXIk9Pnba8KViVsHFn2R9/uXh5c7FkkFM=
11
- =kE6h
5
+ iQEcBAABAgAGBQJTKAuvAAoJECrHRaUKISqMqQ8IAKfMz5RiUwwOTbkRHfbPIg7C
6
+ ScvfeE9smkw4aTjIhu0eOnt34eUKzpJrFtsZT3pYNMqhJ0bVefE/AuFJE9ZiWLUk
7
+ +1XyNOgYwS7V/9390c9P0/BsBySkXMTQmOYwofjP5lF/lG1tEUV9/RG3u3yNdAV0
8
+ bkcwI4RRakA042dySBircovPZr3Oy1Tse82kFK3m1NP3YA2pWNyMKySdEVNyyh5U
9
+ LqSEgF39cYBCOQ19sYbDORMeeB07snXUNLk2wdhFeWvB11T5SzaKgNM5KpBro4Lp
10
+ BdYuVxh7o9tlSHBfyN9zCSYcmrm4M+qP0oLge3JoCormhSMYf+MZMTCPOauT94U=
11
+ =m76H
12
12
  -----END PGP SIGNATURE-----
data.tar.gz.asc CHANGED
@@ -2,11 +2,11 @@
2
2
  Version: GnuPG/MacGPG2 v2.0.17 (Darwin)
3
3
  Comment: GPGTools - http://gpgtools.org
4
4
 
5
- iQEcBAABAgAGBQJTHZ8EAAoJECrHRaUKISqM5lYH/3XJx9eqN+CwmPt3TuntozWD
6
- cG+UX9LbGUSol+BGvE5wwuKfa10n0Gt7o8/OjsiWgmeI2RcgTrP8YcLSH/dsqRNB
7
- DZdzpdq+03fJrgysYriG1e2UKhoG0BvY89Ux9ZncB3iiyteF+TMVdswdgwJiCVLF
8
- miWYlX7efLoQu9ii7iDYWQq3nCImeefl3ei6lopS7lpAEdWplTQf2dUEGzJyCzd2
9
- r1F2pN4gIUFDE/hOg8g/bQIs/HeZq3hlcDKJ3nXWpiSlgpT6Ywrs0kuIUE2KGDzJ
10
- gW9SEsBgnV5RuC0kIYsVBGv+//hxoCvj22eZUz1cfvhZagAHzcL1n+t7+LRHkuY=
11
- =28wv
5
+ iQEcBAABAgAGBQJTKAuvAAoJECrHRaUKISqMVwUIALZ5285fqasa+0WXQCSGqoms
6
+ 8gU5FXOvKzhgkNrC6sKVt3d6hrXsgnd+ofgy8UzedVidkBTk6fWWCqaUAnccbkVM
7
+ yznhoYdTVASa4pnXSnkS+cmEShMGQy9OoIeZpQA3pGYd5f1BQUJLzJPR8nvYDSOA
8
+ VmG1z4qFa8o+WP2sHl8mOt1wsvOHMjsTXbJx/YVzvdymRUg2hZkIk0ll+XK1dqoL
9
+ Q92FkTw+zLLLqVTyNl/HvQWpzgj3xhMiuCbxTHm/7/4RXA9Xh9JziUkQjXU5iNcv
10
+ e1CZRz2+UhhN8fsBLIQRGdqe0qiInqbGSFlff9qibFgTtdihGcEwaudjZq/XODI=
11
+ =w0gy
12
12
  -----END PGP SIGNATURE-----
data/CHANGELOG CHANGED
@@ -1,3 +1,27 @@
1
+ Release 4.0.39
2
+ --------------
3
+
4
+ * Fixed a crash that could happen if the client disconnects while a chunked
5
+ response is being sent. Fixes issue #1062.
6
+ * In Phusion Passenger Standalone, it is now possible to customize the Nginx
7
+ configuration file on Heroku. It is now also possible to permanently apply
8
+ changes to the Nginx configuration file, surviving upgrades. Please refer
9
+ to the "Advanced configuration" section of the Phusion Passenger Standalone
10
+ manual for more information.
11
+ * The programming language selection menu in passenger-install-apache2-module
12
+ and passenger-install-nginx-module only works on terminals that support
13
+ UTF-8 and that have a UTF-8 capable font. To cater to users who cannot meet
14
+ these requirements (e.g. PuTTY users using any of the default Windows fonts),
15
+ it is now possible to switch the menu to a plain text mode by pressing '!'.
16
+ Fixes issue #1066.
17
+ * Fixed printing UTF-8 characters in log files in Phusion Passenger Standalone.
18
+ * It is now possible to dump live backtraces of Python apps through the
19
+ 'SIGABRT' signal.
20
+ * Fixed closing of file descriptors on OS X 10.9.
21
+ * Fixed compilation problems with Apple Clang 503.0.38 on OS X.
22
+ * Fixed compilation of native_support on Rubinius.
23
+
24
+
1
25
  Release 4.0.38
2
26
  --------------
3
27
 
@@ -36,4 +36,9 @@ PhusionPassenger.locate_directories
36
36
  PhusionPassenger.require_passenger_lib 'standalone/main'
37
37
 
38
38
  STDOUT.sync = STDERR.sync = true
39
+
40
+ # Fixes https://github.com/phusion/passenger-ruby-heroku-demo/issues/11
41
+ STDOUT.binmode
42
+ STDERR.binmode
43
+
39
44
  PhusionPassenger::Standalone::Main.run!(ARGV)
@@ -157,7 +157,7 @@ private
157
157
  puts
158
158
  if interactive?
159
159
  puts "Use <space> to select."
160
- puts "<dgray>If the menu doesn't display correctly, ensure that your terminal supports UTF-8.</dgray>"
160
+ puts "<dgray>If the menu doesn't display correctly, press '!'</dgray>"
161
161
  else
162
162
  puts "Override selection with --languages."
163
163
  end
@@ -174,7 +174,7 @@ private
174
174
  puts
175
175
  if interactive?
176
176
  puts "Use <space> to select."
177
- puts "<dgray>If the menu doesn't display correctly, ensure that your terminal supports UTF-8.</dgray>"
177
+ puts "<dgray>If the menu doesn't display correctly, press '!'</dgray>"
178
178
  else
179
179
  puts "Override selection with --languages."
180
180
  end
@@ -106,10 +106,8 @@ APACHE2_MODULE_INPUT_FILES.each_pair do |target, sources|
106
106
  object_basename = File.basename(target)
107
107
  object_filename = APACHE2_OUTPUT_DIR + object_basename
108
108
  compile_cxx(sources[0],
109
- "#{EXTRA_PRE_CXXFLAGS} " <<
110
109
  "#{APACHE2_MODULE_CXXFLAGS} " <<
111
- "-o #{object_filename} " <<
112
- "#{EXTRA_CXXFLAGS}")
110
+ "-o #{object_filename}")
113
111
  end
114
112
  end
115
113
 
@@ -140,10 +138,8 @@ end
140
138
 
141
139
  file APACHE2_MOD_PASSENGER_O => ['ext/apache2/mod_passenger.c'] do
142
140
  compile_c('ext/apache2/mod_passenger.c',
143
- "#{EXTRA_PRE_CFLAGS} " <<
144
141
  "#{APACHE2_MODULE_CFLAGS} " <<
145
- "-o #{APACHE2_MOD_PASSENGER_O} " <<
146
- "#{EXTRA_CFLAGS}")
142
+ "-o #{APACHE2_MOD_PASSENGER_O}")
147
143
  end
148
144
 
149
145
  task :clean => 'apache2:clean'
@@ -233,6 +233,11 @@ task 'test:cxx' => dependencies do
233
233
  abort "You cannot set both REPEAT=1 and GDB=1."
234
234
  end
235
235
  sh "cd test && while #{command}; do echo -------------------------------------------; done"
236
+ elsif boolean_option('REPEAT_FOREVER')
237
+ if boolean_option('GDB')
238
+ abort "You cannot set both REPEAT_FOREVER=1 and GDB=1."
239
+ end
240
+ sh "cd test && while true; do #{command}; echo -------------------------------------------; done"
236
241
  else
237
242
  sh "cd test && exec #{command}"
238
243
  end
@@ -325,6 +325,7 @@ task 'package:update_homebrew' do
325
325
  abort("Unable to substitute Homebrew formula tarball filename")
326
326
  formula.gsub!(/sha1 .*/, "sha1 '#{sha1}'") ||
327
327
  abort("Unable to substitute Homebrew formula SHA-1")
328
+ formula.gsub!(/^ bottle do.*?end\n *\n/m, '')
328
329
  necessary_dirs = ORIG_TARBALL_FILES.call.map{ |filename| filename.split("/").first }.uniq
329
330
  necessary_dirs -= Packaging::HOMEBREW_EXCLUDE
330
331
  necessary_dirs += ["buildout"]
@@ -0,0 +1,36 @@
1
+ #!/usr/bin/env ruby
2
+ # Lists the test names in the given .cpp test file.
3
+ require_relative '../lib/phusion_passenger/utils/ansi_colors'
4
+
5
+ include PhusionPassenger::Utils::AnsiColors
6
+
7
+ def extract_category_name(occurrence)
8
+ occurrence =~ / (.+) /
9
+ return $1
10
+ end
11
+
12
+ def extract_test_name(occurrence)
13
+ occurrence = occurrence.sub(/.*?\((.+)\).*/m, '\1')
14
+ occurrence.gsub!(/"\n[ \t]*"/m, '')
15
+ occurrence.sub!(/\A"/, '')
16
+ occurrence.sub!(/"\Z/, '')
17
+ return occurrence
18
+ end
19
+
20
+ def start(filename)
21
+ STDOUT.write(DEFAULT_TERMINAL_COLOR)
22
+ begin
23
+ occurrences = File.read(filename).scan(%r{/\*\*\*\*\* .+? \*\*\*\*\*/|set_test_name\(.+?\);}m)
24
+ occurrences.each do |occurrence|
25
+ if occurrence =~ %r{\A/}
26
+ puts ansi_colorize("<b>" + extract_category_name(occurrence) + "</b>")
27
+ else
28
+ puts " " + extract_test_name(occurrence)
29
+ end
30
+ end
31
+ ensure
32
+ STDOUT.write(RESET)
33
+ end
34
+ end
35
+
36
+ start(ARGV[0])
@@ -57,6 +57,9 @@ Most configuration is done by customizing the arguments passed to the `passenger
57
57
  passenger start --ssl --ssl-certificate ... --ssl-certificate-key ... --ssl-port 3001
58
58
  ------------------------------------------------------------------------------
59
59
 
60
+ `--nginx-config-template`::
61
+ Specifies the Nginx configuration template file to use. See <<advanced_configuration,Advanced configuration>>.
62
+
60
63
  See `--help` for all available options.
61
64
 
62
65
 
@@ -88,6 +91,9 @@ The following configuration options are supported:
88
91
  +
89
92
  When in mass deployment mode, you will probably want to set a different `port` too. If you don't, and you end up in a situation in which a port is used for both HTTP and HTTPS traffic, then the builtin Nginx core will abort with an error.
90
93
 
94
+ `nginx_config_template`::
95
+ Equivalent to the `--nginx-config-template` command line option.
96
+
91
97
  Here is an example configuration file:
92
98
 
93
99
  [source,javascript]
@@ -104,20 +110,29 @@ Here is an example configuration file:
104
110
 
105
111
  [[advanced_configuration]]
106
112
  === Advanced configuration
113
+ :version: 4.0.39
114
+ include::users_guide_snippets/since_version.txt[]
107
115
 
108
116
  Phusion Passenger Standalone is built on the same technology that powers link:Users%20guide%20Nginx.html[Phusion Passenger for Nginx], so any configuration option supported by Phusion Passenger for Nginx can be applied to Standalone as well. You can do this by editing the Standalone configuration template directly.
109
117
 
110
- First, go to the directory where Phusion Passenger is installed:
118
+ First, create a copy of the default Phusion Passenger Nginx configuration template file:
111
119
 
112
120
  -------------------------------------
113
- cd $(passenger-config --root)
121
+ cp $(passenger-config about resourcesdir)/templates/standalone/config.erb nginx.conf.erb
114
122
  -------------------------------------
115
123
 
116
- Then open the file `resources/templates/standalone/config.erb`.
124
+ Then open `nginx.conf.erb` and modify it as you see fit. The file is a normal Nginx configuration file, in the ERB template format.
125
+
126
+ Every time you start Standalone, you must pass the `--nginx-config-template` parameter, which tells Standalone to use your specific Nginx configuration template file. For example:
127
+
128
+ [source,sh]
129
+ ------------------------------------------
130
+ passenger start --nginx-config-template nginx.config.erb
131
+ ------------------------------------------
117
132
 
118
- NOTE: If you installed Phusion Passenger using the Debian or Ubuntu packages, then the filename is `/usr/share/passenger/templates/standalone/config.erb` or `/usr/share/passenger-enterprise/templates/standalone/config.erb`.
133
+ Alternatively, if you don't want to pass this parameter every time, you can also set the `nginx_config_template` option in <<config_file,passenger-standalone.json>>.
119
134
 
120
- Please note that changes to this file only last until you reinstall or upgrade Phusion Passenger. We are currently working on a mechanism for permanently editing the configuration file.
135
+ NOTE: The original configuration template file may change from time to time, e.g. because new features are introduced into Phusion Passenger. If your configuration template file does not contain the required changes, then these new features may not work properly. In the worst case, Standalone might even malfunction. Therefore, every time you upgrade Phusion Passenger, you should check whether the original configuration template file has changed, and merge back any changes into your own file.
121
136
 
122
137
 
123
138
  == Using Passenger Standalone in production
@@ -128,20 +128,13 @@ The most likely reason why a spike occurs is because your application is frozen,
128
128
  [[debugging_frozen]]
129
129
  === Debugging frozen applications ===
130
130
 
131
- If one of your application instances is frozen (stopped responding), then you
131
+ If one of your application processes is frozen (stopped responding), then you
132
132
  can figure out where it is frozen by killing it with 'SIGABRT'. This will cause the
133
- application to raise an exception, with a backtrace.
134
-
135
- The exception (with full backtrace information) is normally logged into the web server
136
- error log. But if your application or if its web framework has its own exception logging
137
- routines, then exceptions might be logged into the application's log files instead.
138
- This is the case with Ruby on Rails. So if you kill a Ruby on Rails application with
139
- 'SIGABRT', please check the application's 'production.log' first (assuming that you're
140
- running it in a 'production' environment). If you don't see a backtrace there, check
141
- the web server error log.
142
-
143
- NOTE: It is safe to kill application instances, even in live environments. Phusion Passenger
144
- will restart killed application instances, as if nothing bad happened.
133
+ processs to print a backtrace, after which it aborts. The backtrace information is
134
+ logged into the web server error log file.
135
+
136
+ In case of Ruby applications, you can also send the 'SIGQUIT' signal to have it print
137
+ a backtrace without aborting.
145
138
 
146
139
 
147
140
  === Accessing individual application processes ===
@@ -88,7 +88,7 @@
88
88
 
89
89
  #define NGINX_DOC_URL "http://www.modrails.com/documentation/Users%20guide%20Nginx.html"
90
90
 
91
- #define PASSENGER_VERSION "4.0.38"
91
+ #define PASSENGER_VERSION "4.0.39"
92
92
 
93
93
  #define POOL_HELPER_THREAD_STACK_SIZE 262144
94
94
 
@@ -1,6 +1,6 @@
1
1
  /*
2
2
  * Phusion Passenger - https://www.phusionpassenger.com/
3
- * Copyright (c) 2010 Phusion
3
+ * Copyright (c) 2010-2014 Phusion
4
4
  *
5
5
  * "Phusion Passenger" is a trademark of Hongli Lai & Ninh Bui.
6
6
  *
@@ -29,11 +29,11 @@
29
29
  #include <boost/bind.hpp>
30
30
  #include <string>
31
31
 
32
- #include "StaticString.h"
33
- #include "Exceptions.h"
34
- #include "Utils/MessageIO.h"
35
- #include "Utils/IOUtils.h"
36
- #include "Utils/ScopeGuard.h"
32
+ #include <StaticString.h>
33
+ #include <Exceptions.h>
34
+ #include <Utils/MessageIO.h>
35
+ #include <Utils/IOUtils.h>
36
+ #include <Utils/ScopeGuard.h>
37
37
 
38
38
 
39
39
  namespace Passenger {
@@ -241,7 +241,7 @@ public:
241
241
  va_start(ap, name);
242
242
  try {
243
243
  try {
244
- writeArrayMessage(fd, name, ap);
244
+ writeArrayMessageVA(fd, name, ap);
245
245
  } catch (const SystemException &) {
246
246
  autoDisconnect();
247
247
  throw;
@@ -1031,13 +1031,17 @@ getFileDescriptorLimit() {
1031
1031
  }
1032
1032
 
1033
1033
  long result;
1034
- if (sysconfResult > rlimitResult) {
1034
+ // OS X 10.9 returns LLONG_MAX. It doesn't make sense
1035
+ // to use that result so we limit ourselves to the
1036
+ // sysconf result.
1037
+ if (rlimitResult >= INT_MAX || sysconfResult > rlimitResult) {
1035
1038
  result = sysconfResult;
1036
1039
  } else {
1037
1040
  result = rlimitResult;
1038
1041
  }
1042
+
1039
1043
  if (result < 0) {
1040
- // Both calls returned errors.
1044
+ // Unable to query the file descriptor limit.
1041
1045
  result = 9999;
1042
1046
  } else if (result < 2) {
1043
1047
  // The calls reported broken values.
@@ -1,6 +1,6 @@
1
1
  /*
2
2
  * Phusion Passenger - https://www.phusionpassenger.com/
3
- * Copyright (c) 2011 Phusion
3
+ * Copyright (c) 2011-2014 Phusion
4
4
  *
5
5
  * "Phusion Passenger" is a trademark of Hongli Lai & Ninh Bui.
6
6
  *
@@ -492,7 +492,7 @@ writeArrayMessage(int fd, const StaticString args[], unsigned int nargs, unsigne
492
492
  }
493
493
 
494
494
  inline void
495
- writeArrayMessage(int fd, const StaticString &name, va_list &ap, unsigned long long *timeout = NULL) {
495
+ writeArrayMessageVA(int fd, const StaticString &name, va_list &ap, unsigned long long *timeout = NULL) {
496
496
  StaticString args[10];
497
497
  unsigned int nargs = 1;
498
498
  bool done = false;
@@ -547,16 +547,11 @@ struct _VaGuard {
547
547
  * arguments as message elements. The list must be terminated with a NULL.
548
548
  */
549
549
  inline void
550
- writeArrayMessage(int fd, const StaticString &name, ...) {
550
+ writeArrayMessage(int fd, const char *name, ...) {
551
551
  va_list ap;
552
552
  va_start(ap, name);
553
553
  _VaGuard guard(ap);
554
- writeArrayMessage(fd, name, ap);
555
- }
556
-
557
- inline void
558
- writeArrayMessage(int fd, const char *name) {
559
- abort();
554
+ writeArrayMessageVA(fd, name, ap);
560
555
  }
561
556
 
562
557
  /** Version of writeArrayMessage() that accepts a variadic list of 'const char *'
@@ -564,11 +559,11 @@ writeArrayMessage(int fd, const char *name) {
564
559
  * with a NULL.
565
560
  */
566
561
  inline void
567
- writeArrayMessage(int fd, unsigned long long *timeout, const StaticString &name, ...) {
562
+ writeArrayMessage(int fd, unsigned long long *timeout, const char *name, ...) {
568
563
  va_list ap;
569
564
  va_start(ap, name);
570
565
  _VaGuard guard(ap);
571
- writeArrayMessage(fd, name, ap, timeout);
566
+ writeArrayMessageVA(fd, name, ap, timeout);
572
567
  }
573
568
 
574
569
  /**
@@ -165,14 +165,17 @@ void
165
165
  Client::onAppInputChunkEnd(void *userData) {
166
166
  Client *client = (Client *) userData;
167
167
  assert(client != NULL);
168
- assert(client->requestHandler != NULL);
169
- client->requestHandler->onAppInputChunkEnd(client->shared_from_this());
168
+ // onAppInputChunk() could have triggered an error which caused a disconnect.
169
+ if (client->connected()) {
170
+ client->requestHandler->onAppInputChunkEnd(client->shared_from_this());
171
+ }
170
172
  }
171
173
 
172
174
  void
173
175
  Client::onAppInputError(const EventedBufferedInputPtr &source, const char *message, int errnoCode) {
174
176
  Client *client = (Client *) source->userData;
175
- if (client != NULL) {
177
+ // onAppInputChunk() could have triggered an error which caused a disconnect.
178
+ if (client != NULL && client->connected()) {
176
179
  client->requestHandler->onAppInputError(client->shared_from_this(), message, errnoCode);
177
180
  }
178
181
  }
@@ -72,6 +72,40 @@
72
72
  (o)
73
73
  clientOutputWatcher
74
74
 
75
+
76
+
77
+ REQUEST BODY HANDLING STRATEGIES
78
+
79
+ This table describes how we should handle the request body (the part in the request
80
+ that comes after the request header, and may include WebSocket data), given various
81
+ factors. Strategies that are listed first have precedence.
82
+
83
+ Method 'Upgrade' 'Content-Length' or Application Action
84
+ header 'Transfer-Encoding' socket
85
+ present? header present? protocol
86
+ ---------------------------------------------------------------------------------------------
87
+
88
+ GET/HEAD - Y - Reject request[1]
89
+ Other Y - - Reject request[2]
90
+
91
+ - N N http_session Set requestBodyLength=0, keep socket open when done forwarding.
92
+ GET/HEAD Y N http_session Set requestBodyLength=-1, keep socket open when done forwarding.
93
+ Other N Y http_session Keep socket open when done forwarding. If Transfer-Encoding is
94
+ chunked, rechunck the body during forwarding.
95
+
96
+ - N N session Set requestBodyLength=0, half-close app socket when done forwarding.
97
+ GET/HEAD Y N session Set requestBodyLength=-1, half-close app socket when done forwarding.
98
+ Other N Y session Half-close app socket when done forwarding.
99
+ ---------------------------------------------------------------------------------------------
100
+
101
+ [1] Supporting situations in which there is both an HTTP request body and WebSocket data
102
+ is way too complicated. The RequestHandler code is complicated enough as it is.
103
+ Furthermore, GET requests with a body, although legal, are almost nonexistent and
104
+ support by other servers are shaky at best. For these reasons, we don't bother
105
+ supporting GET requests with body at all.
106
+ [2] RFC 6455 states that WebSocket upgrades may only happen over GET requests.
107
+ We don't bother supporting non-WebSocket upgrades.
108
+
75
109
  */
76
110
 
77
111
  #ifndef _PASSENGER_REQUEST_HANDLER_H_
@@ -180,10 +214,11 @@ private:
180
214
  state = DISCONNECTED;
181
215
  backgroundOperations = 0;
182
216
  requestBodyIsBuffered = false;
217
+ requestIsChunked = false;
183
218
  freeBufferedConnectPassword();
184
219
  connectedAt = 0;
185
- contentLength = 0;
186
- clientBodyAlreadyRead = 0;
220
+ requestBodyLength = 0;
221
+ requestBodyAlreadyRead = 0;
187
222
  checkoutSessionAfterCommit = false;
188
223
  stickySession = false;
189
224
  sessionCheckedOut = false;
@@ -268,8 +303,19 @@ public:
268
303
  ev::timer timeoutTimer;
269
304
 
270
305
  ev_tstamp connectedAt;
271
- long long contentLength;
272
- unsigned long long clientBodyAlreadyRead;
306
+ /** The size of the request body. The request body is the part that comes
307
+ * after the request headers, which may be the HTTP request message body,
308
+ * but may also be any other arbitrary data that is sent over the request
309
+ * socket (e.g. WebSocket data).
310
+ *
311
+ * Possible values:
312
+ *
313
+ * -1: infinite. Should keep forwarding client body until end of stream.
314
+ * 0: no client body. Should stop after sending headers to application.
315
+ * >0: Should forward exactly this many bytes of the client body.
316
+ */
317
+ long long requestBodyLength;
318
+ unsigned long long requestBodyAlreadyRead;
273
319
  Options options;
274
320
  ScgiRequestParser scgiParser;
275
321
  SessionPtr session;
@@ -283,6 +329,7 @@ public:
283
329
  } scopeLogs;
284
330
  unsigned int sessionCheckoutTry;
285
331
  bool requestBodyIsBuffered;
332
+ bool requestIsChunked;
286
333
  bool sessionCheckedOut;
287
334
  bool checkoutSessionAfterCommit;
288
335
  bool stickySession;
@@ -470,9 +517,13 @@ public:
470
517
  }
471
518
  }
472
519
 
520
+ /**
521
+ * Checks whether we should half-close the application socket after forwarding
522
+ * the request. HTTP does not formally support half-closing, and Node.js treats a
523
+ * half-close as a full close, so we only half-close session sockets, not
524
+ * HTTP sockets.
525
+ */
473
526
  bool shouldHalfCloseWrite() const {
474
- // Many broken HTTP servers consider a half close to be a full close, so don't
475
- // half close HTTP sessions.
476
527
  return session->getProtocol() == "session";
477
528
  }
478
529
 
@@ -532,8 +583,9 @@ public:
532
583
  }
533
584
  stream
534
585
  << indent << "requestBodyIsBuffered = " << boolStr(requestBodyIsBuffered) << "\n"
535
- << indent << "contentLength = " << contentLength << "\n"
536
- << indent << "clientBodyAlreadyRead = " << clientBodyAlreadyRead << "\n"
586
+ << indent << "requestIsChunked = " << boolStr(requestIsChunked) << "\n"
587
+ << indent << "requestBodyLength = " << requestBodyLength << "\n"
588
+ << indent << "requestBodyAlreadyRead = " << requestBodyAlreadyRead << "\n"
537
589
  << indent << "clientInput = " << clientInput.get() << " " << clientInput->inspect() << "\n"
538
590
  << indent << "clientInput started = " << boolStr(clientInput->isStarted()) << "\n"
539
591
  << indent << "clientBodyBuffer started = " << boolStr(clientBodyBuffer->isStarted()) << "\n"
@@ -1057,7 +1109,7 @@ private:
1057
1109
  // Process chunked transfer encoding.
1058
1110
  Header transferEncoding = lookupHeader(headerData, "Transfer-Encoding", "transfer-encoding");
1059
1111
  if (!transferEncoding.empty() && transferEncoding.value == "chunked") {
1060
- P_TRACE(3, "Response with chunked transfer encoding detected.");
1112
+ RH_TRACE(client, 3, "Response with chunked transfer encoding detected.");
1061
1113
  client->chunkedResponse = true;
1062
1114
  removeHeader(headerData, transferEncoding);
1063
1115
  }
@@ -1673,6 +1725,72 @@ private:
1673
1725
  return modified;
1674
1726
  }
1675
1727
 
1728
+ void reportBadRequestAndDisconnect(const ClientPtr &client, const char *message) {
1729
+ writeSimpleResponse(client, message, 400);
1730
+ if (client->connected()) {
1731
+ disconnectWithError(client, message);
1732
+ }
1733
+ }
1734
+
1735
+ void checkAndInternalizeRequestHeaders(const ClientPtr &client) {
1736
+ ScgiRequestParser &parser = client->scgiParser;
1737
+ StaticString requestMethod = parser.getHeader("REQUEST_METHOD");
1738
+
1739
+ if (requestMethod.empty()) {
1740
+ reportBadRequestAndDisconnect(client, "Bad request (no request method given)");
1741
+ return;
1742
+ }
1743
+
1744
+ // Check Content-Length and Transfer-Encoding.
1745
+ long long contentLength = getULongLongOption(client, "CONTENT_LENGTH");
1746
+ StaticString transferEncoding = parser.getHeader("HTTP_TRANSFER_ENCODING");
1747
+ if (contentLength != -1 && !transferEncoding.empty()) {
1748
+ reportBadRequestAndDisconnect(client, "Bad request (request may not contain both Content-Length and Transfer-Encoding)");
1749
+ return;
1750
+ }
1751
+ if (!transferEncoding.empty() && transferEncoding != "chunked") {
1752
+ reportBadRequestAndDisconnect(client, "Bad request (only Transfer-Encoding chunked is supported)");
1753
+ return;
1754
+ }
1755
+ // According to the HTTP/1.1 spec, Content-Length may not be 0.
1756
+ // We could reject the request, but some important HTTP clients are broken
1757
+ // (*cough* Ruby Net::HTTP *cough*) and fixing them is too much of
1758
+ // a pain, so we choose support it.
1759
+ if (contentLength == 0) {
1760
+ contentLength = -1;
1761
+ assert(transferEncoding.empty());
1762
+ }
1763
+
1764
+ StaticString upgrade = parser.getHeader("HTTP_UPGRADE");
1765
+ const bool requestIsGetOrHead = requestMethod == "GET" || requestMethod == "HEAD";
1766
+ const bool requestBodyOffered = contentLength != -1 || !transferEncoding.empty();
1767
+
1768
+ // Reject requests that have a request body even though it's not allowed,
1769
+ // and requests that have an Upgrade header even though it's not allowed.
1770
+ if (requestIsGetOrHead) {
1771
+ if (requestBodyOffered) {
1772
+ reportBadRequestAndDisconnect(client, "Bad request (GET and HEAD requests may not contain a request body)");
1773
+ return;
1774
+ }
1775
+ } else {
1776
+ if (!upgrade.empty()) {
1777
+ reportBadRequestAndDisconnect(client, "Bad request (Upgrade header is only allowed for non-GET and non-HEAD requests)");
1778
+ return;
1779
+ }
1780
+ }
1781
+
1782
+ if (!requestBodyOffered) {
1783
+ if (upgrade.empty()) {
1784
+ client->requestBodyLength = 0;
1785
+ } else {
1786
+ client->requestBodyLength = -1;
1787
+ }
1788
+ } else {
1789
+ client->requestBodyLength = contentLength;
1790
+ client->requestIsChunked = !transferEncoding.empty();
1791
+ }
1792
+ }
1793
+
1676
1794
  static void fillPoolOption(const ClientPtr &client, StaticString &field, const StaticString &name) {
1677
1795
  ScgiRequestParser::const_iterator it = client->scgiParser.getHeaderIterator(name);
1678
1796
  if (it != client->scgiParser.end()) {
@@ -1903,7 +2021,11 @@ private:
1903
2021
  * onClientData exits.
1904
2022
  */
1905
2023
  parser.rebuildData(modified);
1906
- client->contentLength = getULongLongOption(client, "CONTENT_LENGTH");
2024
+
2025
+ checkAndInternalizeRequestHeaders(client);
2026
+ if (!client->connected()) {
2027
+ return consumed;
2028
+ }
1907
2029
  fillPoolOptions(client);
1908
2030
  if (!client->connected()) {
1909
2031
  return consumed;
@@ -1918,7 +2040,7 @@ private:
1918
2040
  client->state = Client::BUFFERING_REQUEST_BODY;
1919
2041
  client->requestBodyIsBuffered = true;
1920
2042
  client->beginScopeLog(&client->scopeLogs.bufferingRequestBody, "buffering request body");
1921
- if (client->contentLength == 0) {
2043
+ if (client->requestBodyLength == 0) {
1922
2044
  client->clientInput->stop();
1923
2045
  state_bufferingRequestBody_onClientEof(client);
1924
2046
  return 0;
@@ -1944,10 +2066,10 @@ private:
1944
2066
  state_bufferingRequestBody_verifyInvariants(client);
1945
2067
  assert(!client->clientBodyBuffer->isCommittingToDisk());
1946
2068
 
1947
- if (client->contentLength >= 0) {
2069
+ if (client->requestBodyLength >= 0) {
1948
2070
  size = std::min<unsigned long long>(
1949
2071
  size,
1950
- (unsigned long long) client->contentLength - client->clientBodyAlreadyRead
2072
+ (unsigned long long) client->requestBodyLength - client->requestBodyAlreadyRead
1951
2073
  );
1952
2074
  }
1953
2075
 
@@ -1957,13 +2079,13 @@ private:
1957
2079
  client->backgroundOperations++; // TODO: figure out whether this is necessary
1958
2080
  client->clientInput->stop();
1959
2081
  }
1960
- client->clientBodyAlreadyRead += size;
2082
+ client->requestBodyAlreadyRead += size;
1961
2083
 
1962
2084
  RH_TRACE(client, 3, "Buffered " << size << " bytes of client body data; total=" <<
1963
- client->clientBodyAlreadyRead << ", content-length=" << client->contentLength);
1964
- assert(client->contentLength == -1 || client->clientBodyAlreadyRead <= (unsigned long long) client->contentLength);
2085
+ client->requestBodyAlreadyRead << ", content-length=" << client->requestBodyLength);
2086
+ assert(client->requestBodyLength == -1 || client->requestBodyAlreadyRead <= (unsigned long long) client->requestBodyLength);
1965
2087
 
1966
- if (client->contentLength >= 0 && client->clientBodyAlreadyRead == (unsigned long long) client->contentLength) {
2088
+ if (client->requestBodyLength >= 0 && client->requestBodyAlreadyRead == (unsigned long long) client->requestBodyLength) {
1967
2089
  if (client->clientBodyBuffer->isCommittingToDisk()) {
1968
2090
  RH_TRACE(client, 3, "Done buffering request body, but clientBodyBuffer not yet done committing data to disk; waiting until it's done");
1969
2091
  client->checkoutSessionAfterCommit = true;
@@ -2327,7 +2449,7 @@ private:
2327
2449
  client->state = Client::FORWARDING_BODY_TO_APP;
2328
2450
  if (client->requestBodyIsBuffered) {
2329
2451
  client->clientBodyBuffer->start();
2330
- } else if (client->contentLength == 0) {
2452
+ } else if (client->requestBodyLength == 0) {
2331
2453
  state_forwardingBodyToApp_onClientEof(client);
2332
2454
  } else {
2333
2455
  client->clientInput->start();
@@ -2341,10 +2463,10 @@ private:
2341
2463
  state_forwardingBodyToApp_verifyInvariants(client);
2342
2464
  assert(!client->requestBodyIsBuffered);
2343
2465
 
2344
- if (client->contentLength >= 0) {
2466
+ if (client->requestBodyLength >= 0) {
2345
2467
  size = std::min<unsigned long long>(
2346
2468
  size,
2347
- (unsigned long long) client->contentLength - client->clientBodyAlreadyRead
2469
+ (unsigned long long) client->requestBodyLength - client->requestBodyAlreadyRead
2348
2470
  );
2349
2471
  }
2350
2472
 
@@ -2374,12 +2496,12 @@ private:
2374
2496
  }
2375
2497
  return 0;
2376
2498
  } else {
2377
- client->clientBodyAlreadyRead += ret;
2499
+ client->requestBodyAlreadyRead += ret;
2378
2500
 
2379
2501
  RH_TRACE(client, 3, "Managed to forward " << ret << " bytes; total=" <<
2380
- client->clientBodyAlreadyRead << ", content-length=" << client->contentLength);
2381
- assert(client->contentLength == -1 || client->clientBodyAlreadyRead <= (unsigned long long) client->contentLength);
2382
- if (client->contentLength >= 0 && client->clientBodyAlreadyRead == (unsigned long long) client->contentLength) {
2502
+ client->requestBodyAlreadyRead << ", content-length=" << client->requestBodyLength);
2503
+ assert(client->requestBodyLength == -1 || client->requestBodyAlreadyRead <= (unsigned long long) client->requestBodyLength);
2504
+ if (client->requestBodyLength >= 0 && client->requestBodyAlreadyRead == (unsigned long long) client->requestBodyLength) {
2383
2505
  client->clientInput->stop();
2384
2506
  state_forwardingBodyToApp_onClientEof(client);
2385
2507
  }