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.
- checksums.yaml +8 -8
- checksums.yaml.gz.asc +7 -7
- data.tar.gz.asc +7 -7
- data/CHANGELOG +24 -0
- data/bin/passenger +5 -0
- data/bin/passenger-install-apache2-module +1 -1
- data/bin/passenger-install-nginx-module +1 -1
- data/build/apache2.rb +2 -6
- data/build/cxx_tests.rb +5 -0
- data/build/packaging.rb +1 -0
- data/dev/list_tests.rb +36 -0
- data/doc/Users guide Standalone.txt +20 -5
- data/doc/users_guide_snippets/analysis_and_system_maintenance.txt +6 -13
- data/ext/common/Constants.h +1 -1
- data/ext/common/MessageClient.h +7 -7
- data/ext/common/Utils.cpp +6 -2
- data/ext/common/Utils/MessageIO.h +6 -11
- data/ext/common/agents/HelperAgent/RequestHandler.cpp +6 -3
- data/ext/common/agents/HelperAgent/RequestHandler.h +146 -24
- data/ext/ruby/extconf.rb +12 -0
- data/ext/ruby/passenger_native_support.c +18 -13
- data/helper-scripts/wsgi-loader.py +25 -2
- data/lib/phusion_passenger.rb +1 -1
- data/lib/phusion_passenger/config/about_command.rb +3 -0
- data/lib/phusion_passenger/rack/thread_handler_extension.rb +1 -4
- data/lib/phusion_passenger/request_handler/thread_handler.rb +0 -21
- data/lib/phusion_passenger/standalone/command.rb +10 -3
- data/lib/phusion_passenger/standalone/start_command.rb +4 -0
- data/lib/phusion_passenger/utils/tee_input.rb +82 -14
- data/lib/phusion_passenger/utils/terminal_choice_menu.rb +17 -2
- data/lib/phusion_passenger/utils/unseekable_socket.rb +0 -30
- data/resources/templates/error_layout.html.template +1 -1
- data/resources/templates/standalone/config.erb +18 -5
- data/test/cxx/RequestHandlerTest.cpp +410 -194
- data/test/integration_tests/native_packaging_spec.rb +5 -1
- data/test/ruby/request_handler_spec.rb +4 -71
- data/test/ruby/utils/tee_input_spec.rb +235 -0
- metadata +5 -3
- metadata.gz.asc +7 -7
checksums.yaml
CHANGED
@@ -1,15 +1,15 @@
|
|
1
1
|
---
|
2
2
|
!binary "U0hBMQ==":
|
3
3
|
metadata.gz: !binary |-
|
4
|
-
|
4
|
+
NGY1MTk2N2EzOGQwZTdiMGFjZTNjNWE2MmEwZDQ2Yjk3NzA5YThmNQ==
|
5
5
|
data.tar.gz: !binary |-
|
6
|
-
|
6
|
+
OTJiNzQ2OGI0ZTI5MGFjZTdlMTQxM2RhNmZlNmJmNzJjNWVhOWYzYQ==
|
7
7
|
SHA512:
|
8
8
|
metadata.gz: !binary |-
|
9
|
-
|
10
|
-
|
11
|
-
|
9
|
+
ZjJhNGRlMzkzZDA2ZjU3ZDI0ZjA4ZjNlZTAwOTNiZDllOWZkMzY3NGIwZjJj
|
10
|
+
ZWYzZWVhZTQzMmM2NjAyZmUyZTU2YTllYjQ4NzQ1ZGIzNGY3MGQxMjQ1Y2My
|
11
|
+
NGU3ZjlhZTc5YTljNzA1NTQ1NDdmZTkxNjc0NzAxMDdkNzRjYjk=
|
12
12
|
data.tar.gz: !binary |-
|
13
|
-
|
14
|
-
|
15
|
-
|
13
|
+
ZGUxZWI2MGU1MGQ3Mzc4MjE0MWEyNzI2OGEyYzdlMWY5MGY2NzhiZjkxM2E4
|
14
|
+
MTRmMTM1NzY0YTcwNmNmM2ZiZTlkNDY3MTBiMWM1ZGU3OTBmNTdkMmYxZTdj
|
15
|
+
ZTU0ZWJiMTlkMmU3ZTdkOGQ3MTZiODI3YWMxMDc3NjEyMTc2OTA=
|
checksums.yaml.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
|
-
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
=
|
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
|
-
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
=
|
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
|
|
data/bin/passenger
CHANGED
@@ -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,
|
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,
|
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
|
data/build/apache2.rb
CHANGED
@@ -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'
|
data/build/cxx_tests.rb
CHANGED
@@ -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
|
data/build/packaging.rb
CHANGED
@@ -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"]
|
data/dev/list_tests.rb
ADDED
@@ -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,
|
118
|
+
First, create a copy of the default Phusion Passenger Nginx configuration template file:
|
111
119
|
|
112
120
|
-------------------------------------
|
113
|
-
|
121
|
+
cp $(passenger-config about resourcesdir)/templates/standalone/config.erb nginx.conf.erb
|
114
122
|
-------------------------------------
|
115
123
|
|
116
|
-
Then open
|
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
|
-
|
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
|
-
|
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
|
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
|
-
|
134
|
-
|
135
|
-
|
136
|
-
|
137
|
-
|
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 ===
|
data/ext/common/Constants.h
CHANGED
data/ext/common/MessageClient.h
CHANGED
@@ -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
|
33
|
-
#include
|
34
|
-
#include
|
35
|
-
#include
|
36
|
-
#include
|
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
|
-
|
244
|
+
writeArrayMessageVA(fd, name, ap);
|
245
245
|
} catch (const SystemException &) {
|
246
246
|
autoDisconnect();
|
247
247
|
throw;
|
data/ext/common/Utils.cpp
CHANGED
@@ -1031,13 +1031,17 @@ getFileDescriptorLimit() {
|
|
1031
1031
|
}
|
1032
1032
|
|
1033
1033
|
long result;
|
1034
|
-
|
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
|
-
//
|
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
|
-
|
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
|
550
|
+
writeArrayMessage(int fd, const char *name, ...) {
|
551
551
|
va_list ap;
|
552
552
|
va_start(ap, name);
|
553
553
|
_VaGuard guard(ap);
|
554
|
-
|
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
|
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
|
-
|
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
|
-
|
169
|
-
|
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
|
-
|
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
|
-
|
186
|
-
|
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
|
-
|
272
|
-
|
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 << "
|
536
|
-
<< indent << "
|
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
|
-
|
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
|
-
|
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->
|
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->
|
2069
|
+
if (client->requestBodyLength >= 0) {
|
1948
2070
|
size = std::min<unsigned long long>(
|
1949
2071
|
size,
|
1950
|
-
(unsigned long long) client->
|
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->
|
2082
|
+
client->requestBodyAlreadyRead += size;
|
1961
2083
|
|
1962
2084
|
RH_TRACE(client, 3, "Buffered " << size << " bytes of client body data; total=" <<
|
1963
|
-
client->
|
1964
|
-
assert(client->
|
2085
|
+
client->requestBodyAlreadyRead << ", content-length=" << client->requestBodyLength);
|
2086
|
+
assert(client->requestBodyLength == -1 || client->requestBodyAlreadyRead <= (unsigned long long) client->requestBodyLength);
|
1965
2087
|
|
1966
|
-
if (client->
|
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->
|
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->
|
2466
|
+
if (client->requestBodyLength >= 0) {
|
2345
2467
|
size = std::min<unsigned long long>(
|
2346
2468
|
size,
|
2347
|
-
(unsigned long long) client->
|
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->
|
2499
|
+
client->requestBodyAlreadyRead += ret;
|
2378
2500
|
|
2379
2501
|
RH_TRACE(client, 3, "Managed to forward " << ret << " bytes; total=" <<
|
2380
|
-
client->
|
2381
|
-
assert(client->
|
2382
|
-
if (client->
|
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
|
}
|