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
@@ -92,14 +92,6 @@ class UnseekableSocket
|
|
92
92
|
@socket
|
93
93
|
end
|
94
94
|
|
95
|
-
def simulate_eof!
|
96
|
-
@simulate_eof = true
|
97
|
-
end
|
98
|
-
|
99
|
-
def stop_simulating_eof!
|
100
|
-
@simulate_eof = false
|
101
|
-
end
|
102
|
-
|
103
95
|
def fileno
|
104
96
|
@socket.fileno
|
105
97
|
end
|
@@ -165,82 +157,60 @@ class UnseekableSocket
|
|
165
157
|
end
|
166
158
|
|
167
159
|
def gets
|
168
|
-
return nil if @simulate_eof
|
169
160
|
@socket.gets
|
170
161
|
rescue => e
|
171
162
|
raise annotate(e)
|
172
163
|
end
|
173
164
|
|
174
165
|
def read(*args)
|
175
|
-
if @simulate_eof
|
176
|
-
length, buffer = args
|
177
|
-
if buffer
|
178
|
-
buffer.replace(binary_string(""))
|
179
|
-
else
|
180
|
-
buffer = binary_string("")
|
181
|
-
end
|
182
|
-
if length
|
183
|
-
return nil
|
184
|
-
else
|
185
|
-
return buffer
|
186
|
-
end
|
187
|
-
end
|
188
166
|
@socket.read(*args)
|
189
167
|
rescue => e
|
190
168
|
raise annotate(e)
|
191
169
|
end
|
192
170
|
|
193
171
|
def read_nonblock(*args)
|
194
|
-
raise EOFError, "end of file reached" if @simulate_eof
|
195
172
|
@socket.read_nonblock(*args)
|
196
173
|
rescue => e
|
197
174
|
raise annotate(e)
|
198
175
|
end
|
199
176
|
|
200
177
|
def readpartial(*args)
|
201
|
-
raise EOFError, "end of file reached" if @simulate_eof
|
202
178
|
@socket.readpartial(*args)
|
203
179
|
rescue => e
|
204
180
|
raise annotate(e)
|
205
181
|
end
|
206
182
|
|
207
183
|
def readline
|
208
|
-
raise EOFError, "end of file reached" if @simulate_eof
|
209
184
|
@socket.readline
|
210
185
|
rescue => e
|
211
186
|
raise annotate(e)
|
212
187
|
end
|
213
188
|
|
214
189
|
def recv(*args)
|
215
|
-
raise EOFError, "end of file reached" if @simulate_eof
|
216
190
|
@socket.recv(*args)
|
217
191
|
rescue => e
|
218
192
|
raise annotate(e)
|
219
193
|
end
|
220
194
|
|
221
195
|
def recvfrom(*args)
|
222
|
-
raise EOFError, "end of file reached" if @simulate_eof
|
223
196
|
@socket.recvfrom(*args)
|
224
197
|
rescue => e
|
225
198
|
raise annotate(e)
|
226
199
|
end
|
227
200
|
|
228
201
|
def recvfrom_nonblock(*args)
|
229
|
-
raise EOFError, "end of file reached" if @simulate_eof
|
230
202
|
@socket.recvfrom_nonblock(*args)
|
231
203
|
rescue => e
|
232
204
|
raise annotate(e)
|
233
205
|
end
|
234
206
|
|
235
207
|
def each(&block)
|
236
|
-
return if @simulate_eof
|
237
208
|
@socket.each(&block)
|
238
209
|
rescue => e
|
239
210
|
raise annotate(e)
|
240
211
|
end
|
241
212
|
|
242
213
|
def eof?
|
243
|
-
return true if @simulate_eof
|
244
214
|
@socket.eof?
|
245
215
|
rescue => e
|
246
216
|
raise annotate(e)
|
@@ -36,7 +36,7 @@
|
|
36
36
|
<dl>
|
37
37
|
<dt>Application root</dt>
|
38
38
|
<dd>{{APP_ROOT}}</dd>
|
39
|
-
<dt>Environment (value of RAILS_ENV, RACK_ENV, WSGI_ENV and
|
39
|
+
<dt>Environment (value of RAILS_ENV, RACK_ENV, WSGI_ENV, NODE_ENV and PASSENGER_APP_ENV)</dt>
|
40
40
|
<dd>{{ENVIRONMENT}}</dd>
|
41
41
|
{{if IS_RUBY_APP}}
|
42
42
|
<dt>Ruby interpreter command</dt>
|
@@ -1,8 +1,21 @@
|
|
1
|
-
|
2
|
-
#
|
3
|
-
#
|
4
|
-
#
|
5
|
-
|
1
|
+
##############################################################
|
2
|
+
# Phusion Passenger Standalone uses a template file to
|
3
|
+
# generate an Nginx configuration file. The original template
|
4
|
+
# file can be found by running the following command:
|
5
|
+
#
|
6
|
+
# ls $(passenger-config about resourcesdir)/templates/standalone/config.erb
|
7
|
+
#
|
8
|
+
# You can create a copy of this template file and customize it
|
9
|
+
# to your liking. Just make sure you tell Phusion Passenger Standalone
|
10
|
+
# to use your template file by passing the --nginx-config-template
|
11
|
+
# parameter.
|
12
|
+
#
|
13
|
+
# *** NOTE ***
|
14
|
+
# If you customize the template file, make sure you keep an eye
|
15
|
+
# on the original template file and merge any changes.
|
16
|
+
# New Phusion Passenger features may require changes to the template
|
17
|
+
# file.
|
18
|
+
##############################################################
|
6
19
|
|
7
20
|
|
8
21
|
master_process on;
|
@@ -100,32 +100,39 @@ namespace tut {
|
|
100
100
|
void sendHeaders(const map<string, string> &headers, ...) {
|
101
101
|
va_list ap;
|
102
102
|
const char *arg;
|
103
|
+
map<string, string> finalHeaders;
|
103
104
|
map<string, string>::const_iterator it;
|
104
105
|
vector<StaticString> args;
|
106
|
+
unsigned int totalSize = 0;
|
105
107
|
|
106
108
|
for (it = headers.begin(); it != headers.end(); it++) {
|
107
|
-
|
108
|
-
|
109
|
+
string key = string(it->first.data(), it->first.size() + 1);
|
110
|
+
string value = string(it->second.data(), it->second.size() + 1);
|
111
|
+
finalHeaders[key] = value;
|
109
112
|
}
|
110
113
|
|
111
114
|
va_start(ap, headers);
|
112
115
|
while ((arg = va_arg(ap, const char *)) != NULL) {
|
113
|
-
|
116
|
+
string key(arg, strlen(arg) + 1);
|
117
|
+
arg = va_arg(ap, const char *);
|
118
|
+
string value(arg, strlen(arg) + 1);
|
119
|
+
finalHeaders[key] = value;
|
114
120
|
}
|
115
121
|
va_end(ap);
|
116
122
|
|
117
|
-
|
118
|
-
|
119
|
-
|
120
|
-
|
121
|
-
totalSize +=
|
123
|
+
for (it = finalHeaders.begin(); it != finalHeaders.end(); it++) {
|
124
|
+
args.push_back(it->first);
|
125
|
+
args.push_back(it->second);
|
126
|
+
totalSize += it->first.size();
|
127
|
+
totalSize += it->second.size();
|
122
128
|
}
|
129
|
+
|
123
130
|
char totalSizeString[10];
|
124
131
|
snprintf(totalSizeString, sizeof(totalSizeString), "%u:", totalSize);
|
125
|
-
|
126
|
-
|
127
|
-
|
128
|
-
gatheredWrite(connection,
|
132
|
+
args.insert(args.begin(), StaticString(totalSizeString));
|
133
|
+
args.push_back(",");
|
134
|
+
|
135
|
+
gatheredWrite(connection, &args[0], args.size(), NULL);
|
129
136
|
}
|
130
137
|
|
131
138
|
string stripHeaders(const string &str) {
|
@@ -166,8 +173,11 @@ namespace tut {
|
|
166
173
|
|
167
174
|
DEFINE_TEST_GROUP_WITH_LIMIT(RequestHandlerTest, 80);
|
168
175
|
|
176
|
+
|
177
|
+
/***** Basic tests *****/
|
178
|
+
|
169
179
|
TEST_METHOD(1) {
|
170
|
-
|
180
|
+
set_test_name("A request is forwarded to the app process, and its response is forwarded back.");
|
171
181
|
init();
|
172
182
|
connect();
|
173
183
|
sendHeaders(defaultHeaders,
|
@@ -183,7 +193,7 @@ namespace tut {
|
|
183
193
|
}
|
184
194
|
|
185
195
|
TEST_METHOD(2) {
|
186
|
-
|
196
|
+
set_test_name("It can handle multiple requests in serial.");
|
187
197
|
init();
|
188
198
|
for (int i = 0; i < 10; i++) {
|
189
199
|
connect();
|
@@ -201,7 +211,7 @@ namespace tut {
|
|
201
211
|
}
|
202
212
|
|
203
213
|
TEST_METHOD(3) {
|
204
|
-
|
214
|
+
set_test_name("It can handle request data that is sent piece-wise.");
|
205
215
|
defaultHeaders["PASSENGER_APP_ROOT"] = wsgiAppPath;
|
206
216
|
defaultHeaders["PATH_INFO"] = "/";
|
207
217
|
|
@@ -235,7 +245,36 @@ namespace tut {
|
|
235
245
|
}
|
236
246
|
|
237
247
|
TEST_METHOD(4) {
|
238
|
-
|
248
|
+
set_test_name("It closes the connection with the application if the client has closed the connection.");
|
249
|
+
init();
|
250
|
+
connect();
|
251
|
+
sendHeaders(defaultHeaders,
|
252
|
+
"PASSENGER_APP_ROOT", wsgiAppPath.c_str(),
|
253
|
+
"PATH_INFO", "/stream",
|
254
|
+
NULL
|
255
|
+
);
|
256
|
+
BufferedIO io(connection);
|
257
|
+
ensure_equals(io.readLine(), "HTTP/1.1 200 OK\r\n");
|
258
|
+
ProcessPtr process;
|
259
|
+
{
|
260
|
+
LockGuard l(pool->syncher);
|
261
|
+
ensure_equals(pool->getProcessCount(false), 1u);
|
262
|
+
SuperGroupPtr superGroup = pool->superGroups.get(wsgiAppPath);
|
263
|
+
process = superGroup->defaultGroup->enabledProcesses.front();
|
264
|
+
ensure_equals(process->sessions, 1);
|
265
|
+
}
|
266
|
+
connection.close();
|
267
|
+
EVENTUALLY(5,
|
268
|
+
LockGuard l(pool->syncher);
|
269
|
+
result = process->sessions == 0;
|
270
|
+
);
|
271
|
+
}
|
272
|
+
|
273
|
+
|
274
|
+
/***** Connect password tests *****/
|
275
|
+
|
276
|
+
TEST_METHOD(5) {
|
277
|
+
set_test_name("It denies access if the connect password is wrong.");
|
239
278
|
agentOptions.requestSocketPassword = "hello world";
|
240
279
|
setLogLevel(-1);
|
241
280
|
init();
|
@@ -270,8 +309,8 @@ namespace tut {
|
|
270
309
|
ensure_equals(response, "");
|
271
310
|
}
|
272
311
|
|
273
|
-
TEST_METHOD(
|
274
|
-
|
312
|
+
TEST_METHOD(6) {
|
313
|
+
set_test_name("It disconnects the client if the connect password is not sent within a certain time.");
|
275
314
|
agentOptions.requestSocketPassword = "hello world";
|
276
315
|
setLogLevel(-1);
|
277
316
|
handler = boost::make_shared<RequestHandler>(bg.safe, requestSocket, pool, agentOptions);
|
@@ -285,8 +324,8 @@ namespace tut {
|
|
285
324
|
ensure(timer.elapsed() <= 60);
|
286
325
|
}
|
287
326
|
|
288
|
-
TEST_METHOD(
|
289
|
-
|
327
|
+
TEST_METHOD(7) {
|
328
|
+
set_test_name("It works correctly if the connect password is sent piece-wise.");
|
290
329
|
agentOptions.requestSocketPassword = "hello world";
|
291
330
|
init();
|
292
331
|
connect();
|
@@ -302,35 +341,11 @@ namespace tut {
|
|
302
341
|
ensure(containsSubstring(readAll(connection), "front page"));
|
303
342
|
}
|
304
343
|
|
305
|
-
|
306
|
-
|
307
|
-
|
308
|
-
connect();
|
309
|
-
sendHeaders(defaultHeaders,
|
310
|
-
"PASSENGER_APP_ROOT", wsgiAppPath.c_str(),
|
311
|
-
"PATH_INFO", "/stream",
|
312
|
-
NULL
|
313
|
-
);
|
314
|
-
BufferedIO io(connection);
|
315
|
-
ensure_equals(io.readLine(), "HTTP/1.1 200 OK\r\n");
|
316
|
-
ProcessPtr process;
|
317
|
-
{
|
318
|
-
LockGuard l(pool->syncher);
|
319
|
-
ensure_equals(pool->getProcessCount(false), 1u);
|
320
|
-
SuperGroupPtr superGroup = pool->superGroups.get(wsgiAppPath);
|
321
|
-
process = superGroup->defaultGroup->enabledProcesses.front();
|
322
|
-
ensure_equals(process->sessions, 1);
|
323
|
-
}
|
324
|
-
connection.close();
|
325
|
-
EVENTUALLY(5,
|
326
|
-
LockGuard l(pool->syncher);
|
327
|
-
result = process->sessions == 0;
|
328
|
-
);
|
329
|
-
}
|
330
|
-
|
344
|
+
|
345
|
+
/***** Error page tests *****/
|
346
|
+
|
331
347
|
TEST_METHOD(10) {
|
332
|
-
|
333
|
-
// a generic error page.
|
348
|
+
set_test_name("If the app crashes at startup without an error page then it renders a generic error page.");
|
334
349
|
TempDir tempdir("tmp.handler");
|
335
350
|
writeFile("tmp.handler/start.rb",
|
336
351
|
"STDERR.puts 'I have failed'");
|
@@ -351,8 +366,7 @@ namespace tut {
|
|
351
366
|
}
|
352
367
|
|
353
368
|
TEST_METHOD(11) {
|
354
|
-
|
355
|
-
// a friendly error page.
|
369
|
+
set_test_name("If the app crashes at startup with an error page then it renders a friendly error page.");
|
356
370
|
TempDir tempdir("tmp.handler");
|
357
371
|
writeFile("tmp.handler/start.rb",
|
358
372
|
"STDERR.puts 'Error'\n"
|
@@ -377,7 +391,7 @@ namespace tut {
|
|
377
391
|
}
|
378
392
|
|
379
393
|
TEST_METHOD(12) {
|
380
|
-
|
394
|
+
set_test_name("If spawning fails because of an internal error then it reports the error appropriately.");
|
381
395
|
TempDir tempdir("tmp.handler");
|
382
396
|
writeFile("tmp.handler/start.rb", "");
|
383
397
|
|
@@ -403,7 +417,7 @@ namespace tut {
|
|
403
417
|
}
|
404
418
|
|
405
419
|
TEST_METHOD(13) {
|
406
|
-
|
420
|
+
set_test_name("Error pages respect the PASSENGER_STATUS_LINE option.");
|
407
421
|
TempDir tempdir("tmp.handler");
|
408
422
|
writeFile("tmp.handler/start.rb",
|
409
423
|
"STDERR.puts 'I have failed'");
|
@@ -425,8 +439,7 @@ namespace tut {
|
|
425
439
|
}
|
426
440
|
|
427
441
|
TEST_METHOD(14) {
|
428
|
-
|
429
|
-
// a friendly error page.
|
442
|
+
set_test_name("If PASSENGER_FRIENDLY_ERROR_PAGES is false then it does not render a friendly error page.");
|
430
443
|
TempDir tempdir("tmp.handler");
|
431
444
|
writeFile("tmp.handler/start.rb",
|
432
445
|
"STDERR.puts 'Error'\n"
|
@@ -452,31 +465,36 @@ namespace tut {
|
|
452
465
|
ensure(containsSubstring(response, "We're sorry, but something went wrong"));
|
453
466
|
}
|
454
467
|
|
455
|
-
|
456
|
-
|
468
|
+
|
469
|
+
/***** Buffering tests *****/
|
470
|
+
|
471
|
+
TEST_METHOD(21) {
|
472
|
+
set_test_name("If PASSENGER_BUFFERING is true, and Content-Length is given, it buffers the request body.");
|
457
473
|
DeleteFileEventually file("tmp.output");
|
458
474
|
|
459
475
|
init();
|
460
476
|
connect();
|
461
477
|
sendHeaders(defaultHeaders,
|
462
478
|
"PASSENGER_APP_ROOT", wsgiAppPath.c_str(),
|
479
|
+
"PASSENGER_BUFFERING", "true",
|
480
|
+
"REQUEST_METHOD", "POST",
|
463
481
|
"PATH_INFO", "/raw_upload_to_file",
|
482
|
+
"CONTENT_LENGTH", "12",
|
464
483
|
"HTTP_X_OUTPUT", (root + "/test/tmp.output").c_str(),
|
465
484
|
NULL);
|
466
485
|
writeExact(connection, "hello\n");
|
467
|
-
|
468
|
-
result = fileExists("tmp.output")
|
486
|
+
SHOULD_NEVER_HAPPEN(200,
|
487
|
+
result = fileExists("tmp.output");
|
469
488
|
);
|
470
489
|
writeExact(connection, "world\n");
|
471
|
-
EVENTUALLY(
|
472
|
-
result =
|
490
|
+
EVENTUALLY(1,
|
491
|
+
result = fileExists("tmp.output");
|
473
492
|
);
|
474
|
-
shutdown(connection, SHUT_WR);
|
475
493
|
ensure_equals(stripHeaders(readAll(connection)), "ok");
|
476
494
|
}
|
477
495
|
|
478
|
-
TEST_METHOD(
|
479
|
-
|
496
|
+
TEST_METHOD(22) {
|
497
|
+
set_test_name("If PASSENGER_BUFFERING is true, and Transfer-Encoding is given, it buffers the request body.");
|
480
498
|
DeleteFileEventually file("tmp.output");
|
481
499
|
|
482
500
|
init();
|
@@ -484,7 +502,9 @@ namespace tut {
|
|
484
502
|
sendHeaders(defaultHeaders,
|
485
503
|
"PASSENGER_APP_ROOT", wsgiAppPath.c_str(),
|
486
504
|
"PASSENGER_BUFFERING", "true",
|
505
|
+
"REQUEST_METHOD", "POST",
|
487
506
|
"PATH_INFO", "/raw_upload_to_file",
|
507
|
+
"HTTP_TRANSFER_ENCODING", "chunked",
|
488
508
|
"HTTP_X_OUTPUT", (root + "/test/tmp.output").c_str(),
|
489
509
|
NULL);
|
490
510
|
writeExact(connection, "hello\n");
|
@@ -499,7 +519,7 @@ namespace tut {
|
|
499
519
|
ensure_equals(stripHeaders(readAll(connection)), "ok");
|
500
520
|
}
|
501
521
|
|
502
|
-
TEST_METHOD(
|
522
|
+
TEST_METHOD(24) {
|
503
523
|
set_test_name("Test buffering of large request bodies that fit in neither the socket "
|
504
524
|
"buffer nor the FileBackedPipe memory buffer, and that the application "
|
505
525
|
"cannot read quickly enough.");
|
@@ -517,8 +537,10 @@ namespace tut {
|
|
517
537
|
connect();
|
518
538
|
sendHeaders(defaultHeaders,
|
519
539
|
"PASSENGER_APP_ROOT", wsgiAppPath.c_str(),
|
540
|
+
"REQUEST_METHOD", "POST",
|
520
541
|
"PATH_INFO", "/raw_upload_to_file",
|
521
542
|
"PASSENGER_BUFFERING", "true",
|
543
|
+
"CONTENT_LENGTH", toString(requestBody.size()).c_str(),
|
522
544
|
"HTTP_X_WAIT_FOR_FILE", "/tmp/wait.txt",
|
523
545
|
"HTTP_X_OUTPUT", "/tmp/output.txt",
|
524
546
|
NULL);
|
@@ -539,12 +561,38 @@ namespace tut {
|
|
539
561
|
ensure_equals(buf.st_size, (off_t) requestBody.size());
|
540
562
|
}
|
541
563
|
|
542
|
-
TEST_METHOD(
|
543
|
-
|
564
|
+
TEST_METHOD(25) {
|
565
|
+
set_test_name("Test handling of slow clients that can't receive response data fast enough (response buffering).");
|
566
|
+
init();
|
567
|
+
connect();
|
568
|
+
sendHeaders(defaultHeaders,
|
569
|
+
"PASSENGER_APP_ROOT", wsgiAppPath.c_str(),
|
570
|
+
"PATH_INFO", "/blob",
|
571
|
+
"HTTP_X_SIZE", "10485760",
|
572
|
+
NULL);
|
573
|
+
EVENTUALLY(10,
|
574
|
+
result = containsSubstring(inspect(), "appInput reachedEnd = true");
|
575
|
+
);
|
576
|
+
string result = stripHeaders(readAll(connection));
|
577
|
+
ensure_equals(result.size(), 10485760u);
|
578
|
+
const char *data = result.data();
|
579
|
+
const char *end = result.data() + result.size();
|
580
|
+
while (data < end) {
|
581
|
+
ensure_equals(*data, 'x');
|
582
|
+
data++;
|
583
|
+
}
|
584
|
+
}
|
585
|
+
|
586
|
+
|
587
|
+
/***** Header handling tests *****/
|
588
|
+
|
589
|
+
TEST_METHOD(26) {
|
590
|
+
set_test_name("It replaces HTTP_CONTENT_LENGTH with CONTENT_LENGTH.");
|
544
591
|
init();
|
545
592
|
connect();
|
546
593
|
sendHeaders(defaultHeaders,
|
547
594
|
"PASSENGER_APP_ROOT", wsgiAppPath.c_str(),
|
595
|
+
"REQUEST_METHOD", "POST",
|
548
596
|
"PATH_INFO", "/env",
|
549
597
|
"HTTP_CONTENT_LENGTH", "5",
|
550
598
|
NULL);
|
@@ -554,8 +602,8 @@ namespace tut {
|
|
554
602
|
ensure(!containsSubstring(response, "HTTP_CONTENT_LENGTH"));
|
555
603
|
}
|
556
604
|
|
557
|
-
TEST_METHOD(
|
558
|
-
|
605
|
+
TEST_METHOD(27) {
|
606
|
+
set_test_name("It replaces HTTP_CONTENT_TYPE with CONTENT_TYPE.");
|
559
607
|
init();
|
560
608
|
connect();
|
561
609
|
sendHeaders(defaultHeaders,
|
@@ -568,8 +616,8 @@ namespace tut {
|
|
568
616
|
ensure(!containsSubstring(response, "HTTP_CONTENT_TYPE"));
|
569
617
|
}
|
570
618
|
|
571
|
-
TEST_METHOD(
|
572
|
-
|
619
|
+
TEST_METHOD(28) {
|
620
|
+
set_test_name("The response doesn't contain an HTTP status line if PASSENGER_STATUS_LINE is false.");
|
573
621
|
init();
|
574
622
|
connect();
|
575
623
|
sendHeaders(defaultHeaders,
|
@@ -582,9 +630,8 @@ namespace tut {
|
|
582
630
|
ensure(containsSubstring(response, "Status: 200 OK\r\n"));
|
583
631
|
}
|
584
632
|
|
585
|
-
TEST_METHOD(
|
586
|
-
|
587
|
-
// then a reason phrase is automatically appended.
|
633
|
+
TEST_METHOD(29) {
|
634
|
+
set_test_name("If the application outputs a status line without a reason phrase, then a reason phrase is automatically appended.");
|
588
635
|
init();
|
589
636
|
connect();
|
590
637
|
sendHeaders(defaultHeaders,
|
@@ -597,9 +644,8 @@ namespace tut {
|
|
597
644
|
ensure(containsSubstring(response, "Status: 201 Created\r\n"));
|
598
645
|
}
|
599
646
|
|
600
|
-
TEST_METHOD(
|
601
|
-
|
602
|
-
// then that reason phrase is used.
|
647
|
+
TEST_METHOD(30) {
|
648
|
+
set_test_name("If the application outputs a status line with a custom reason phrase, then that reason phrase is used.");
|
603
649
|
init();
|
604
650
|
connect();
|
605
651
|
sendHeaders(defaultHeaders,
|
@@ -611,37 +657,187 @@ namespace tut {
|
|
611
657
|
ensure(containsSubstring(response, "HTTP/1.1 201 Bunnies Jump\r\n"));
|
612
658
|
ensure(containsSubstring(response, "Status: 201 Bunnies Jump\r\n"));
|
613
659
|
}
|
614
|
-
|
615
|
-
TEST_METHOD(
|
616
|
-
|
617
|
-
|
660
|
+
|
661
|
+
TEST_METHOD(31) {
|
662
|
+
set_test_name("It appends a Date header if the app doesn't output one.");
|
663
|
+
|
664
|
+
init();
|
665
|
+
connect();
|
666
|
+
sendHeaders(defaultHeaders,
|
667
|
+
"PASSENGER_APP_ROOT", wsgiAppPath.c_str(),
|
668
|
+
"PATH_INFO", "/pid",
|
669
|
+
NULL);
|
670
|
+
|
671
|
+
string result = readAll(connection);
|
672
|
+
ensure(result.find("Date: ") != string::npos);
|
618
673
|
}
|
619
674
|
|
620
|
-
TEST_METHOD(
|
621
|
-
|
675
|
+
TEST_METHOD(32) {
|
676
|
+
set_test_name("It rejects non-GET, non-HEAD requests with an Upgrade header.");
|
677
|
+
|
622
678
|
init();
|
623
679
|
connect();
|
624
680
|
sendHeaders(defaultHeaders,
|
625
681
|
"PASSENGER_APP_ROOT", wsgiAppPath.c_str(),
|
626
|
-
"PATH_INFO", "/
|
627
|
-
"
|
682
|
+
"PATH_INFO", "/",
|
683
|
+
"REQUEST_METHOD", "POST",
|
684
|
+
"HTTP_UPGRADE", "WebSocket",
|
628
685
|
NULL);
|
629
|
-
|
630
|
-
|
686
|
+
string response = readAll(connection);
|
687
|
+
ensure(containsSubstring(response, "HTTP/1.1 400 Bad Request"));
|
688
|
+
}
|
689
|
+
|
690
|
+
TEST_METHOD(33) {
|
691
|
+
set_test_name("It rejects GET/HEAD requests with a Content-Length header.");
|
692
|
+
|
693
|
+
init();
|
694
|
+
connect();
|
695
|
+
sendHeaders(defaultHeaders,
|
696
|
+
"PASSENGER_APP_ROOT", wsgiAppPath.c_str(),
|
697
|
+
"PATH_INFO", "/",
|
698
|
+
"REQUEST_METHOD", "GET",
|
699
|
+
"CONTENT_LENGTH", "2",
|
700
|
+
NULL);
|
701
|
+
string response = readAll(connection);
|
702
|
+
ensure(containsSubstring(response, "HTTP/1.1 400 Bad Request"));
|
703
|
+
|
704
|
+
connect();
|
705
|
+
sendHeaders(defaultHeaders,
|
706
|
+
"PASSENGER_APP_ROOT", wsgiAppPath.c_str(),
|
707
|
+
"PATH_INFO", "/",
|
708
|
+
"REQUEST_METHOD", "HEAD",
|
709
|
+
"CONTENT_LENGTH", "2",
|
710
|
+
NULL);
|
711
|
+
response = readAll(connection);
|
712
|
+
ensure(containsSubstring(response, "HTTP/1.1 400 Bad Request"));
|
713
|
+
}
|
714
|
+
|
715
|
+
TEST_METHOD(34) {
|
716
|
+
set_test_name("It rejects GET/HEAD requests with a Transfer-Encoding header.");
|
717
|
+
|
718
|
+
init();
|
719
|
+
connect();
|
720
|
+
sendHeaders(defaultHeaders,
|
721
|
+
"PASSENGER_APP_ROOT", wsgiAppPath.c_str(),
|
722
|
+
"PATH_INFO", "/",
|
723
|
+
"REQUEST_METHOD", "GET",
|
724
|
+
"HTTP_TRANSFER_ENCODING", "chunked",
|
725
|
+
NULL);
|
726
|
+
string response = readAll(connection);
|
727
|
+
ensure(containsSubstring(response, "HTTP/1.1 400 Bad Request"));
|
728
|
+
|
729
|
+
connect();
|
730
|
+
sendHeaders(defaultHeaders,
|
731
|
+
"PASSENGER_APP_ROOT", wsgiAppPath.c_str(),
|
732
|
+
"PATH_INFO", "/",
|
733
|
+
"REQUEST_METHOD", "HEAD",
|
734
|
+
"HTTP_TRANSFER_ENCODING", "chunked",
|
735
|
+
NULL);
|
736
|
+
response = readAll(connection);
|
737
|
+
ensure(containsSubstring(response, "HTTP/1.1 400 Bad Request"));
|
738
|
+
}
|
739
|
+
|
740
|
+
|
741
|
+
/***** Advanced connection handling tests *****/
|
742
|
+
|
743
|
+
TEST_METHOD(40) {
|
744
|
+
set_test_name("It streams the request body to the application.");
|
745
|
+
DeleteFileEventually file("tmp.output");
|
746
|
+
|
747
|
+
init();
|
748
|
+
connect();
|
749
|
+
sendHeaders(defaultHeaders,
|
750
|
+
"PASSENGER_APP_ROOT", wsgiAppPath.c_str(),
|
751
|
+
"REQUEST_METHOD", "POST",
|
752
|
+
"PATH_INFO", "/raw_upload_to_file",
|
753
|
+
"HTTP_TRANSFER_ENCODING", "chunked",
|
754
|
+
"HTTP_X_OUTPUT", (root + "/test/tmp.output").c_str(),
|
755
|
+
NULL);
|
756
|
+
writeExact(connection, "hello\n");
|
757
|
+
EVENTUALLY(5,
|
758
|
+
result = fileExists("tmp.output") && readAll("tmp.output") == "hello\n";
|
759
|
+
);
|
760
|
+
writeExact(connection, "world\n");
|
761
|
+
EVENTUALLY(3,
|
762
|
+
result = readAll("tmp.output") == "hello\nworld\n";
|
631
763
|
);
|
764
|
+
shutdown(connection, SHUT_WR);
|
765
|
+
ensure_equals(stripHeaders(readAll(connection)), "ok");
|
766
|
+
}
|
767
|
+
|
768
|
+
TEST_METHOD(41) {
|
769
|
+
set_test_name("If no Content-Length and no Transfer-Encoding are given, and buffering is on: "
|
770
|
+
"it does not pass any request body data.");
|
771
|
+
|
772
|
+
DeleteFileEventually d("/tmp/output.txt");
|
773
|
+
|
774
|
+
init();
|
775
|
+
connect();
|
776
|
+
sendHeaders(defaultHeaders,
|
777
|
+
"PASSENGER_APP_ROOT", wsgiAppPath.c_str(),
|
778
|
+
"PATH_INFO", "/raw_upload_to_file",
|
779
|
+
"REQUEST_METHOD", "POST",
|
780
|
+
"PASSENGER_BUFFERING", "true",
|
781
|
+
"HTTP_X_OUTPUT", "/tmp/output.txt",
|
782
|
+
NULL);
|
783
|
+
writeExact(connection, "hello\n");
|
784
|
+
|
632
785
|
string result = stripHeaders(readAll(connection));
|
633
|
-
ensure_equals(result
|
634
|
-
|
635
|
-
|
636
|
-
|
637
|
-
ensure_equals(*data, 'x');
|
638
|
-
data++;
|
639
|
-
}
|
786
|
+
ensure_equals(result, "ok");
|
787
|
+
struct stat buf;
|
788
|
+
ensure(stat("/tmp/output.txt", &buf) == 0);
|
789
|
+
ensure_equals(buf.st_size, (off_t) 0);
|
640
790
|
}
|
641
791
|
|
642
|
-
TEST_METHOD(
|
643
|
-
set_test_name("
|
644
|
-
|
792
|
+
TEST_METHOD(42) {
|
793
|
+
set_test_name("If no Content-Length and no Transfer-Encoding are given, and buffering is off: "
|
794
|
+
"it does not pass any request body data.");
|
795
|
+
|
796
|
+
DeleteFileEventually d("/tmp/output.txt");
|
797
|
+
|
798
|
+
init();
|
799
|
+
connect();
|
800
|
+
sendHeaders(defaultHeaders,
|
801
|
+
"PASSENGER_APP_ROOT", wsgiAppPath.c_str(),
|
802
|
+
"PATH_INFO", "/raw_upload_to_file",
|
803
|
+
"REQUEST_METHOD", "POST",
|
804
|
+
"HTTP_X_OUTPUT", "/tmp/output.txt",
|
805
|
+
NULL);
|
806
|
+
writeExact(connection, "hello\n");
|
807
|
+
|
808
|
+
string result = stripHeaders(readAll(connection));
|
809
|
+
ensure_equals(result, "ok");
|
810
|
+
struct stat buf;
|
811
|
+
ensure(stat("/tmp/output.txt", &buf) == 0);
|
812
|
+
ensure_equals(buf.st_size, (off_t) 0);
|
813
|
+
}
|
814
|
+
|
815
|
+
TEST_METHOD(43) {
|
816
|
+
set_test_name("If Upgrade is given, it keeps passing the request body until end of stream.");
|
817
|
+
|
818
|
+
DeleteFileEventually d("/tmp/output.txt");
|
819
|
+
|
820
|
+
init();
|
821
|
+
connect();
|
822
|
+
sendHeaders(defaultHeaders,
|
823
|
+
"PASSENGER_APP_ROOT", wsgiAppPath.c_str(),
|
824
|
+
"PATH_INFO", "/raw_upload_to_file",
|
825
|
+
"HTTP_UPGRADE", "websocket",
|
826
|
+
"HTTP_X_OUTPUT", "/tmp/output.txt",
|
827
|
+
NULL);
|
828
|
+
writeExact(connection, "hello\n");
|
829
|
+
shutdown(connection, SHUT_WR);
|
830
|
+
|
831
|
+
string result = stripHeaders(readAll(connection));
|
832
|
+
ensure_equals(result, "ok");
|
833
|
+
struct stat buf;
|
834
|
+
ensure(stat("/tmp/output.txt", &buf) == 0);
|
835
|
+
ensure_equals(buf.st_size, (off_t) 6);
|
836
|
+
}
|
837
|
+
|
838
|
+
TEST_METHOD(45) {
|
839
|
+
set_test_name("If Content-Length is given, buffering is on, and request body is large: "
|
840
|
+
"it passes Content-Length bytes of the request body.");
|
645
841
|
|
646
842
|
DeleteFileEventually d("/tmp/output.txt");
|
647
843
|
|
@@ -656,6 +852,7 @@ namespace tut {
|
|
656
852
|
sendHeaders(defaultHeaders,
|
657
853
|
"PASSENGER_APP_ROOT", wsgiAppPath.c_str(),
|
658
854
|
"PATH_INFO", "/raw_upload_to_file",
|
855
|
+
"REQUEST_METHOD", "POST",
|
659
856
|
"CONTENT_LENGTH", toString(requestBody.size()).c_str(),
|
660
857
|
"PASSENGER_BUFFERING", "true",
|
661
858
|
"HTTP_X_OUTPUT", "/tmp/output.txt",
|
@@ -669,9 +866,9 @@ namespace tut {
|
|
669
866
|
ensure_equals(buf.st_size, (off_t) requestBody.size());
|
670
867
|
}
|
671
868
|
|
672
|
-
TEST_METHOD(
|
673
|
-
set_test_name("
|
674
|
-
|
869
|
+
TEST_METHOD(46) {
|
870
|
+
set_test_name("If Content-Length is given, buffering is on, and request body is small: "
|
871
|
+
"it passes Content-Length bytes of the request body.");
|
675
872
|
|
676
873
|
DeleteFileEventually d("/tmp/output.txt");
|
677
874
|
string requestBody = "hello world";
|
@@ -681,6 +878,7 @@ namespace tut {
|
|
681
878
|
sendHeaders(defaultHeaders,
|
682
879
|
"PASSENGER_APP_ROOT", wsgiAppPath.c_str(),
|
683
880
|
"PATH_INFO", "/raw_upload_to_file",
|
881
|
+
"REQUEST_METHOD", "POST",
|
684
882
|
"CONTENT_LENGTH", toString(requestBody.size()).c_str(),
|
685
883
|
"PASSENGER_BUFFERING", "true",
|
686
884
|
"HTTP_X_OUTPUT", "/tmp/output.txt",
|
@@ -694,9 +892,9 @@ namespace tut {
|
|
694
892
|
ensure_equals(buf.st_size, (off_t) requestBody.size());
|
695
893
|
}
|
696
894
|
|
697
|
-
TEST_METHOD(
|
698
|
-
set_test_name("
|
699
|
-
|
895
|
+
TEST_METHOD(47) {
|
896
|
+
set_test_name("If Content-Length is given, buffering is off, and request body is large: "
|
897
|
+
"it passes Content-Length bytes of the request body.");
|
700
898
|
|
701
899
|
DeleteFileEventually d("/tmp/output.txt");
|
702
900
|
|
@@ -713,6 +911,7 @@ namespace tut {
|
|
713
911
|
sendHeaders(defaultHeaders,
|
714
912
|
"PASSENGER_APP_ROOT", wsgiAppPath.c_str(),
|
715
913
|
"PATH_INFO", "/raw_upload_to_file",
|
914
|
+
"REQUEST_METHOD", "POST",
|
716
915
|
"CONTENT_LENGTH", toString(requestBody.size()).c_str(),
|
717
916
|
"HTTP_X_OUTPUT", "/tmp/output.txt",
|
718
917
|
NULL);
|
@@ -726,9 +925,9 @@ namespace tut {
|
|
726
925
|
ensure_equals(buf.st_size, (off_t) requestBody.size());
|
727
926
|
}
|
728
927
|
|
729
|
-
TEST_METHOD(
|
730
|
-
set_test_name("
|
731
|
-
|
928
|
+
TEST_METHOD(48) {
|
929
|
+
set_test_name("If Content-Length is given, buffering is off, and request body is small: "
|
930
|
+
"it passes Content-Length bytes of the request body.");
|
732
931
|
|
733
932
|
DeleteFileEventually d("/tmp/output.txt");
|
734
933
|
string requestBody = "hello world";
|
@@ -738,6 +937,7 @@ namespace tut {
|
|
738
937
|
sendHeaders(defaultHeaders,
|
739
938
|
"PASSENGER_APP_ROOT", wsgiAppPath.c_str(),
|
740
939
|
"PATH_INFO", "/raw_upload_to_file",
|
940
|
+
"REQUEST_METHOD", "POST",
|
741
941
|
"CONTENT_LENGTH", toString(requestBody.size()).c_str(),
|
742
942
|
"HTTP_X_OUTPUT", "/tmp/output.txt",
|
743
943
|
NULL);
|
@@ -751,122 +951,78 @@ namespace tut {
|
|
751
951
|
ensure_equals(buf.st_size, (off_t) requestBody.size());
|
752
952
|
}
|
753
953
|
|
754
|
-
TEST_METHOD(
|
755
|
-
set_test_name("
|
954
|
+
TEST_METHOD(49) {
|
955
|
+
set_test_name("If Transfer-Encoding is given and buffering is on: "
|
956
|
+
"it keeps passing the request body until end of stream.");
|
756
957
|
|
757
958
|
DeleteFileEventually d("/tmp/output.txt");
|
758
959
|
|
960
|
+
// 2.6 MB of request body. Guaranteed not to fit in any socket buffer.
|
961
|
+
string requestBody;
|
962
|
+
for (int i = 0; i < 204800; i++) {
|
963
|
+
requestBody.append("hello world!\n");
|
964
|
+
}
|
965
|
+
|
759
966
|
init();
|
760
967
|
connect();
|
761
968
|
sendHeaders(defaultHeaders,
|
762
969
|
"PASSENGER_APP_ROOT", wsgiAppPath.c_str(),
|
763
970
|
"PATH_INFO", "/raw_upload_to_file",
|
764
|
-
"
|
971
|
+
"REQUEST_METHOD", "POST",
|
765
972
|
"PASSENGER_BUFFERING", "true",
|
973
|
+
"HTTP_TRANSFER_ENCODING", "chunked",
|
766
974
|
"HTTP_X_OUTPUT", "/tmp/output.txt",
|
767
975
|
NULL);
|
768
|
-
writeExact(connection,
|
976
|
+
writeExact(connection, requestBody);
|
977
|
+
shutdown(connection, SHUT_WR);
|
769
978
|
|
770
979
|
string result = stripHeaders(readAll(connection));
|
771
980
|
ensure_equals(result, "ok");
|
772
981
|
struct stat buf;
|
773
982
|
ensure(stat("/tmp/output.txt", &buf) == 0);
|
774
|
-
ensure_equals(buf.st_size, (off_t)
|
983
|
+
ensure_equals(buf.st_size, (off_t) requestBody.size());
|
775
984
|
}
|
776
985
|
|
777
|
-
TEST_METHOD(
|
778
|
-
set_test_name("
|
986
|
+
TEST_METHOD(50) {
|
987
|
+
set_test_name("If Transfer-Encoding is given and buffering is off: "
|
988
|
+
"it keeps passing the request body until end of stream.");
|
779
989
|
|
780
990
|
DeleteFileEventually d("/tmp/output.txt");
|
781
991
|
|
992
|
+
// 2.6 MB of request body. Guaranteed not to fit in any socket buffer.
|
993
|
+
string requestBody;
|
994
|
+
for (int i = 0; i < 204800; i++) {
|
995
|
+
requestBody.append("hello world!\n");
|
996
|
+
}
|
997
|
+
|
782
998
|
init();
|
783
999
|
connect();
|
784
1000
|
sendHeaders(defaultHeaders,
|
785
1001
|
"PASSENGER_APP_ROOT", wsgiAppPath.c_str(),
|
786
1002
|
"PATH_INFO", "/raw_upload_to_file",
|
787
|
-
"
|
1003
|
+
"REQUEST_METHOD", "POST",
|
1004
|
+
"HTTP_TRANSFER_ENCODING", "chunked",
|
788
1005
|
"HTTP_X_OUTPUT", "/tmp/output.txt",
|
789
1006
|
NULL);
|
790
|
-
writeExact(connection,
|
1007
|
+
writeExact(connection, requestBody);
|
1008
|
+
shutdown(connection, SHUT_WR);
|
791
1009
|
|
792
1010
|
string result = stripHeaders(readAll(connection));
|
793
1011
|
ensure_equals(result, "ok");
|
794
1012
|
struct stat buf;
|
795
1013
|
ensure(stat("/tmp/output.txt", &buf) == 0);
|
796
|
-
ensure_equals(buf.st_size, (off_t)
|
797
|
-
}
|
798
|
-
|
799
|
-
TEST_METHOD(46) {
|
800
|
-
// If the application outputs a request oobw header, handler should remove the header, mark
|
801
|
-
// the process as oobw requested. The process should continue to process requests until the
|
802
|
-
// spawner spawns another process (to avoid the group being empty). As soon as the new
|
803
|
-
// process is spawned, the original process will make the oobw request. Afterwards, the
|
804
|
-
// original process is re-enabled.
|
805
|
-
init();
|
806
|
-
connect();
|
807
|
-
sendHeaders(defaultHeaders,
|
808
|
-
"PASSENGER_APP_ROOT", wsgiAppPath.c_str(),
|
809
|
-
"PATH_INFO", "/oobw",
|
810
|
-
NULL);
|
811
|
-
string response = readAll(connection);
|
812
|
-
ensure("status is not 200", containsSubstring(response, "Status: 200 OK\r\n"));
|
813
|
-
ensure("contains oowb header", !containsSubstring(response, "X-Passenger-Request-OOB-Work:"));
|
814
|
-
pid_t origPid = atoi(stripHeaders(response));
|
815
|
-
|
816
|
-
// Get a reference to the orignal process and verify oobw has been requested.
|
817
|
-
ProcessPtr origProcess;
|
818
|
-
{
|
819
|
-
LockGuard l(pool->syncher);
|
820
|
-
origProcess = pool->superGroups.get(wsgiAppPath)->defaultGroup->disablingProcesses.front();
|
821
|
-
ensure("OOBW requested", origProcess->oobwStatus == Process::OOBW_IN_PROGRESS);
|
822
|
-
}
|
823
|
-
ensure("sanity check", origPid == origProcess->pid); // just a sanity check
|
824
|
-
|
825
|
-
// Issue requests until the new process handles it.
|
826
|
-
pid_t pid;
|
827
|
-
EVENTUALLY(2,
|
828
|
-
connect();
|
829
|
-
sendHeaders(defaultHeaders,
|
830
|
-
"PASSENGER_APP_ROOT", wsgiAppPath.c_str(),
|
831
|
-
"PATH_INFO", "/pid",
|
832
|
-
NULL);
|
833
|
-
string response = readAll(connection);
|
834
|
-
ensure("status is 200", containsSubstring(response, "Status: 200 OK\r\n"));
|
835
|
-
pid = atoi(stripHeaders(response));
|
836
|
-
result = (pid != origPid);
|
837
|
-
);
|
838
|
-
|
839
|
-
// Wait for the original process to finish oobw request.
|
840
|
-
EVENTUALLY(2,
|
841
|
-
boost::unique_lock<boost::mutex> lock(pool->syncher);
|
842
|
-
result = origProcess->oobwStatus == Process::OOBW_NOT_ACTIVE;
|
843
|
-
);
|
844
|
-
|
845
|
-
// Final asserts.
|
846
|
-
{
|
847
|
-
boost::unique_lock<boost::mutex> lock(pool->syncher);
|
848
|
-
ensure_equals("2 enabled processes", pool->superGroups.get(wsgiAppPath)->defaultGroup->enabledProcesses.size(), 2u);
|
849
|
-
ensure_equals("oobw is reset", origProcess->oobwStatus, Process::OOBW_NOT_ACTIVE);
|
850
|
-
ensure_equals("process is enabled", origProcess->enabled, Process::ENABLED);
|
851
|
-
}
|
1014
|
+
ensure_equals(buf.st_size, (off_t) requestBody.size());
|
852
1015
|
}
|
853
1016
|
|
854
|
-
TEST_METHOD(
|
855
|
-
set_test_name("
|
856
|
-
|
857
|
-
init();
|
858
|
-
connect();
|
859
|
-
sendHeaders(defaultHeaders,
|
860
|
-
"PASSENGER_APP_ROOT", wsgiAppPath.c_str(),
|
861
|
-
"PATH_INFO", "/pid",
|
862
|
-
NULL);
|
1017
|
+
TEST_METHOD(51) {
|
1018
|
+
set_test_name("If Transfer-Encoding is given and the application socket uses the HTTP protocol, "
|
1019
|
+
"rechunk the body when forwarding it to the application.");
|
863
1020
|
|
864
|
-
|
865
|
-
ensure(result.find("Date: ") != string::npos);
|
1021
|
+
fprintf(stderr, "TODO: implement test 51\n");
|
866
1022
|
}
|
867
1023
|
|
868
|
-
TEST_METHOD(
|
869
|
-
set_test_name("It
|
1024
|
+
TEST_METHOD(54) {
|
1025
|
+
set_test_name("It writes an appropriate response if the request queue is overflown.");
|
870
1026
|
|
871
1027
|
initPoolDebugging();
|
872
1028
|
debug->restarting = false;
|
@@ -883,8 +1039,8 @@ namespace tut {
|
|
883
1039
|
ensure(response.find("This website is under heavy load") != string::npos);
|
884
1040
|
}
|
885
1041
|
|
886
|
-
TEST_METHOD(
|
887
|
-
set_test_name("It
|
1042
|
+
TEST_METHOD(55) {
|
1043
|
+
set_test_name("It uses the status code dictated by PASSENGER_REQUEST_QUEUE_OVERFLOW_STATUS_CODE "
|
888
1044
|
"if the request queue is overflown");
|
889
1045
|
|
890
1046
|
initPoolDebugging();
|
@@ -903,7 +1059,7 @@ namespace tut {
|
|
903
1059
|
ensure(response.find("This website is under heavy load") != string::npos);
|
904
1060
|
}
|
905
1061
|
|
906
|
-
TEST_METHOD(
|
1062
|
+
TEST_METHOD(56) {
|
907
1063
|
set_test_name("PASSENGER_REQUEST_QUEUE_OVERFLOW_STATUS_CODE should work even if it is an unknown code");
|
908
1064
|
|
909
1065
|
initPoolDebugging();
|
@@ -922,8 +1078,8 @@ namespace tut {
|
|
922
1078
|
ensure(response.find("This website is under heavy load") != string::npos);
|
923
1079
|
}
|
924
1080
|
|
925
|
-
TEST_METHOD(
|
926
|
-
set_test_name("It relieves the application process after having read its entire response data");
|
1081
|
+
TEST_METHOD(57) {
|
1082
|
+
set_test_name("It relieves the application process after having read its entire response data.");
|
927
1083
|
|
928
1084
|
init();
|
929
1085
|
connect();
|
@@ -947,8 +1103,8 @@ namespace tut {
|
|
947
1103
|
}
|
948
1104
|
}
|
949
1105
|
|
950
|
-
TEST_METHOD(
|
951
|
-
set_test_name("It supports responses in chunked transfer encoding");
|
1106
|
+
TEST_METHOD(58) {
|
1107
|
+
set_test_name("It supports responses in chunked transfer encoding.");
|
952
1108
|
|
953
1109
|
init();
|
954
1110
|
connect();
|
@@ -974,8 +1130,8 @@ namespace tut {
|
|
974
1130
|
ensure(containsSubstring(response, "Counter: 2\n"));
|
975
1131
|
}
|
976
1132
|
|
977
|
-
TEST_METHOD(
|
978
|
-
set_test_name("It supports switching protocols when communicating over application session sockets");
|
1133
|
+
TEST_METHOD(59) {
|
1134
|
+
set_test_name("It supports switching protocols when communicating over application session sockets.");
|
979
1135
|
|
980
1136
|
init();
|
981
1137
|
connect();
|
@@ -1008,8 +1164,8 @@ namespace tut {
|
|
1008
1164
|
ensure_equals(io.readLine(), "Echo: hello\n");
|
1009
1165
|
}
|
1010
1166
|
|
1011
|
-
TEST_METHOD(
|
1012
|
-
set_test_name("It supports switching protocols when communication over application http_session sockets");
|
1167
|
+
TEST_METHOD(60) {
|
1168
|
+
set_test_name("It supports switching protocols when communication over application http_session sockets.");
|
1013
1169
|
|
1014
1170
|
init();
|
1015
1171
|
connect();
|
@@ -1052,6 +1208,66 @@ namespace tut {
|
|
1052
1208
|
ensure_equals(io.readLine(), "Echo: hello\n");
|
1053
1209
|
}
|
1054
1210
|
|
1211
|
+
|
1212
|
+
/***** Out-of-band work tests *****/
|
1213
|
+
|
1214
|
+
TEST_METHOD(65) {
|
1215
|
+
set_test_name("If the application outputs a request oobw header, handler should remove the header, mark "
|
1216
|
+
"the process as oobw requested. The process should continue to process requests until the "
|
1217
|
+
"spawner spawns another process (to avoid the group being empty). As soon as the new "
|
1218
|
+
"process is spawned, the original process will make the oobw request. Afterwards, the "
|
1219
|
+
"original process is re-enabled.");
|
1220
|
+
init();
|
1221
|
+
connect();
|
1222
|
+
sendHeaders(defaultHeaders,
|
1223
|
+
"PASSENGER_APP_ROOT", wsgiAppPath.c_str(),
|
1224
|
+
"PATH_INFO", "/oobw",
|
1225
|
+
NULL);
|
1226
|
+
string response = readAll(connection);
|
1227
|
+
ensure("status is not 200", containsSubstring(response, "Status: 200 OK\r\n"));
|
1228
|
+
ensure("contains oowb header", !containsSubstring(response, "X-Passenger-Request-OOB-Work:"));
|
1229
|
+
pid_t origPid = atoi(stripHeaders(response));
|
1230
|
+
|
1231
|
+
// Get a reference to the orignal process and verify oobw has been requested.
|
1232
|
+
ProcessPtr origProcess;
|
1233
|
+
{
|
1234
|
+
LockGuard l(pool->syncher);
|
1235
|
+
origProcess = pool->superGroups.get(wsgiAppPath)->defaultGroup->disablingProcesses.front();
|
1236
|
+
ensure("OOBW requested", origProcess->oobwStatus == Process::OOBW_IN_PROGRESS);
|
1237
|
+
}
|
1238
|
+
ensure("sanity check", origPid == origProcess->pid); // just a sanity check
|
1239
|
+
|
1240
|
+
// Issue requests until the new process handles it.
|
1241
|
+
pid_t pid;
|
1242
|
+
EVENTUALLY(2,
|
1243
|
+
connect();
|
1244
|
+
sendHeaders(defaultHeaders,
|
1245
|
+
"PASSENGER_APP_ROOT", wsgiAppPath.c_str(),
|
1246
|
+
"PATH_INFO", "/pid",
|
1247
|
+
NULL);
|
1248
|
+
string response = readAll(connection);
|
1249
|
+
ensure("status is 200", containsSubstring(response, "Status: 200 OK\r\n"));
|
1250
|
+
pid = atoi(stripHeaders(response));
|
1251
|
+
result = (pid != origPid);
|
1252
|
+
);
|
1253
|
+
|
1254
|
+
// Wait for the original process to finish oobw request.
|
1255
|
+
EVENTUALLY(2,
|
1256
|
+
boost::unique_lock<boost::mutex> lock(pool->syncher);
|
1257
|
+
result = origProcess->oobwStatus == Process::OOBW_NOT_ACTIVE;
|
1258
|
+
);
|
1259
|
+
|
1260
|
+
// Final asserts.
|
1261
|
+
{
|
1262
|
+
boost::unique_lock<boost::mutex> lock(pool->syncher);
|
1263
|
+
ensure_equals("2 enabled processes", pool->superGroups.get(wsgiAppPath)->defaultGroup->enabledProcesses.size(), 2u);
|
1264
|
+
ensure_equals("oobw is reset", origProcess->oobwStatus, Process::OOBW_NOT_ACTIVE);
|
1265
|
+
ensure_equals("process is enabled", origProcess->enabled, Process::ENABLED);
|
1266
|
+
}
|
1267
|
+
}
|
1268
|
+
|
1055
1269
|
// Test small response buffering.
|
1056
1270
|
// Test large response buffering.
|
1271
|
+
|
1272
|
+
/***************************/
|
1057
1273
|
}
|