passenger 4.0.36 → 4.0.37
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/NEWS +51 -0
- data/README.md +3 -1
- data/build/integration_tests.rb +5 -1
- data/build/test_basics.rb +2 -2
- data/dev/run_travis.sh +3 -0
- data/doc/Users guide Nginx.txt +3 -3
- data/ext/common/ApplicationPool2/Group.h +2 -1
- data/ext/common/ApplicationPool2/Implementation.cpp +30 -1
- data/ext/common/ApplicationPool2/Pool.h +26 -3
- data/ext/common/ApplicationPool2/Process.h +39 -10
- data/ext/common/Constants.h +1 -1
- data/ext/common/MultiLibeio.cpp +4 -0
- data/ext/common/ServerInstanceDir.h +1 -1
- data/ext/common/Utils.cpp +29 -0
- data/ext/common/Utils.h +7 -1
- data/ext/common/Utils/BufferedIO.h +13 -0
- data/ext/common/agents/HelperAgent/Main.cpp +6 -2
- data/ext/common/agents/HelperAgent/RequestHandler.h +32 -1
- data/helper-scripts/meteor-loader.rb +126 -10
- data/helper-scripts/node-loader.js +5 -3
- data/helper-scripts/wsgi-loader.py +23 -11
- data/lib/phusion_passenger.rb +1 -1
- data/lib/phusion_passenger/config/detach_process_command.rb +96 -0
- data/lib/phusion_passenger/config/main.rb +1 -0
- data/lib/phusion_passenger/request_handler.rb +11 -4
- data/node_lib/phusion_passenger/httplib_emulation.js +20 -14
- data/test/cxx/RequestHandlerTest.cpp +80 -0
- data/test/integration_tests/apache2_tests.rb +57 -0
- data/test/integration_tests/nginx_tests.rb +62 -0
- data/test/node/httplib_emulation_spec.js +137 -5
- data/test/node/spec_helper.js +13 -0
- data/test/stub/node/app.js +125 -0
- data/test/stub/node/public/.gitignore +0 -0
- data/test/stub/node/tmp/.gitignore +0 -0
- data/test/stub/rack/config.ru +19 -0
- data/test/stub/wsgi/passenger_wsgi.py +37 -1
- metadata +6 -2
- metadata.gz.asc +7 -7
data/ext/common/MultiLibeio.cpp
CHANGED
@@ -55,6 +55,10 @@ struct Data {
|
|
55
55
|
: libev(_libev),
|
56
56
|
callback(_callback)
|
57
57
|
{
|
58
|
+
// If this assertion fails, then in the context of RequestHandler it means
|
59
|
+
// that it was operating on a client that has already been disconnected.
|
60
|
+
// The RequestHandler code is probably missing some necessary checks on
|
61
|
+
// `client->connected()`.
|
58
62
|
assert(_libev != NULL);
|
59
63
|
}
|
60
64
|
};
|
data/ext/common/Utils.cpp
CHANGED
@@ -143,6 +143,35 @@ getFileType(const StaticString &filename, CachedFileStat *cstat, unsigned int th
|
|
143
143
|
}
|
144
144
|
}
|
145
145
|
|
146
|
+
FileType
|
147
|
+
getFileTypeNoFollowSymlinks(const StaticString &filename) {
|
148
|
+
struct stat buf;
|
149
|
+
int ret;
|
150
|
+
|
151
|
+
ret = lstat(filename.c_str(), &buf);
|
152
|
+
if (ret == 0) {
|
153
|
+
if (S_ISREG(buf.st_mode)) {
|
154
|
+
return FT_REGULAR;
|
155
|
+
} else if (S_ISDIR(buf.st_mode)) {
|
156
|
+
return FT_DIRECTORY;
|
157
|
+
} else if (S_ISLNK(buf.st_mode)) {
|
158
|
+
return FT_SYMLINK;
|
159
|
+
} else {
|
160
|
+
return FT_OTHER;
|
161
|
+
}
|
162
|
+
} else {
|
163
|
+
if (errno == ENOENT) {
|
164
|
+
return FT_NONEXISTANT;
|
165
|
+
} else {
|
166
|
+
int e = errno;
|
167
|
+
string message("Cannot lstat '");
|
168
|
+
message.append(filename);
|
169
|
+
message.append("'");
|
170
|
+
throw FileSystemException(message, e, filename);
|
171
|
+
}
|
172
|
+
}
|
173
|
+
}
|
174
|
+
|
146
175
|
void
|
147
176
|
createFile(const string &filename, const StaticString &contents, mode_t permissions, uid_t owner,
|
148
177
|
gid_t group, bool overwrite)
|
data/ext/common/Utils.h
CHANGED
@@ -65,6 +65,8 @@ typedef enum {
|
|
65
65
|
FT_REGULAR,
|
66
66
|
/** A directory. */
|
67
67
|
FT_DIRECTORY,
|
68
|
+
/** A symlink. Only returned by getFileTypeNoFollowSymlinks(), not by getFileType(). */
|
69
|
+
FT_SYMLINK,
|
68
70
|
/** Something else, e.g. a pipe or a socket. */
|
69
71
|
FT_OTHER
|
70
72
|
} FileType;
|
@@ -110,7 +112,7 @@ bool fileExists(const StaticString &filename, CachedFileStat *cstat = 0,
|
|
110
112
|
/**
|
111
113
|
* Check whether 'filename' exists and what kind of file it is.
|
112
114
|
*
|
113
|
-
* @param filename The filename to check.
|
115
|
+
* @param filename The filename to check. It MUST be NULL-terminated.
|
114
116
|
* @param mstat A CachedFileStat object, if you want to use cached statting.
|
115
117
|
* @param throttleRate A throttle rate for cstat. Only applicable if cstat is not NULL.
|
116
118
|
* @return The file type.
|
@@ -121,6 +123,10 @@ bool fileExists(const StaticString &filename, CachedFileStat *cstat = 0,
|
|
121
123
|
*/
|
122
124
|
FileType getFileType(const StaticString &filename, CachedFileStat *cstat = 0,
|
123
125
|
unsigned int throttleRate = 0);
|
126
|
+
/**
|
127
|
+
* Like getFileType(), but does not follow symlinks.
|
128
|
+
*/
|
129
|
+
FileType getFileTypeNoFollowSymlinks(const StaticString &filename);
|
124
130
|
|
125
131
|
/**
|
126
132
|
* Create the given file with the given contents, permissions and ownership.
|
@@ -171,6 +171,19 @@ public:
|
|
171
171
|
return output;
|
172
172
|
}
|
173
173
|
|
174
|
+
/**
|
175
|
+
* Reads a line and returns the line including the newline character. Upon
|
176
|
+
* encountering EOF, the empty string is returned.
|
177
|
+
*
|
178
|
+
* The `max` parameter dictates the maximum length of the returned line.
|
179
|
+
* If the line is longer than this number of characters, then a SecurityException
|
180
|
+
* is thrown, and the BufferedIO becomes unusable (enters an undefined state).
|
181
|
+
*
|
182
|
+
* @throws SystemException
|
183
|
+
* @throws TimeoutException
|
184
|
+
* @throws SecurityException
|
185
|
+
* @throws boost::thread_interrupted
|
186
|
+
*/
|
174
187
|
string readLine(unsigned int max = 1024, unsigned long long *timeout = NULL) {
|
175
188
|
string output;
|
176
189
|
readUntil(boost::bind(newlineFound, _1, _2, &output, max), timeout);
|
@@ -1,6 +1,6 @@
|
|
1
1
|
/*
|
2
2
|
* Phusion Passenger - https://www.phusionpassenger.com/
|
3
|
-
* Copyright (c) 2010-
|
3
|
+
* Copyright (c) 2010-2014 Phusion
|
4
4
|
*
|
5
5
|
* "Phusion Passenger" is a trademark of Hongli Lai & Ninh Bui.
|
6
6
|
*
|
@@ -437,7 +437,7 @@ public:
|
|
437
437
|
accountsDatabase->add("_passenger-status", options.adminToolStatusPassword, false,
|
438
438
|
Account::INSPECT_BASIC_INFO | Account::INSPECT_SENSITIVE_INFO |
|
439
439
|
Account::INSPECT_BACKTRACES | Account::INSPECT_REQUESTS |
|
440
|
-
Account::RESTART);
|
440
|
+
Account::DETACH | Account::RESTART);
|
441
441
|
accountsDatabase->add("_web_server", options.exitPassword, false, Account::EXIT);
|
442
442
|
messageServer = boost::make_shared<MessageServer>(
|
443
443
|
parseUnixSocketAddress(options.adminSocketAddress), accountsDatabase);
|
@@ -584,6 +584,9 @@ public:
|
|
584
584
|
* inaccessible.
|
585
585
|
*/
|
586
586
|
P_DEBUG("Watchdog seems to be killed; forcing shutdown of all subprocesses");
|
587
|
+
// We send a SIGTERM first to allow processes to gracefully shut down.
|
588
|
+
syscalls::killpg(getpgrp(), SIGTERM);
|
589
|
+
usleep(500000);
|
587
590
|
syscalls::killpg(getpgrp(), SIGKILL);
|
588
591
|
_exit(2); // In case killpg() fails.
|
589
592
|
} else {
|
@@ -592,6 +595,7 @@ public:
|
|
592
595
|
*/
|
593
596
|
P_DEBUG("Received command to exit gracefully. "
|
594
597
|
"Waiting until 5 seconds after all clients have disconnected...");
|
598
|
+
pool->prepareForShutdown();
|
595
599
|
requestHandler->resetInactivityTime();
|
596
600
|
while (requestHandler->inactivityTime() < 5000) {
|
597
601
|
syscalls::usleep(250000);
|
@@ -696,8 +696,17 @@ private:
|
|
696
696
|
status, (unsigned long) data.size());
|
697
697
|
|
698
698
|
client->clientOutputPipe->write(header, pos - header);
|
699
|
+
if (!client->connected()) {
|
700
|
+
return;
|
701
|
+
}
|
699
702
|
client->clientOutputPipe->write(data.data(), data.size());
|
703
|
+
if (!client->connected()) {
|
704
|
+
return;
|
705
|
+
}
|
700
706
|
client->clientOutputPipe->end();
|
707
|
+
if (!client->connected()) {
|
708
|
+
return;
|
709
|
+
}
|
701
710
|
|
702
711
|
if (client->useUnionStation()) {
|
703
712
|
snprintf(header, end - header, "Status: %d %s",
|
@@ -778,8 +787,17 @@ private:
|
|
778
787
|
|
779
788
|
const string header = str.str();
|
780
789
|
client->clientOutputPipe->write(header.data(), header.size());
|
790
|
+
if (!client->connected()) {
|
791
|
+
return;
|
792
|
+
}
|
781
793
|
client->clientOutputPipe->write(data.data(), data.size());
|
794
|
+
if (!client->connected()) {
|
795
|
+
return;
|
796
|
+
}
|
782
797
|
client->clientOutputPipe->end();
|
798
|
+
if (!client->connected()) {
|
799
|
+
return;
|
800
|
+
}
|
783
801
|
|
784
802
|
if (client->useUnionStation()) {
|
785
803
|
client->logMessage("Status: 500 Internal Server Error");
|
@@ -1094,6 +1112,10 @@ private:
|
|
1094
1112
|
void writeToClientOutputPipe(const ClientPtr &client, const StaticString &data) {
|
1095
1113
|
bool wasCommittingToDisk = client->clientOutputPipe->isCommittingToDisk();
|
1096
1114
|
bool nowCommittingToDisk = !client->clientOutputPipe->write(data.data(), data.size());
|
1115
|
+
if (!client->connected()) {
|
1116
|
+
// EPIPE/ECONNRESET detected.
|
1117
|
+
return;
|
1118
|
+
}
|
1097
1119
|
if (!wasCommittingToDisk && nowCommittingToDisk) {
|
1098
1120
|
RH_TRACE(client, 3, "Buffering response data to disk; temporarily stopping application socket.");
|
1099
1121
|
client->backgroundOperations++;
|
@@ -2010,6 +2032,7 @@ private:
|
|
2010
2032
|
}
|
2011
2033
|
|
2012
2034
|
void sessionCheckedOut_real(ClientPtr client, const SessionPtr &session, const ExceptionPtr &e) {
|
2035
|
+
RH_LOG_EVENT(client, "sessionCheckedOut");
|
2013
2036
|
if (!client->connected()) {
|
2014
2037
|
return;
|
2015
2038
|
}
|
@@ -2200,7 +2223,6 @@ private:
|
|
2200
2223
|
data.append(" ");
|
2201
2224
|
data.append(parser.getHeader("REQUEST_URI"));
|
2202
2225
|
data.append(" HTTP/1.1\r\n");
|
2203
|
-
data.append("Connection: close\r\n");
|
2204
2226
|
|
2205
2227
|
for (it = parser.begin(); it != end; it++) {
|
2206
2228
|
if (startsWith(it->first, "HTTP_") && it->first != "HTTP_CONNECTION") {
|
@@ -2221,6 +2243,15 @@ private:
|
|
2221
2243
|
}
|
2222
2244
|
}
|
2223
2245
|
|
2246
|
+
StaticString connection = parser.getHeader("HTTP_CONNECTION");
|
2247
|
+
if (connection == "upgrade" || connection == "Upgrade") {
|
2248
|
+
data.append("Connection: ");
|
2249
|
+
data.append(connection.data(), connection.size());
|
2250
|
+
data.append("\r\n");
|
2251
|
+
} else {
|
2252
|
+
data.append("Connection: close\r\n");
|
2253
|
+
}
|
2254
|
+
|
2224
2255
|
StaticString header = parser.getHeader("CONTENT_LENGTH");
|
2225
2256
|
if (!header.empty()) {
|
2226
2257
|
data.append("Content-Length: ");
|
@@ -1,7 +1,7 @@
|
|
1
1
|
#!/usr/bin/env ruby
|
2
2
|
# encoding: binary
|
3
3
|
# Phusion Passenger - https://www.phusionpassenger.com/
|
4
|
-
# Copyright (c) 2010-
|
4
|
+
# Copyright (c) 2010-2014 Phusion
|
5
5
|
#
|
6
6
|
# "Phusion Passenger" is a trademark of Hongli Lai & Ninh Bui.
|
7
7
|
#
|
@@ -24,6 +24,8 @@
|
|
24
24
|
# THE SOFTWARE.
|
25
25
|
|
26
26
|
require 'socket'
|
27
|
+
require 'thread'
|
28
|
+
require 'logger'
|
27
29
|
|
28
30
|
module PhusionPassenger
|
29
31
|
module App
|
@@ -52,6 +54,13 @@ module App
|
|
52
54
|
end
|
53
55
|
end
|
54
56
|
|
57
|
+
def self.init_passenger
|
58
|
+
require "#{options["ruby_libdir"]}/phusion_passenger"
|
59
|
+
PhusionPassenger.locate_directories(options["passenger_root"])
|
60
|
+
PhusionPassenger.require_passenger_lib 'message_channel'
|
61
|
+
PhusionPassenger.require_passenger_lib 'utils/tmpio'
|
62
|
+
end
|
63
|
+
|
55
64
|
def self.ping_port(port)
|
56
65
|
socket_domain = Socket::Constants::AF_INET
|
57
66
|
sockaddr = Socket.pack_sockaddr_in(port, '127.0.0.1')
|
@@ -77,7 +86,14 @@ module App
|
|
77
86
|
end
|
78
87
|
end
|
79
88
|
|
80
|
-
def self.
|
89
|
+
def self.create_control_server
|
90
|
+
dir = Utils.mktmpdir('meteor')
|
91
|
+
filename = "#{dir}/control"
|
92
|
+
server = UNIXServer.new(filename)
|
93
|
+
return [server, dir, filename]
|
94
|
+
end
|
95
|
+
|
96
|
+
def self.load_app(control_server)
|
81
97
|
port = nil
|
82
98
|
tries = 0
|
83
99
|
while port.nil? && tries < 200
|
@@ -101,34 +117,134 @@ module App
|
|
101
117
|
# Meteor its own process group, and sending signals to the
|
102
118
|
# entire process group.
|
103
119
|
Process.setpgrp
|
120
|
+
control_server.close
|
104
121
|
exec("meteor run -p #{port} #{production}")
|
105
122
|
end
|
106
123
|
$0 = options["process_title"] if options["process_title"]
|
107
124
|
$0 = "#{$0} (#{pid})"
|
108
125
|
return [pid, port]
|
109
126
|
end
|
127
|
+
|
128
|
+
class ExitFlag
|
129
|
+
def initialize
|
130
|
+
@mutex = Mutex.new
|
131
|
+
@cond = ConditionVariable.new
|
132
|
+
@exit = false
|
133
|
+
end
|
134
|
+
|
135
|
+
def set
|
136
|
+
@mutex.synchronize do
|
137
|
+
@exit = true
|
138
|
+
@cond.broadcast
|
139
|
+
end
|
140
|
+
end
|
141
|
+
|
142
|
+
def wait
|
143
|
+
@mutex.synchronize do
|
144
|
+
while !@exit
|
145
|
+
@cond.wait(@mutex)
|
146
|
+
end
|
147
|
+
end
|
148
|
+
end
|
149
|
+
end
|
150
|
+
|
151
|
+
# When the HelperAgent is shutting down, it first sends a message (A) to application
|
152
|
+
# processes through the control socket that this is happening. The HelperAgent then
|
153
|
+
# waits until all HTTP connections are closed, before sending another message
|
154
|
+
# to application processes that they should shut down (B).
|
155
|
+
# Because Meteor opens long-running connections (e.g. for WebSocket), we have to shut
|
156
|
+
# down the Meteor app when A arrives, otherwise the HelperAgent will never send B.
|
157
|
+
def self.wait_for_exit_message(control_server)
|
158
|
+
exit_flag = ExitFlag.new
|
159
|
+
start_control_server_thread(control_server, exit_flag)
|
160
|
+
start_stdin_waiter_thread(exit_flag)
|
161
|
+
exit_flag.wait
|
162
|
+
end
|
163
|
+
|
164
|
+
def self.start_control_server_thread(control_server, exit_flag)
|
165
|
+
Thread.new do
|
166
|
+
Thread.current.abort_on_exception = true
|
167
|
+
while true
|
168
|
+
process_next_control_client(control_server, exit_flag)
|
169
|
+
end
|
170
|
+
end
|
171
|
+
end
|
172
|
+
|
173
|
+
def self.process_next_control_client(control_server, exit_flag)
|
174
|
+
logger = Logger.new(STDERR)
|
175
|
+
begin
|
176
|
+
client = control_server.accept
|
177
|
+
channel = MessageChannel.new(client)
|
178
|
+
while message = channel.read
|
179
|
+
process_next_control_message(message, logger, exit_flag)
|
180
|
+
end
|
181
|
+
rescue Exception => e
|
182
|
+
logger.error("#{e} (#{e.class})\n " + e.backtrace.join("\n "))
|
183
|
+
ensure
|
184
|
+
begin
|
185
|
+
client.close if client
|
186
|
+
rescue SystemCallError, IOError, SocketError
|
187
|
+
end
|
188
|
+
end
|
189
|
+
end
|
190
|
+
|
191
|
+
def self.process_next_control_message(message, logger, exit_flag)
|
192
|
+
if message[0] == "abort_long_running_connections"
|
193
|
+
logger.debug("Aborting long-running connections")
|
194
|
+
exit_flag.set
|
195
|
+
else
|
196
|
+
logger.error("Invalid control message: #{message.inspect}")
|
197
|
+
end
|
198
|
+
end
|
199
|
+
|
200
|
+
def self.start_stdin_waiter_thread(exit_flag)
|
201
|
+
Thread.new do
|
202
|
+
Thread.current.abort_on_exception = true
|
203
|
+
begin
|
204
|
+
STDIN.readline
|
205
|
+
rescue EOFError
|
206
|
+
ensure
|
207
|
+
exit_flag.set
|
208
|
+
end
|
209
|
+
end
|
210
|
+
end
|
211
|
+
|
110
212
|
|
111
213
|
|
112
214
|
################## Main code ##################
|
113
215
|
|
114
216
|
|
115
217
|
handshake_and_read_startup_request
|
116
|
-
|
218
|
+
init_passenger
|
117
219
|
begin
|
220
|
+
control_server, control_dir, control_filename = create_control_server
|
221
|
+
pid, port = load_app(control_server)
|
118
222
|
while !ping_port(port)
|
119
223
|
sleep 0.01
|
120
224
|
end
|
121
225
|
puts "!> Ready"
|
122
226
|
puts "!> socket: main;tcp://127.0.0.1:#{port};http_session;0"
|
227
|
+
puts "!> socket: control;unix:#{control_filename};control;0"
|
123
228
|
puts "!> "
|
124
|
-
|
125
|
-
STDIN.readline
|
126
|
-
rescue EOFError
|
127
|
-
end
|
229
|
+
wait_for_exit_message(control_server)
|
128
230
|
ensure
|
129
|
-
|
130
|
-
|
131
|
-
|
231
|
+
if pid
|
232
|
+
Process.kill('INT', -pid) rescue nil
|
233
|
+
Process.waitpid(pid) rescue nil
|
234
|
+
Process.kill('INT', -pid) rescue nil
|
235
|
+
end
|
236
|
+
if control_server
|
237
|
+
control_server.close
|
238
|
+
begin
|
239
|
+
File.unlink(control_filename)
|
240
|
+
rescue SystemCallError
|
241
|
+
end
|
242
|
+
require 'fileutils'
|
243
|
+
begin
|
244
|
+
FileUtils.remove_entry_secure(control_dir)
|
245
|
+
rescue SystemCallError
|
246
|
+
end
|
247
|
+
end
|
132
248
|
end
|
133
249
|
|
134
250
|
end # module App
|
@@ -135,15 +135,17 @@ function installServer() {
|
|
135
135
|
finalizeStartup();
|
136
136
|
|
137
137
|
PhusionPassenger.on('request', function(headers, socket, bodyBegin) {
|
138
|
-
var req
|
139
|
-
if (
|
138
|
+
var req, res;
|
139
|
+
if (headers['HTTP_UPGRADE']) {
|
140
140
|
if (EventEmitter.listenerCount(server, 'upgrade') > 0) {
|
141
|
+
req = HttplibEmulation.createIncomingMessage(headers, socket, bodyBegin);
|
141
142
|
server.emit('upgrade', req, socket, bodyBegin);
|
142
143
|
} else {
|
143
144
|
socket.destroy();
|
144
145
|
}
|
145
146
|
} else {
|
146
|
-
|
147
|
+
req = HttplibEmulation.createIncomingMessage(headers, socket, bodyBegin);
|
148
|
+
res = HttplibEmulation.createServerResponse(req);
|
147
149
|
server.emit('request', req, res);
|
148
150
|
}
|
149
151
|
});
|
@@ -106,6 +106,7 @@ class RequestHandler:
|
|
106
106
|
if not client:
|
107
107
|
done = True
|
108
108
|
break
|
109
|
+
socket_hijacked = False
|
109
110
|
try:
|
110
111
|
try:
|
111
112
|
env, input_stream = self.parse_request(client)
|
@@ -113,7 +114,7 @@ class RequestHandler:
|
|
113
114
|
if env['REQUEST_METHOD'] == 'ping':
|
114
115
|
self.process_ping(env, input_stream, client)
|
115
116
|
else:
|
116
|
-
self.process_request(env, input_stream, client)
|
117
|
+
socket_hijacked = self.process_request(env, input_stream, client)
|
117
118
|
except KeyboardInterrupt:
|
118
119
|
done = True
|
119
120
|
except IOError:
|
@@ -123,16 +124,17 @@ class RequestHandler:
|
|
123
124
|
except Exception:
|
124
125
|
logging.exception("WSGI application raised an exception!")
|
125
126
|
finally:
|
126
|
-
|
127
|
-
|
128
|
-
|
129
|
-
|
130
|
-
|
131
|
-
|
132
|
-
|
133
|
-
|
134
|
-
|
135
|
-
|
127
|
+
if not socket_hijacked:
|
128
|
+
try:
|
129
|
+
# Shutdown the socket like this just in case the app
|
130
|
+
# spawned a child process that keeps it open.
|
131
|
+
client.shutdown(socket.SHUT_WR)
|
132
|
+
except:
|
133
|
+
pass
|
134
|
+
try:
|
135
|
+
client.close()
|
136
|
+
except:
|
137
|
+
pass
|
136
138
|
except KeyboardInterrupt:
|
137
139
|
pass
|
138
140
|
|
@@ -232,7 +234,16 @@ class RequestHandler:
|
|
232
234
|
headers_set[:] = [status, response_headers]
|
233
235
|
return write
|
234
236
|
|
237
|
+
def hijack():
|
238
|
+
env['passenger.hijacked_socket'] = output_stream
|
239
|
+
return output_stream
|
240
|
+
|
241
|
+
env['passenger.hijack'] = hijack
|
242
|
+
|
235
243
|
result = self.app(env, start_response)
|
244
|
+
if 'passenger.hijacked_socket' in env:
|
245
|
+
# Socket connection hijacked. Don't do anything.
|
246
|
+
return True
|
236
247
|
try:
|
237
248
|
for data in result:
|
238
249
|
# Don't send headers until body appears.
|
@@ -244,6 +255,7 @@ class RequestHandler:
|
|
244
255
|
finally:
|
245
256
|
if hasattr(result, 'close'):
|
246
257
|
result.close()
|
258
|
+
return False
|
247
259
|
|
248
260
|
def process_ping(self, env, input_stream, output_stream):
|
249
261
|
output_stream.sendall(b"pong")
|