passenger 4.0.45 → 4.0.46

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 (94) hide show
  1. checksums.yaml +8 -8
  2. checksums.yaml.gz.asc +7 -7
  3. data.tar.gz.asc +7 -7
  4. data/.editorconfig +19 -0
  5. data/CHANGELOG +47 -0
  6. data/CONTRIBUTING.md +9 -1
  7. data/CONTRIBUTORS +4 -0
  8. data/Vagrantfile +7 -3
  9. data/build/agents.rb +1 -0
  10. data/build/misc.rb +6 -4
  11. data/dev/vagrant/bashrc +2 -0
  12. data/doc/Design and Architecture.txt +9 -7
  13. data/doc/Users guide Apache.idmap.txt +2 -0
  14. data/doc/Users guide Apache.txt +24 -4
  15. data/doc/Users guide Nginx.idmap.txt +4 -0
  16. data/doc/Users guide Nginx.txt +23 -4
  17. data/doc/images/code_walkthrough.jpg +0 -0
  18. data/doc/users_guide_snippets/installation.txt +38 -0
  19. data/ext/common/AgentsStarter.h +6 -1
  20. data/ext/common/ApplicationPool2/Common.h +17 -2
  21. data/ext/common/ApplicationPool2/DirectSpawner.h +5 -11
  22. data/ext/common/ApplicationPool2/DummySpawner.h +2 -4
  23. data/ext/common/ApplicationPool2/ErrorRenderer.h +119 -0
  24. data/ext/common/ApplicationPool2/Implementation.cpp +159 -11
  25. data/ext/common/ApplicationPool2/Options.h +16 -7
  26. data/ext/common/ApplicationPool2/Pool.h +28 -24
  27. data/ext/common/ApplicationPool2/Process.h +1 -9
  28. data/ext/common/ApplicationPool2/SmartSpawner.h +15 -18
  29. data/ext/common/ApplicationPool2/Spawner.h +18 -14
  30. data/ext/common/ApplicationPool2/SpawnerFactory.h +12 -30
  31. data/ext/common/Constants.h +1 -1
  32. data/ext/common/Exceptions.h +15 -2
  33. data/ext/common/UnionStation/Core.h +9 -0
  34. data/ext/common/Utils/JsonUtils.h +53 -0
  35. data/ext/common/Utils/ProcessMetricsCollector.h +1 -1
  36. data/ext/common/Utils/SpeedMeter.h +7 -3
  37. data/ext/common/Utils/SystemMetricsCollector.h +8 -6
  38. data/ext/common/agents/HelperAgent/Main.cpp +4 -4
  39. data/ext/common/agents/HelperAgent/RequestHandler.h +115 -56
  40. data/ext/nginx/ConfigurationCommands.c +1 -1
  41. data/ext/nginx/ConfigurationCommands.c.erb +6 -1
  42. data/ext/nginx/ContentHandler.c +2 -1
  43. data/ext/nginx/config +1 -1
  44. data/helper-scripts/node-loader.js +23 -0
  45. data/helper-scripts/wsgi-loader.py +12 -4
  46. data/lib/phusion_passenger.rb +1 -1
  47. data/lib/phusion_passenger/active_support3_extensions/init.rb +39 -78
  48. data/lib/phusion_passenger/constants.rb +3 -1
  49. data/lib/phusion_passenger/loader_shared_helpers.rb +10 -5
  50. data/lib/phusion_passenger/nginx/config_options.rb +3 -1
  51. data/lib/phusion_passenger/packaging.rb +1 -0
  52. data/lib/phusion_passenger/public_api.rb +108 -16
  53. data/lib/phusion_passenger/rack/thread_handler_extension.rb +1 -0
  54. data/lib/phusion_passenger/request_handler.rb +2 -2
  55. data/lib/phusion_passenger/request_handler/thread_handler.rb +28 -46
  56. data/lib/phusion_passenger/standalone/command.rb +8 -1
  57. data/lib/phusion_passenger/standalone/main.rb +0 -1
  58. data/lib/phusion_passenger/standalone/start_command.rb +4 -0
  59. data/lib/phusion_passenger/union_station/connection.rb +67 -0
  60. data/lib/phusion_passenger/{analytics_logger.rb → union_station/core.rb} +55 -256
  61. data/lib/phusion_passenger/union_station/transaction.rb +168 -0
  62. data/lib/phusion_passenger/utils.rb +4 -0
  63. data/lib/phusion_passenger/utils/lock.rb +62 -0
  64. data/resources/mime.types +1 -0
  65. data/resources/templates/error_layout.html.template +2 -0
  66. data/resources/templates/standalone/config.erb +1 -0
  67. data/test/cxx/ApplicationPool2/DirectSpawnerTest.cpp +5 -3
  68. data/test/cxx/ApplicationPool2/PoolTest.cpp +13 -3
  69. data/test/cxx/ApplicationPool2/SmartSpawnerTest.cpp +16 -13
  70. data/test/cxx/ApplicationPool2/SpawnerTestCases.cpp +6 -0
  71. data/test/cxx/FileBackedPipeTest.cpp +1 -1
  72. data/test/cxx/RequestHandlerTest.cpp +158 -2
  73. data/test/cxx/ServerInstanceDirTest.cpp +2 -0
  74. data/test/cxx/TestSupport.h +21 -2
  75. data/test/cxx/UtilsTest.cpp +1 -0
  76. data/test/ruby/classic_rails/loader_spec.rb +0 -1
  77. data/test/ruby/classic_rails/preloader_spec.rb +0 -1
  78. data/test/ruby/rails3.0/loader_spec.rb +2 -2
  79. data/test/ruby/rails3.0/preloader_spec.rb +2 -2
  80. data/test/ruby/rails3.1/loader_spec.rb +2 -2
  81. data/test/ruby/rails3.1/preloader_spec.rb +2 -2
  82. data/test/ruby/rails3.2/loader_spec.rb +2 -2
  83. data/test/ruby/rails3.2/preloader_spec.rb +2 -2
  84. data/test/ruby/rails4.0/loader_spec.rb +2 -2
  85. data/test/ruby/rails4.0/preloader_spec.rb +2 -2
  86. data/test/ruby/request_handler_spec.rb +8 -8
  87. data/test/ruby/shared/rails/{analytics_logging_extensions_sharedspec.rb → union_station_extensions_sharedspec.rb} +5 -4
  88. data/test/ruby/union_station_spec.rb +283 -0
  89. data/test/stub/wsgi/passenger_wsgi.py +41 -5
  90. metadata +12 -7
  91. metadata.gz.asc +7 -7
  92. data/helper-scripts/wsgi-preloader.py +0 -1
  93. data/lib/phusion_passenger/standalone/package_runtime_command.rb +0 -105
  94. data/test/ruby/analytics_logger_spec.rb +0 -283
@@ -90,7 +90,7 @@
90
90
 
91
91
  #define NGINX_DOC_URL "https://www.phusionpassenger.com/documentation/Users%20guide%20Nginx.html"
92
92
 
93
- #define PASSENGER_VERSION "4.0.45"
93
+ #define PASSENGER_VERSION "4.0.46"
94
94
 
95
95
  #define POOL_HELPER_THREAD_STACK_SIZE 262144
96
96
 
@@ -255,17 +255,26 @@ class SpawnException: public oxt::tracable_exception {
255
255
  public:
256
256
  enum ErrorKind {
257
257
  UNDEFINED_ERROR,
258
- /** The preloader failed to start, not due to a wrong protocol message. */
258
+ /** The preloader failed to start, not due to a wrong protocol message.
259
+ * It did not explicitly supply an error message.
260
+ */
259
261
  PRELOADER_STARTUP_ERROR,
260
262
  /** The preloader sent a wrong protocol message during startup. */
261
263
  PRELOADER_STARTUP_PROTOCOL_ERROR,
264
+ /** The preloader timed out during startup. */
262
265
  PRELOADER_STARTUP_TIMEOUT,
266
+ /** The preloader failed to start, not due to a wrong protocol message.
267
+ * It DID explicitly supply an error message. */
263
268
  PRELOADER_STARTUP_EXPLAINABLE_ERROR,
264
- /** The application failed to start, not due to a wrong protocol message. */
269
+ /** The application failed to start, not due to a wrong protocol message.
270
+ * It did not explicitly supply an error message. */
265
271
  APP_STARTUP_ERROR,
266
272
  /** The application sent a wrong protocol message during startup. */
267
273
  APP_STARTUP_PROTOCOL_ERROR,
274
+ /** The application timed out during startup. */
268
275
  APP_STARTUP_TIMEOUT,
276
+ /** The application failed to start, not due to a wrong protocol message.
277
+ * It DID explicitly supply an error message. */
269
278
  APP_STARTUP_EXPLAINABLE_ERROR
270
279
  };
271
280
 
@@ -342,6 +351,10 @@ public:
342
351
  return get(name);
343
352
  }
344
353
 
354
+ void set(const string &name, const string &value) {
355
+ annotations[name] = value;
356
+ }
357
+
345
358
  string get(const string &name) const {
346
359
  map<string, string>::const_iterator it = annotations.find(name);
347
360
  if (it == annotations.end()) {
@@ -381,6 +381,9 @@ public:
381
381
  // Get a connection to the logging server.
382
382
  ConnectionPtr connection = checkoutConnection();
383
383
  if (connection == NULL) {
384
+ P_TRACE(2, "Created NULL Union Station transaction: group=" << groupName <<
385
+ ", category=" << category << ", txnId=" <<
386
+ StaticString(txnId, txnIdEnd - txnId));
384
387
  return createNullTransaction();
385
388
  }
386
389
 
@@ -395,8 +398,14 @@ public:
395
398
  category,
396
399
  unionStationKey);
397
400
  guard.clear();
401
+ P_TRACE(2, "Created new Union Station transaction: group=" << groupName <<
402
+ ", category=" << category << ", txnId=" <<
403
+ StaticString(txnId, txnIdEnd - txnId));
398
404
  return transaction;
399
405
  } else {
406
+ P_TRACE(2, "Created NULL Union Station transaction: group=" << groupName <<
407
+ ", category=" << category << ", txnId=" <<
408
+ StaticString(txnId, txnIdEnd - txnId));
400
409
  return createNullTransaction();
401
410
  }
402
411
  }
@@ -0,0 +1,53 @@
1
+ /*
2
+ * Phusion Passenger - https://www.phusionpassenger.com/
3
+ * Copyright (c) 2014 Phusion
4
+ *
5
+ * "Phusion Passenger" is a trademark of Hongli Lai & Ninh Bui.
6
+ *
7
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
8
+ * of this software and associated documentation files (the "Software"), to deal
9
+ * in the Software without restriction, including without limitation the rights
10
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
11
+ * copies of the Software, and to permit persons to whom the Software is
12
+ * furnished to do so, subject to the following conditions:
13
+ *
14
+ * The above copyright notice and this permission notice shall be included in
15
+ * all copies or substantial portions of the Software.
16
+ *
17
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
18
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
19
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
20
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
21
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
22
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
23
+ * THE SOFTWARE.
24
+ */
25
+ #ifndef _PASSENGER_UTILS_JSON_UTILS_H_
26
+ #define _PASSENGER_UTILS_JSON_UTILS_H_
27
+
28
+ #include <string>
29
+ #include <StaticString.h>
30
+ #include <Utils/json.h>
31
+ #include <Utils/StrIntUtils.h>
32
+
33
+ namespace Passenger {
34
+
35
+ using namespace std;
36
+
37
+ inline string
38
+ stringifyJson(const Json::Value &value) {
39
+ Json::FastWriter writer;
40
+ string str = writer.write(value);
41
+ str.erase(str.size() - 1, 1);
42
+ return str;
43
+ }
44
+
45
+ /** `str` MUST be NULL-terminated! */
46
+ inline string
47
+ jsonString(const Passenger::StaticString &str) {
48
+ return stringifyJson(Json::Value(Json::StaticString(str.data())));
49
+ }
50
+
51
+ } // namespace Passenger
52
+
53
+ #endif /* _PASSENGER_UTILS_JSON_UTILS_H_ */
@@ -92,7 +92,7 @@ struct ProcessMetrics {
92
92
  */
93
93
  ssize_t swap;
94
94
  /** OS X Snow Leopard does not report the VM size correctly, so don't use this. */
95
- size_t vmsize;
95
+ ssize_t vmsize;
96
96
  pid_t processGroupId;
97
97
  uid_t uid;
98
98
  string command;
@@ -191,7 +191,7 @@ public:
191
191
  return count;
192
192
  }
193
193
 
194
- /** Current speed over the configured window. Returns NaN if less than 2
194
+ /** Current speed over the configured window. Returns unknownSpeed() if less than 2
195
195
  * samples have been collected so far.
196
196
  */
197
197
  double currentSpeed() const {
@@ -221,16 +221,20 @@ public:
221
221
  }
222
222
  avgWeight /= std::max(1, (int) count - 1 - begin);
223
223
 
224
- interval = getSample(count - 1).timestamp - getSample(begin).timestamp;
224
+ interval = getSample((int) count - 1).timestamp - getSample(begin).timestamp;
225
225
  if (interval > 0) {
226
226
  // sum / interval is the speed per average delta interval,
227
227
  // so we extrapolate that over the entire window interval.
228
228
  return (sum / interval) * (window / avgWeight);
229
229
  } else {
230
- return numeric_limits<double>::quiet_NaN();
230
+ return unknownSpeed();
231
231
  }
232
232
  }
233
233
 
234
+ static ValueType unknownSpeed() {
235
+ return numeric_limits<ValueType>::max();
236
+ }
237
+
234
238
  #if 1
235
239
  void debug() const {
236
240
  const unsigned long long timeThreshold = getTimeThreshold();
@@ -45,7 +45,6 @@
45
45
  #include <sys/sysinfo.h>
46
46
  #include <Exceptions.h>
47
47
  #include <Utils/StringScanning.h>
48
- #include <Utils/SpeedMeter.h>
49
48
  #include <Utils/IOUtils.h>
50
49
  #endif
51
50
  #ifdef __APPLE__
@@ -69,6 +68,7 @@
69
68
  #include <Utils/StrIntUtils.h>
70
69
  #include <Utils/SystemTime.h>
71
70
  #include <Utils/AnsiColorConstants.h>
71
+ #include <Utils/SpeedMeter.h>
72
72
 
73
73
  /*
74
74
  * Useful resources
@@ -346,13 +346,15 @@ public:
346
346
  time_t boottime;
347
347
 
348
348
  /** Speed at which processes are created per second.
349
- * NaN if it's not yet known (because too few samples have been taken so far).
349
+ * SpeedMeter<unsigned long long>::unknownSpeed() if it's not yet known (because too
350
+ * few samples have been taken so far).
350
351
  * -1 if there was an error querying this information.
351
352
  * -2 if the OS does not support this metric.
352
353
  */
353
354
  double forkRate;
354
355
  /** Speed at which the OS swaps in and swaps out data, in KB/sec.
355
- * NaN if it's not yet known (because too few samples have been taken so far).
356
+ * SpeedMeter<size_t>::unknownSpeed() if it's not yet known
357
+ * (because too few samples have been taken so far).
356
358
  * -1 if there was an error querying this information.
357
359
  * -2 if the OS does not support this metric.
358
360
  */
@@ -537,7 +539,7 @@ public:
537
539
 
538
540
  if (forkRate != -2) {
539
541
  stream << "Fork rate : ";
540
- if (std::isnan(forkRate) || forkRate < 0) {
542
+ if (forkRate == SpeedMeter<unsigned long long>::unknownSpeed() || forkRate < 0) {
541
543
  if (options.colors) {
542
544
  stream << ANSI_COLOR_DGRAY;
543
545
  }
@@ -648,7 +650,7 @@ public:
648
650
 
649
651
  if (swapInRate != -2) {
650
652
  stream << "Swap in : ";
651
- if (std::isnan(swapInRate) || swapInRate < 0) {
653
+ if (swapInRate == SpeedMeter<size_t>::unknownSpeed() || swapInRate < 0) {
652
654
  if (options.colors) {
653
655
  stream << ANSI_COLOR_DGRAY;
654
656
  }
@@ -668,7 +670,7 @@ public:
668
670
 
669
671
  if (swapOutRate != -2) {
670
672
  stream << "Swap out : ";
671
- if (std::isnan(swapOutRate) || swapOutRate < 0) {
673
+ if (swapOutRate == SpeedMeter<size_t>::unknownSpeed() || swapOutRate < 0) {
672
674
  if (options.colors) {
673
675
  stream << ANSI_COLOR_DGRAY;
674
676
  }
@@ -462,10 +462,10 @@ public:
462
462
  UPDATE_TRACE_POINT();
463
463
  unionStationCore = boost::make_shared<UnionStation::Core>(options.loggingAgentAddress,
464
464
  "logging", options.loggingAgentPassword);
465
- spawnerFactory = boost::make_shared<SpawnerFactory>(resourceLocator,
466
- generation, boost::make_shared<SpawnerConfig>(randomGenerator));
467
- pool = boost::make_shared<Pool>(spawnerFactory, unionStationCore,
468
- randomGenerator, &options);
465
+ spawnerFactory = boost::make_shared<SpawnerFactory>(generation,
466
+ boost::make_shared<SpawnerConfig>(resourceLocator, unionStationCore,
467
+ randomGenerator));
468
+ pool = boost::make_shared<Pool>(spawnerFactory, &options);
469
469
  pool->initialize();
470
470
  pool->setMax(options.maxPoolSize);
471
471
  pool->setMaxIdleTime(options.poolIdleTime * 1000000);
@@ -112,6 +112,7 @@
112
112
  #include <boost/shared_ptr.hpp>
113
113
  #include <boost/weak_ptr.hpp>
114
114
  #include <boost/make_shared.hpp>
115
+ #include <boost/regex.hpp>
115
116
  #include <ev++.h>
116
117
 
117
118
  #if defined(__GLIBCXX__) || defined(__APPLE__)
@@ -135,6 +136,7 @@
135
136
  #include <UnionStation/Transaction.h>
136
137
  #include <UnionStation/ScopeLog.h>
137
138
  #include <ApplicationPool2/Pool.h>
139
+ #include <ApplicationPool2/ErrorRenderer.h>
138
140
  #include <Utils/StrIntUtils.h>
139
141
  #include <Utils/IOUtils.h>
140
142
  #include <Utils/HttpHeaderBufferer.h>
@@ -225,6 +227,8 @@ private:
225
227
  sessionCheckoutTry = 0;
226
228
  responseHeaderSeen = false;
227
229
  chunkedResponse = false;
230
+ responseContentLength = -1;
231
+ responseBodyAlreadyRead = 0;
228
232
  appRoot.clear();
229
233
  }
230
234
 
@@ -336,6 +340,21 @@ public:
336
340
 
337
341
  bool responseHeaderSeen;
338
342
  bool chunkedResponse;
343
+ /** The size of the response body, set based on the values of
344
+ * the Content-Length and Transfer-Encoding response headers.
345
+ * Possible values:
346
+ *
347
+ * -1: infinite. Should keep forwarding response body until end of stream.
348
+ * This is the case for WebSockets or for responses without Content-Length.
349
+ * Responses with "Transfer-Encoding: chunked" also fall under this
350
+ * category, though in this case encountering the zero-length chunk is
351
+ * treated the same as end of stream.
352
+ * 0 : no client body. Should immediately close connection after forwarding
353
+ * headers.
354
+ * >0: Should forward exactly this many bytes of the response body.
355
+ */
356
+ long long responseContentLength;
357
+ unsigned long long responseBodyAlreadyRead;
339
358
  HttpHeaderBufferer responseHeaderBufferer;
340
359
  Dechunker responseDechunker;
341
360
 
@@ -586,6 +605,8 @@ public:
586
605
  << indent << "requestIsChunked = " << boolStr(requestIsChunked) << "\n"
587
606
  << indent << "requestBodyLength = " << requestBodyLength << "\n"
588
607
  << indent << "requestBodyAlreadyRead = " << requestBodyAlreadyRead << "\n"
608
+ << indent << "responseContentLength = " << responseContentLength << "\n"
609
+ << indent << "responseBodyAlreadyRead = " << responseBodyAlreadyRead << "\n"
589
610
  << indent << "clientInput = " << clientInput.get() << " " << clientInput->inspect() << "\n"
590
611
  << indent << "clientInput started = " << boolStr(clientInput->isStarted()) << "\n"
591
612
  << indent << "clientBodyBuffer started = " << boolStr(clientBodyBuffer->isStarted()) << "\n"
@@ -629,6 +650,7 @@ private:
629
650
  HashMap<int, ClientPtr> clients;
630
651
  Timer inactivityTimer;
631
652
  bool accept4Available;
653
+ boost::regex upgradeHeaderRegex;
632
654
 
633
655
 
634
656
  void disconnect(const ClientPtr &client) {
@@ -774,47 +796,12 @@ private:
774
796
  assert(client->state < Client::FORWARDING_BODY_TO_APP);
775
797
  client->state = Client::WRITING_SIMPLE_RESPONSE;
776
798
 
777
- string templatesDir = resourceLocator.getResourcesDir() + "/templates";
799
+ ErrorRenderer renderer(resourceLocator);
778
800
  string data;
779
801
 
780
802
  if (friendlyErrorPagesEnabled(client)) {
781
803
  try {
782
- string cssFile = templatesDir + "/error_layout.css";
783
- string errorLayoutFile = templatesDir + "/error_layout.html.template";
784
- string generalErrorFile =
785
- (e != NULL && e->isHTML())
786
- ? templatesDir + "/general_error_with_html.html.template"
787
- : templatesDir + "/general_error.html.template";
788
- string css = readAll(cssFile);
789
- StringMap<StaticString> params;
790
-
791
- params.set("CSS", css);
792
- params.set("APP_ROOT", client->options.appRoot);
793
- params.set("RUBY", client->options.ruby);
794
- params.set("ENVIRONMENT", client->options.environment);
795
- params.set("MESSAGE", message);
796
- params.set("IS_RUBY_APP",
797
- (client->options.appType == "classic-rails" || client->options.appType == "rack")
798
- ? "true" : "false");
799
- if (e != NULL) {
800
- params.set("TITLE", "Web application could not be started");
801
- // Store all SpawnException annotations into 'params',
802
- // but convert its name to uppercase.
803
- const map<string, string> &annotations = e->getAnnotations();
804
- map<string, string>::const_iterator it, end = annotations.end();
805
- for (it = annotations.begin(); it != end; it++) {
806
- string name = it->first;
807
- for (string::size_type i = 0; i < name.size(); i++) {
808
- name[i] = toupper(name[i]);
809
- }
810
- params.set(name, it->second);
811
- }
812
- } else {
813
- params.set("TITLE", "Internal server error");
814
- }
815
- string content = Template::apply(readAll(generalErrorFile), params);
816
- params.set("CONTENT", content);
817
- data = Template::apply(readAll(errorLayoutFile), params);
804
+ data = renderer.renderWithDetails(message, client->options, e);
818
805
  } catch (const SystemException &e2) {
819
806
  P_ERROR("Cannot render an error page: " << e2.what() << "\n" <<
820
807
  e2.backtrace());
@@ -822,13 +809,7 @@ private:
822
809
  }
823
810
  } else {
824
811
  try {
825
- StringMap<StaticString> params;
826
- params.set("PROGRAM_NAME", PROGRAM_NAME);
827
- params.set("NGINX_DOC_URL", NGINX_DOC_URL);
828
- params.set("APACHE2_DOC_URL", APACHE2_DOC_URL);
829
- params.set("STANDALONE_DOC_URL", STANDALONE_DOC_URL);
830
- data = Template::apply(readAll(templatesDir + "/undisclosed_error.html.template"),
831
- params);
812
+ data = renderer.renderWithoutDetails();
832
813
  } catch (const SystemException &e2) {
833
814
  P_ERROR("Cannot render an error page: " << e2.what() << "\n" <<
834
815
  e2.backtrace());
@@ -1121,6 +1102,20 @@ private:
1121
1102
  RH_TRACE(client, 3, "Response with chunked transfer encoding detected.");
1122
1103
  client->chunkedResponse = true;
1123
1104
  removeHeader(headerData, transferEncoding);
1105
+ } else {
1106
+ Header contentLength = lookupHeader(headerData, "Content-Length", "content-length");
1107
+ if (!contentLength.empty()) {
1108
+ client->responseContentLength = stringToLL(contentLength.value);
1109
+ }
1110
+ }
1111
+
1112
+ Header connection = lookupHeader(headerData, "Connection", "connection");
1113
+ if (!connection.empty() && (connection.value == "keep-alive"
1114
+ || connection.value == "Keep-Alive"))
1115
+ {
1116
+ RH_TRACE(client, 3, "Keep-alive response detected. Changing to non-keep alive.");
1117
+ removeHeader(headerData, connection);
1118
+ headerData.append("Connection: close\r\n");
1124
1119
  }
1125
1120
 
1126
1121
  // Add X-Powered-By.
@@ -1132,6 +1127,11 @@ private:
1132
1127
 
1133
1128
  // Add sticky session ID.
1134
1129
  if (client->stickySession && client->session != NULL) {
1130
+ StaticString baseURI = client->scgiParser.getHeader("SCRIPT_NAME");
1131
+ if (baseURI.empty()) {
1132
+ baseURI = StaticString("/", 1);
1133
+ }
1134
+
1135
1135
  StaticString cookieName = getStickySessionCookieName(client);
1136
1136
  // Note that we do NOT set HttpOnly. If we set that flag then Chrome
1137
1137
  // doesn't send cookies over WebSocket handshakes. Confirmed on Chrome 25.
@@ -1139,6 +1139,8 @@ private:
1139
1139
  headerData.append(cookieName.data(), cookieName.size());
1140
1140
  headerData.append("=");
1141
1141
  headerData.append(toString(client->session->getStickySessionId()));
1142
+ headerData.append("; Path=");
1143
+ headerData.append(baseURI.data(), baseURI.size());
1142
1144
  headerData.append("\r\n");
1143
1145
 
1144
1146
  // Invalidate all cookies with a different route.
@@ -1160,7 +1162,9 @@ private:
1160
1162
  headerData.append(cookie.first.data(), cookie.first.size());
1161
1163
  headerData.append("=");
1162
1164
  headerData.append(cookie.second.data(), cookie.second.size());
1163
- headerData.append("; path=/; expires=Thu, 01 Jan 1970 00:00:00 GMT\r\n");
1165
+ headerData.append("; Path=");
1166
+ headerData.append(baseURI.data(), baseURI.size());
1167
+ headerData.append("; Expires=Thu, 01 Jan 1970 00:00:00 GMT\r\n");
1164
1168
  }
1165
1169
  }
1166
1170
  }
@@ -1191,6 +1195,7 @@ private:
1191
1195
  removeHeader(headerData, oobw);
1192
1196
  }
1193
1197
 
1198
+ P_TRACE(2, "Fowarding response header from app client: " << headerData);
1194
1199
  headerData.append("\r\n");
1195
1200
  writeToClientOutputPipe(client, headerData);
1196
1201
  return true;
@@ -1234,6 +1239,10 @@ private:
1234
1239
  client->responseHeaderSeen = true;
1235
1240
  StaticString header = client->responseHeaderBufferer.getData();
1236
1241
  if (processResponseHeader(client, header)) {
1242
+ if (client->responseContentLength == 0) {
1243
+ RH_TRACE(client, 3, "Disconnecting client because response Content-Length = 0");
1244
+ onAppInputEof(client);
1245
+ }
1237
1246
  return consumed;
1238
1247
  } else {
1239
1248
  assert(!client->connected());
@@ -1258,7 +1267,40 @@ private:
1258
1267
 
1259
1268
  void onAppInputChunk(const ClientPtr &client, const StaticString &data) {
1260
1269
  RH_LOG_EVENT(client, "onAppInputChunk");
1261
- writeToClientOutputPipe(client, data);
1270
+ StaticString data2;
1271
+
1272
+ if (client->responseContentLength == -1) {
1273
+ data2 = data;
1274
+ } else {
1275
+ size_t rest = client->responseContentLength -
1276
+ client->responseBodyAlreadyRead;
1277
+ data2 = StaticString(data.data(), std::min<size_t>(
1278
+ rest, data.size()));
1279
+ }
1280
+
1281
+ client->responseBodyAlreadyRead += data2.size();
1282
+ assert(client->responseContentLength == -1 || client->responseBodyAlreadyRead <=
1283
+ (unsigned long long) client->responseContentLength);
1284
+ if (data2.empty()) {
1285
+ // Client sent more data than was advertised through
1286
+ // Content-Length. Ignore them.
1287
+ return;
1288
+ }
1289
+
1290
+ writeToClientOutputPipe(client, data2);
1291
+
1292
+ if (client->responseContentLength > 0) {
1293
+ RH_TRACE(client, 3, client->responseBodyAlreadyRead << "/" <<
1294
+ client->responseContentLength <<
1295
+ " bytes of application data forwarded so far.");
1296
+
1297
+ if (client->connected() && (unsigned long long) client->responseContentLength
1298
+ == client->responseBodyAlreadyRead)
1299
+ {
1300
+ RH_TRACE(client, 3, "Disconnecting client because application data has been fully forwarded.");
1301
+ onAppInputEof(client);
1302
+ }
1303
+ }
1262
1304
  }
1263
1305
 
1264
1306
  void onAppInputChunkEnd(const ClientPtr &client) {
@@ -1270,11 +1312,15 @@ private:
1270
1312
  RH_LOG_EVENT(client, "onAppInputEof");
1271
1313
  // Check for session == NULL in order to avoid executing the code twice on
1272
1314
  // responses with chunked encoding.
1315
+ // This also ensures that when onAppInputEof() is called twice (e.g. because
1316
+ // additional data was received after onAppInputChunk has already called onAppInputEof()),
1317
+ // we don't do things twice.
1273
1318
  if (!client->connected() || client->session == NULL) {
1274
1319
  return;
1275
1320
  }
1276
1321
 
1277
1322
  RH_DEBUG(client, "Application sent EOF");
1323
+ client->appInput->stop();
1278
1324
  client->session.reset();
1279
1325
  client->endScopeLog(&client->scopeLogs.requestProxying);
1280
1326
  client->clientOutputPipe->end();
@@ -2254,14 +2300,10 @@ private:
2254
2300
  }
2255
2301
 
2256
2302
  void writeSpawnExceptionErrorResponse(const ClientPtr &client, const boost::shared_ptr<SpawnException> &e) {
2257
- if (strip(e->getErrorPage()).empty()) {
2258
- RH_WARN(client, "Cannot checkout session. " << e->what());
2259
- writeErrorResponse(client, e->what());
2260
- } else {
2261
- RH_WARN(client, "Cannot checkout session.\nError page:\n" <<
2262
- e->getErrorPage());
2263
- writeErrorResponse(client, e->getErrorPage(), e.get());
2264
- }
2303
+ RH_ERROR(client, "Cannot checkout session because a spawning error occurred. " <<
2304
+ "The identifier of the error is " << e->get("ERROR_ID") << ". Please see earlier logs for " <<
2305
+ "details about the error.");
2306
+ writeErrorResponse(client, e->getErrorPage(), e.get());
2265
2307
  }
2266
2308
 
2267
2309
  void writeOtherExceptionErrorResponse(const ClientPtr &client, const ExceptionPtr &e) {
@@ -2418,7 +2460,9 @@ private:
2418
2460
  }
2419
2461
 
2420
2462
  StaticString connection = parser.getHeader("HTTP_CONNECTION");
2421
- if (connection == "upgrade" || connection == "Upgrade") {
2463
+ if (regex_match(connection.data(), connection.data() + connection.size(),
2464
+ upgradeHeaderRegex))
2465
+ {
2422
2466
  data.append("Connection: ");
2423
2467
  data.append(connection.data(), connection.size());
2424
2468
  data.append("\r\n");
@@ -2440,12 +2484,25 @@ private:
2440
2484
  data.append("\r\n");
2441
2485
  }
2442
2486
 
2487
+ header = parser.getHeader("HTTPS");
2488
+ if (!header.empty()) {
2489
+ data.append("X-Forwarded-Proto: https\r\n");
2490
+ }
2491
+
2492
+ header = parser.getHeader("REMOTE_ADDR");
2493
+ if (!header.empty()) {
2494
+ data.append("X-Forwarded-For: ");
2495
+ data.append(header);
2496
+ data.append("\r\n");
2497
+ }
2498
+
2443
2499
  if (client->options.analytics) {
2444
2500
  data.append("Passenger-Txn-Id: ");
2445
2501
  data.append(client->options.transaction->getTxnId());
2446
2502
  data.append("\r\n");
2447
2503
  }
2448
2504
 
2505
+ P_TRACE(3, "Sending headers to application: " << data);
2449
2506
  data.append("\r\n");
2450
2507
 
2451
2508
  StaticString datas[] = { data };
@@ -2650,11 +2707,13 @@ public:
2650
2707
  pool(_pool),
2651
2708
  options(_options),
2652
2709
  resourceLocator(_options.passengerRoot),
2710
+ upgradeHeaderRegex("(keep-alive, *)?upgrade(, *keep-alive)?",
2711
+ boost::regex::perl | boost::regex::icase),
2653
2712
  benchmarkPoint(getDefaultBenchmarkPoint())
2654
2713
  {
2655
2714
  accept4Available = true;
2656
2715
  connectPasswordTimeout = 15000;
2657
- unionStationCore = pool->unionStationCore;
2716
+ unionStationCore = pool->getUnionStationCore();
2658
2717
 
2659
2718
  requestSocketWatcher.set(_requestSocket, ev::READ);
2660
2719
  requestSocketWatcher.set(_libev->getLoop());