passenger 4.0.38 → 4.0.39

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

Potentially problematic release.


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

Files changed (39) hide show
  1. checksums.yaml +8 -8
  2. checksums.yaml.gz.asc +7 -7
  3. data.tar.gz.asc +7 -7
  4. data/CHANGELOG +24 -0
  5. data/bin/passenger +5 -0
  6. data/bin/passenger-install-apache2-module +1 -1
  7. data/bin/passenger-install-nginx-module +1 -1
  8. data/build/apache2.rb +2 -6
  9. data/build/cxx_tests.rb +5 -0
  10. data/build/packaging.rb +1 -0
  11. data/dev/list_tests.rb +36 -0
  12. data/doc/Users guide Standalone.txt +20 -5
  13. data/doc/users_guide_snippets/analysis_and_system_maintenance.txt +6 -13
  14. data/ext/common/Constants.h +1 -1
  15. data/ext/common/MessageClient.h +7 -7
  16. data/ext/common/Utils.cpp +6 -2
  17. data/ext/common/Utils/MessageIO.h +6 -11
  18. data/ext/common/agents/HelperAgent/RequestHandler.cpp +6 -3
  19. data/ext/common/agents/HelperAgent/RequestHandler.h +146 -24
  20. data/ext/ruby/extconf.rb +12 -0
  21. data/ext/ruby/passenger_native_support.c +18 -13
  22. data/helper-scripts/wsgi-loader.py +25 -2
  23. data/lib/phusion_passenger.rb +1 -1
  24. data/lib/phusion_passenger/config/about_command.rb +3 -0
  25. data/lib/phusion_passenger/rack/thread_handler_extension.rb +1 -4
  26. data/lib/phusion_passenger/request_handler/thread_handler.rb +0 -21
  27. data/lib/phusion_passenger/standalone/command.rb +10 -3
  28. data/lib/phusion_passenger/standalone/start_command.rb +4 -0
  29. data/lib/phusion_passenger/utils/tee_input.rb +82 -14
  30. data/lib/phusion_passenger/utils/terminal_choice_menu.rb +17 -2
  31. data/lib/phusion_passenger/utils/unseekable_socket.rb +0 -30
  32. data/resources/templates/error_layout.html.template +1 -1
  33. data/resources/templates/standalone/config.erb +18 -5
  34. data/test/cxx/RequestHandlerTest.cpp +410 -194
  35. data/test/integration_tests/native_packaging_spec.rb +5 -1
  36. data/test/ruby/request_handler_spec.rb +4 -71
  37. data/test/ruby/utils/tee_input_spec.rb +235 -0
  38. metadata +5 -3
  39. metadata.gz.asc +7 -7
@@ -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 PASSENGER_ENV)</dt>
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
- # This file is autogenerated by Phusion Passenger Standalone
3
- # from <%= template_filename %>
4
- # Please edit that file instead.
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
- args.push_back(StaticString(it->first.data(), it->first.size() + 1));
108
- args.push_back(StaticString(it->second.data(), it->second.size() + 1));
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
- args.push_back(StaticString(arg, strlen(arg) + 1));
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
- shared_array<StaticString> args_array(new StaticString[args.size() + 2]);
118
- unsigned int totalSize = 0;
119
- for (unsigned int i = 0; i < args.size(); i++) {
120
- args_array[i + 1] = args[i];
121
- totalSize += args[i].size();
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
- args_array[0] = StaticString(totalSizeString);
126
- args_array[args.size() + 1] = ",";
127
-
128
- gatheredWrite(connection, args_array.get(), args.size() + 2, NULL);
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
- // Test one normal request.
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
- // Test multiple normal requests.
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
- // Test sending request data in pieces.
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
- // It denies access if the connect password is wrong.
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(5) {
274
- // It disconnects us if the connect password is not sent within a certain time.
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(6) {
289
- // It works correct if the connect password is sent in pieces.
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
- TEST_METHOD(7) {
306
- // It closes the connection with the application if the client has closed the connection.
307
- init();
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
- // If the app crashes at startup without an error page then it renders
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
- // If the app crashes at startup with an error page then it renders
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
- // If spawning fails because of an internal error then it reports the error appropriately.
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
- // Error pages respect the PASSENGER_STATUS_LINE option.
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
- // If PASSENGER_FRIENDLY_ERROR_PAGES is false then it does not render
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
- TEST_METHOD(20) {
456
- // It streams the request body to the application.
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
- EVENTUALLY(5,
468
- result = fileExists("tmp.output") && readAll("tmp.output") == "hello\n";
486
+ SHOULD_NEVER_HAPPEN(200,
487
+ result = fileExists("tmp.output");
469
488
  );
470
489
  writeExact(connection, "world\n");
471
- EVENTUALLY(3,
472
- result = readAll("tmp.output") == "hello\nworld\n";
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(21) {
479
- // It buffers the request body if PASSENGER_BUFFERING is true.
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(22) {
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(30) {
543
- // It replaces HTTP_CONTENT_LENGTH with CONTENT_LENGTH.
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(31) {
558
- // It replaces HTTP_CONTENT_TYPE with CONTENT_TYPE.
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(35) {
572
- // The response doesn't contain an HTTP status line if PASSENGER_STATUS_LINE is false.
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(36) {
586
- // If the application outputs a status line without a reason phrase,
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(37) {
601
- // If the application outputs a status line with a custom reason phrase,
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(38) {
616
- // If the application doesn't output a status line then it rejects the application response.
617
- // TODO
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(39) {
621
- // Test handling of slow clients that can't receive response data fast enough (response buffering).
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", "/blob",
627
- "HTTP_X_SIZE", "10485760",
682
+ "PATH_INFO", "/",
683
+ "REQUEST_METHOD", "POST",
684
+ "HTTP_UPGRADE", "WebSocket",
628
685
  NULL);
629
- EVENTUALLY(10,
630
- result = containsSubstring(inspect(), "appInput reachedEnd = true");
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.size(), 10485760u);
634
- const char *data = result.data();
635
- const char *end = result.data() + result.size();
636
- while (data < end) {
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(40) {
643
- set_test_name("Test that RequestHandler does not read more than CONTENT_LENGTH bytes "
644
- "from the client body (when buffering is on and request body is large).");
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(41) {
673
- set_test_name("Test that RequestHandler does not read more than CONTENT_LENGTH bytes "
674
- "from the client body (when buffering is on and request body is small).");
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(42) {
698
- set_test_name("Test that RequestHandler does not read more than CONTENT_LENGTH bytes "
699
- "from the client body (when buffering is off and request body is large).");
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(43) {
730
- set_test_name("Test that RequestHandler does not read more than CONTENT_LENGTH bytes "
731
- "from the client body (when buffering is off and request body is small).");
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(44) {
755
- set_test_name("Test that RequestHandler does not pass any client body data when CONTENT_LENGTH == 0 (when buffering is on).");
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
- "CONTENT_LENGTH", "0",
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, "hello world");
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) 0);
983
+ ensure_equals(buf.st_size, (off_t) requestBody.size());
775
984
  }
776
985
 
777
- TEST_METHOD(45) {
778
- set_test_name("Test that RequestHandler does not pass any client body data when CONTENT_LENGTH == 0 (when buffering is off).");
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
- "CONTENT_LENGTH", "0",
1003
+ "REQUEST_METHOD", "POST",
1004
+ "HTTP_TRANSFER_ENCODING", "chunked",
788
1005
  "HTTP_X_OUTPUT", "/tmp/output.txt",
789
1006
  NULL);
790
- writeExact(connection, "hello world");
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) 0);
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(47) {
855
- set_test_name("The RequestHandler should append a Date header if the app doesn't output one.");
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
- string result = readAll(connection);
865
- ensure(result.find("Date: ") != string::npos);
1021
+ fprintf(stderr, "TODO: implement test 51\n");
866
1022
  }
867
1023
 
868
- TEST_METHOD(48) {
869
- set_test_name("It should write an appropriate response if the request queue is overflown");
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(49) {
887
- set_test_name("It should use the status code dictated by PASSENGER_REQUEST_QUEUE_OVERFLOW_STATUS_CODE "
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(50) {
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(51) {
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(52) {
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(53) {
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(54) {
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
  }