passenger 4.0.44 → 4.0.45

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 (110) hide show
  1. checksums.yaml +8 -8
  2. checksums.yaml.gz.asc +7 -7
  3. data.tar.gz.asc +7 -7
  4. data/.travis.yml +3 -0
  5. data/CHANGELOG +31 -0
  6. data/CONTRIBUTING.md +70 -10
  7. data/CONTRIBUTORS +4 -0
  8. data/README.md +1 -1
  9. data/Vagrantfile +50 -0
  10. data/bin/passenger-install-nginx-module +7 -2
  11. data/build/basics.rb +4 -1
  12. data/build/documentation.rb +6 -0
  13. data/build/node_tests.rb +7 -1
  14. data/build/packaging.rb +5 -0
  15. data/build/test_basics.rb +3 -3
  16. data/debian.template/copyright +1 -1
  17. data/debian.template/passenger.manpages +0 -1
  18. data/dev/rack.test/config.ru +5 -0
  19. data/dev/rack.test/public/asset.txt +1 -0
  20. data/dev/vagrant/apache_default_site.conf +35 -0
  21. data/dev/vagrant/apache_passenger.conf +5 -0
  22. data/dev/vagrant/apache_passenger.load +1 -0
  23. data/dev/vagrant/apache_ports.conf +24 -0
  24. data/dev/vagrant/apache_rack_test.conf +9 -0
  25. data/dev/vagrant/bashrc +21 -0
  26. data/dev/vagrant/nginx.conf +39 -0
  27. data/dev/vagrant/nginx_rakefile +34 -0
  28. data/dev/vagrant/nginx_start +32 -0
  29. data/dev/vagrant/provision.sh +115 -0
  30. data/dev/vagrant/sudoers.conf +5 -0
  31. data/doc/Design and Architecture.txt +515 -0
  32. data/doc/DeveloperQuickstart.md +70 -0
  33. data/doc/Users guide Apache.idmap.txt +24 -18
  34. data/doc/Users guide Apache.txt +200 -62
  35. data/doc/Users guide Nginx.idmap.txt +53 -45
  36. data/doc/Users guide Nginx.txt +501 -360
  37. data/doc/Users guide Standalone.txt +8 -0
  38. data/doc/images/direct_spawning.png +0 -0
  39. data/doc/images/direct_spawning.svg +16 -13
  40. data/doc/images/helper_agent_core_architecture.png +0 -0
  41. data/doc/images/passenger_architecture_overview.png +0 -0
  42. data/doc/images/smart_spawning.png +0 -0
  43. data/doc/images/{smart.svg → smart_spawning.svg} +23 -20
  44. data/doc/images/spawning_preparation_work.png +0 -0
  45. data/doc/images/startup_sequence.png +0 -0
  46. data/doc/users_guide_snippets/appendix_c_spawning_methods.txt +82 -121
  47. data/doc/users_guide_snippets/environment_variables.txt +1 -1
  48. data/doc/users_guide_snippets/support_information.txt +2 -0
  49. data/doc/users_guide_snippets/tips.txt +117 -9
  50. data/ext/apache2/Configuration.hpp +4 -2
  51. data/ext/apache2/ConfigurationCommands.cpp +14 -0
  52. data/ext/apache2/ConfigurationFields.hpp +4 -0
  53. data/ext/apache2/ConfigurationSetters.cpp +22 -0
  54. data/ext/apache2/CreateDirConfig.cpp +2 -0
  55. data/ext/apache2/Hooks.cpp +30 -14
  56. data/ext/apache2/MergeDirConfig.cpp +14 -0
  57. data/ext/apache2/SetHeaders.cpp +8 -0
  58. data/ext/common/ApplicationPool2/AppTypes.cpp +6 -1
  59. data/ext/common/ApplicationPool2/Implementation.cpp +1 -1
  60. data/ext/common/ApplicationPool2/Session.h +1 -1
  61. data/ext/common/Constants.h +9 -7
  62. data/ext/common/Utils/HttpHeaderBufferer.h +23 -4
  63. data/ext/common/Utils/StrIntUtils.h +35 -0
  64. data/ext/common/Utils/StringScanning.h +4 -10
  65. data/ext/common/agents/HelperAgent/RequestHandler.h +90 -49
  66. data/ext/nginx/CacheLocationConfig.c +40 -0
  67. data/ext/nginx/ConfigurationCommands.c +20 -0
  68. data/ext/nginx/ConfigurationFields.h +4 -0
  69. data/ext/nginx/ContentHandler.c +1 -1
  70. data/ext/nginx/CreateLocationConfig.c +9 -0
  71. data/ext/nginx/MergeLocationConfig.c +12 -0
  72. data/ext/nginx/config +2 -2
  73. data/ext/nginx/ngx_http_passenger_module.c +4 -4
  74. data/helper-scripts/node-loader.js +40 -27
  75. data/lib/phusion_passenger.rb +1 -1
  76. data/lib/phusion_passenger/apache2/config_options.rb +14 -2
  77. data/lib/phusion_passenger/constants.rb +7 -6
  78. data/lib/phusion_passenger/loader_shared_helpers.rb +11 -1
  79. data/lib/phusion_passenger/nginx/config_options.rb +8 -0
  80. data/lib/phusion_passenger/packaging.rb +8 -3
  81. data/lib/phusion_passenger/platform_info/apache.rb +3 -0
  82. data/lib/phusion_passenger/platform_info/ruby.rb +4 -1
  83. data/lib/phusion_passenger/standalone/command.rb +0 -1
  84. data/lib/phusion_passenger/standalone/package_runtime_command.rb +1 -0
  85. data/lib/phusion_passenger/standalone/start_command.rb +80 -62
  86. data/lib/phusion_passenger/standalone/status_command.rb +1 -0
  87. data/lib/phusion_passenger/standalone/stop_command.rb +1 -0
  88. data/man/passenger-config.1 +1 -1
  89. data/man/passenger-memory-stats.8 +1 -1
  90. data/man/passenger-status.8 +1 -1
  91. data/npm-shrinkwrap.json +229 -0
  92. data/package.json +28 -0
  93. data/resources/templates/standalone/config.erb +2 -0
  94. data/rpm/Vagrantfile +0 -3
  95. data/test/config.json.vagrant +30 -0
  96. data/test/cxx/HttpHeaderBuffererTest.cpp +64 -10
  97. data/test/cxx/RequestHandlerTest.cpp +35 -13
  98. data/test/integration_tests/apache2_tests.rb +1 -0
  99. data/test/stub/node/app.js +26 -18
  100. metadata +28 -13
  101. metadata.gz.asc +7 -7
  102. data/doc/Architectural overview.idmap.txt +0 -36
  103. data/doc/Architectural overview.txt +0 -410
  104. data/doc/images/smart.png +0 -0
  105. data/ext/common/ApplicationPool2/README.md +0 -56
  106. data/man/passenger-stress-test.1 +0 -43
  107. data/node_lib/phusion_passenger/httplib_emulation.js +0 -215
  108. data/node_lib/phusion_passenger/request_handler.js +0 -73
  109. data/node_lib/phusion_passenger/session_protocol_parser.js +0 -113
  110. data/test/node/httplib_emulation_spec.js +0 -623
@@ -30,7 +30,12 @@
30
30
  namespace Passenger {
31
31
  namespace ApplicationPool2 {
32
32
 
33
- // Don't forget to update ApplicationPool2::Options::getStartCommand() too.
33
+ /* If you update this structure, also update the following:
34
+ * - ApplicationPool2::Options::getStartCommand()
35
+ * - lib/phusion_passenger/standalone/app_finder.rb
36
+ * - The documentation for `PassengerAppEnv` (Apache) and `passenger_app_env` (Nginx)
37
+ * - The Developer Guide, section "Executing the loader or preloader"
38
+ */
34
39
  const AppTypeDefinition appTypeDefinitions[] = {
35
40
  { PAT_RACK, "rack", "config.ru", "Passenger RackApp" },
36
41
  { PAT_WSGI, "wsgi", "passenger_wsgi.py", "Passenger WsgiApp" },
@@ -1290,7 +1290,7 @@ Session::getGupid() const {
1290
1290
  return getProcess()->gupid;
1291
1291
  }
1292
1292
 
1293
- int
1293
+ unsigned int
1294
1294
  Session::getStickySessionId() const {
1295
1295
  return getProcess()->stickySessionId;
1296
1296
  }
@@ -108,7 +108,7 @@ public:
108
108
  const string &getConnectPassword() const;
109
109
  pid_t getPid() const;
110
110
  const string &getGupid() const;
111
- int getStickySessionId() const;
111
+ unsigned int getStickySessionId() const;
112
112
  const GroupPtr getGroup() const;
113
113
  void requestOOBW();
114
114
  int kill(int signo);
@@ -36,7 +36,7 @@
36
36
  #define DEFAULT_BACKEND_ACCOUNT_RIGHTS Account::DETACH
37
37
 
38
38
 
39
- #define APACHE2_DOC_URL "http://www.modrails.com/documentation/Users%20guide%20Apache.html"
39
+ #define APACHE2_DOC_URL "https://www.phusionpassenger.com/documentation/Users%20guide%20Apache.html"
40
40
 
41
41
  #define DEB_APACHE_MODULE_PACKAGE "libapache2-mod-passenger"
42
42
 
@@ -68,6 +68,8 @@
68
68
 
69
69
  #define DEFAULT_START_TIMEOUT 90000
70
70
 
71
+ #define DEFAULT_STICKY_SESSIONS_COOKIE_NAME "_passenger_route"
72
+
71
73
  #define DEFAULT_THREAD_COUNT 1
72
74
 
73
75
  #define DEFAULT_UNION_STATION_GATEWAY_ADDRESS "gateway.unionstationapp.com"
@@ -76,19 +78,19 @@
76
78
 
77
79
  #define DEFAULT_WEB_APP_USER "nobody"
78
80
 
79
- #define ENTERPRISE_URL "http://www.phusionpassenger.com/enterprise"
81
+ #define ENTERPRISE_URL "https://www.phusionpassenger.com/enterprise"
80
82
 
81
83
  #define FEEDBACK_FD 3
82
84
 
83
- #define INDEX_DOC_URL "http://www.modrails.com/documentation/Users%20guide.html"
85
+ #define INDEX_DOC_URL "https://www.phusionpassenger.com/documentation/Users%20guide.html"
84
86
 
85
87
  #define MESSAGE_SERVER_MAX_PASSWORD_SIZE 100
86
88
 
87
89
  #define MESSAGE_SERVER_MAX_USERNAME_SIZE 100
88
90
 
89
- #define NGINX_DOC_URL "http://www.modrails.com/documentation/Users%20guide%20Nginx.html"
91
+ #define NGINX_DOC_URL "https://www.phusionpassenger.com/documentation/Users%20guide%20Nginx.html"
90
92
 
91
- #define PASSENGER_VERSION "4.0.44"
93
+ #define PASSENGER_VERSION "4.0.45"
92
94
 
93
95
  #define POOL_HELPER_THREAD_STACK_SIZE 262144
94
96
 
@@ -114,11 +116,11 @@
114
116
 
115
117
  #define SERVER_INSTANCE_DIR_STRUCTURE_MINOR_VERSION 0
116
118
 
117
- #define STANDALONE_DOC_URL "http://www.modrails.com/documentation/Users%20guide%20Standalone.html"
119
+ #define STANDALONE_DOC_URL "https://www.phusionpassenger.com/documentation/Users%20guide%20Standalone.html"
118
120
 
119
121
  #define STANDALONE_NGINX_CONFIGURE_OPTIONS "--with-cc-opt='-Wno-error' --without-http_fastcgi_module --without-http_scgi_module --without-http_uwsgi_module --with-http_gzip_static_module --with-http_stub_status_module --with-http_ssl_module"
120
122
 
121
- #define SUPPORT_URL "http://www.phusionpassenger.com/support"
123
+ #define SUPPORT_URL "https://www.phusionpassenger.com/documentation_and_support"
122
124
 
123
125
 
124
126
  #endif /* _PASSENGER_CONSTANTS_H */
@@ -28,6 +28,7 @@
28
28
  #include <string>
29
29
  #include <algorithm>
30
30
  #include <cstddef>
31
+ #include <cstring>
31
32
  #include <cassert>
32
33
  #include <StaticString.h>
33
34
  #include <Utils/StreamBoyerMooreHorspool.h>
@@ -41,7 +42,8 @@ using namespace std;
41
42
  *
42
43
  * Feed data until acceptingInput() is false. The entire HTTP header
43
44
  * will become available through getData(). Non-HTTP header data is
44
- * not consumed and will not be included in getData().
45
+ * not consumed and will not be included in getData(). 100-Continue
46
+ * messages are ignored.
45
47
  *
46
48
  * This class has zero-copy support. If the first feed already contains
47
49
  * an entire HTTP header getData() will point to the fed data. Otherwise
@@ -77,6 +79,13 @@ private:
77
79
  char padding[SBMH_SIZE(4)];
78
80
  } u;
79
81
 
82
+ bool is100Continue(const StaticString &buffer) const {
83
+ return buffer.size() >= sizeof("HTTP/1.1 100 Continue\r\n") - 1
84
+ && memcmp(buffer.data(), "HTTP/1.", sizeof("HTTP/1.") - 1) == 0
85
+ && memcmp(buffer.data() + sizeof("HTTP/1.1 ") - 1,
86
+ "100 Continue\r\n", sizeof("100 Continue\r\n") - 1) == 0;
87
+ }
88
+
80
89
  public:
81
90
  HttpHeaderBufferer() {
82
91
  sbmh_init(&u.terminatorFinder,
@@ -114,8 +123,13 @@ public:
114
123
  (const unsigned char *) data,
115
124
  feedSize);
116
125
  if (u.terminatorFinder.found) {
117
- state = DONE;
118
- this->data = StaticString(data, accepted);
126
+ if (is100Continue(StaticString(data, accepted))) {
127
+ reset();
128
+ accepted += feed(data + accepted, size - accepted);
129
+ } else {
130
+ state = DONE;
131
+ this->data = StaticString(data, accepted);
132
+ }
119
133
  } else if (feedSize == max) {
120
134
  state = ERROR;
121
135
  this->data = StaticString(data, accepted);
@@ -135,7 +149,12 @@ public:
135
149
  buffer.append(data, accepted);
136
150
  this->data = buffer;
137
151
  if (u.terminatorFinder.found) {
138
- state = DONE;
152
+ if (is100Continue(buffer)) {
153
+ reset();
154
+ accepted += feed(data + accepted, size - accepted);
155
+ } else {
156
+ state = DONE;
157
+ }
139
158
  } else if (buffer.size() == (size_t) max) {
140
159
  state = ERROR;
141
160
  }
@@ -144,6 +144,41 @@ string replaceAll(const string &str, const string &toFind, const string &replace
144
144
  */
145
145
  string strip(const StaticString &str);
146
146
 
147
+ /**
148
+ * Given a pointer to a NULL-terminated string, update it to a
149
+ * position where all leading whitespaces (0x20) have been skipped.
150
+ */
151
+ inline void
152
+ skipLeadingWhitespaces(const char **data) {
153
+ while (**data == ' ') {
154
+ (*data)++;
155
+ }
156
+ }
157
+
158
+ /**
159
+ * Given a pointer to a string and its end, update the begin pointer to a
160
+ * position where all leading whitespaces (0x20) have been skipped.
161
+ * The pointer will not be moved past `end`.
162
+ */
163
+ inline void
164
+ skipLeadingWhitespaces(const char **data, const char *end) {
165
+ while (*data < end && **data == ' ') {
166
+ (*data)++;
167
+ }
168
+ }
169
+
170
+ /**
171
+ * Given a string and a pointer to its position within it, update the pointer
172
+ * to a position where all trailing whitespaces (0x20) have been skipped.
173
+ * The pointer will not be moved before `begin`.
174
+ */
175
+ inline void
176
+ skipTrailingWhitespaces(const char *begin, const char **pos) {
177
+ while (*pos > begin && (*pos)[-1] == ' ') {
178
+ (*pos)--;
179
+ }
180
+ }
181
+
147
182
  /**
148
183
  * Convert anything to a string.
149
184
  */
@@ -29,6 +29,7 @@
29
29
  #include <cstdlib>
30
30
  #include <string>
31
31
  #include <StaticString.h>
32
+ #include <Utils/StrIntUtils.h>
32
33
 
33
34
 
34
35
  /**
@@ -58,13 +59,6 @@ using namespace std;
58
59
 
59
60
  struct ParseException {};
60
61
 
61
- inline void
62
- _skipLeadingWhitespaces(const char **data) {
63
- while (**data == ' ') {
64
- (*data)++;
65
- }
66
- }
67
-
68
62
  /**
69
63
  * Scan the given data for the first word that appears on the first line.
70
64
  * Leading whitespaces (but not newlines) are ignored. If a word is found
@@ -78,7 +72,7 @@ _skipLeadingWhitespaces(const char **data) {
78
72
  */
79
73
  inline StaticString
80
74
  readNextWord(const char **data) {
81
- _skipLeadingWhitespaces(data);
75
+ skipLeadingWhitespaces(data);
82
76
  if (**data == '\n' || **data == '\0') {
83
77
  throw ParseException();
84
78
  }
@@ -201,7 +195,7 @@ readNextWordAsDouble(const char **data) {
201
195
  */
202
196
  inline string
203
197
  readRestOfLine(const char *data) {
204
- _skipLeadingWhitespaces(&data);
198
+ skipLeadingWhitespaces(&data);
205
199
  // Rest of line is allowed to be empty.
206
200
  if (*data == '\n' || *data == '\0') {
207
201
  return "";
@@ -251,7 +245,7 @@ skipToNextLine(const char **data) {
251
245
  */
252
246
  inline StaticString
253
247
  readNextSentence(const char **data, char terminator) {
254
- _skipLeadingWhitespaces(data);
248
+ skipLeadingWhitespaces(data);
255
249
  if (**data == '\n' || **data == '\0' || **data == terminator) {
256
250
  throw ParseException();
257
251
  }
@@ -85,24 +85,22 @@
85
85
  present? header present? protocol
86
86
  ---------------------------------------------------------------------------------------------
87
87
 
88
- GET/HEAD - Y - Reject request[1]
88
+ GET/HEAD Y Y - Reject request[1]
89
89
  Other Y - - Reject request[2]
90
90
 
91
- - N N http_session Set requestBodyLength=0, keep socket open when done forwarding.
92
91
  GET/HEAD Y N http_session Set requestBodyLength=-1, keep socket open when done forwarding.
93
- Other N Y http_session Keep socket open when done forwarding. If Transfer-Encoding is
92
+ - N N http_session Set requestBodyLength=0, keep socket open when done forwarding.
93
+ - N Y http_session Keep socket open when done forwarding. If Transfer-Encoding is
94
94
  chunked, rechunck the body during forwarding.
95
95
 
96
- - N N session Set requestBodyLength=0, half-close app socket when done forwarding.
97
96
  GET/HEAD Y N session Set requestBodyLength=-1, half-close app socket when done forwarding.
98
- Other N Y session Half-close app socket when done forwarding.
97
+ - N N session Set requestBodyLength=0, half-close app socket when done forwarding.
98
+ - N Y session Half-close app socket when done forwarding.
99
99
  ---------------------------------------------------------------------------------------------
100
100
 
101
101
  [1] Supporting situations in which there is both an HTTP request body and WebSocket data
102
- is way too complicated. The RequestHandler code is complicated enough as it is.
103
- Furthermore, GET requests with a body, although legal, are almost nonexistent and
104
- support by other servers are shaky at best. For these reasons, we don't bother
105
- supporting GET requests with body at all.
102
+ is way too complicated. The RequestHandler code is complicated enough as it is,
103
+ so we choose not to support requests like these.
106
104
  [2] RFC 6455 states that WebSocket upgrades may only happen over GET requests.
107
105
  We don't bother supporting non-WebSocket upgrades.
108
106
 
@@ -1135,11 +1133,37 @@ private:
1135
1133
  // Add sticky session ID.
1136
1134
  if (client->stickySession && client->session != NULL) {
1137
1135
  StaticString cookieName = getStickySessionCookieName(client);
1136
+ // Note that we do NOT set HttpOnly. If we set that flag then Chrome
1137
+ // doesn't send cookies over WebSocket handshakes. Confirmed on Chrome 25.
1138
1138
  headerData.append("Set-Cookie: ");
1139
1139
  headerData.append(cookieName.data(), cookieName.size());
1140
1140
  headerData.append("=");
1141
1141
  headerData.append(toString(client->session->getStickySessionId()));
1142
- headerData.append("; HttpOnly\r\n");
1142
+ headerData.append("\r\n");
1143
+
1144
+ // Invalidate all cookies with a different route.
1145
+ //
1146
+ // TODO: This is not entirely correct. Clients MAY send multiple Cookie
1147
+ // headers, although this is in practice extremely rare.
1148
+ // http://stackoverflow.com/questions/16305814/are-multiple-cookie-headers-allowed-in-an-http-request
1149
+ StaticString cookieHeader = client->scgiParser.getHeader("HTTP_COOKIE");
1150
+ vector< pair<StaticString, StaticString> > cookies;
1151
+ pair<StaticString, StaticString> cookie;
1152
+
1153
+ parseCookieHeader(cookieHeader, cookies);
1154
+
1155
+ foreach (cookie, cookies) {
1156
+ if (cookie.first == cookieName) {
1157
+ unsigned int stickySessionId = stringToUint(cookie.second);
1158
+ if (stickySessionId != client->session->getStickySessionId()) {
1159
+ headerData.append("Set-Cookie: ");
1160
+ headerData.append(cookie.first.data(), cookie.first.size());
1161
+ headerData.append("=");
1162
+ headerData.append(cookie.second.data(), cookie.second.size());
1163
+ headerData.append("; path=/; expires=Thu, 01 Jan 1970 00:00:00 GMT\r\n");
1164
+ }
1165
+ }
1166
+ }
1143
1167
  }
1144
1168
 
1145
1169
  // Add Date header. https://code.google.com/p/phusion-passenger/issues/detail?id=485
@@ -1206,6 +1230,7 @@ private:
1206
1230
  disconnectWithError(client, "application response format error (invalid header)");
1207
1231
  } else {
1208
1232
  // Now that we have a full header, do something with it.
1233
+ RH_TRACE(client, 3, "Response header fully buffered");
1209
1234
  client->responseHeaderSeen = true;
1210
1235
  StaticString header = client->responseHeaderBufferer.getData();
1211
1236
  if (processResponseHeader(client, header)) {
@@ -1776,18 +1801,10 @@ private:
1776
1801
  const bool requestIsGetOrHead = requestMethod == "GET" || requestMethod == "HEAD";
1777
1802
  const bool requestBodyOffered = contentLength != -1 || !transferEncoding.empty();
1778
1803
 
1779
- // Reject requests that have a request body even though it's not allowed,
1780
- // and requests that have an Upgrade header even though it's not allowed.
1781
- if (requestIsGetOrHead) {
1782
- if (requestBodyOffered) {
1783
- reportBadRequestAndDisconnect(client, "Bad request (GET and HEAD requests may not contain a request body)");
1784
- return;
1785
- }
1786
- } else {
1787
- if (!upgrade.empty()) {
1788
- reportBadRequestAndDisconnect(client, "Bad request (Upgrade header is only allowed for non-GET and non-HEAD requests)");
1789
- return;
1790
- }
1804
+ // Reject requests that have a request body and an Upgrade header.
1805
+ if (!requestIsGetOrHead && !upgrade.empty()) {
1806
+ reportBadRequestAndDisconnect(client, "Bad request (Upgrade header is only allowed for non-GET and non-HEAD requests)");
1807
+ return;
1791
1808
  }
1792
1809
 
1793
1810
  if (!requestBodyOffered) {
@@ -1960,48 +1977,72 @@ private:
1960
1977
  }
1961
1978
  }
1962
1979
 
1980
+ void parseCookieHeader(const StaticString &header,
1981
+ vector< pair<StaticString, StaticString> > &cookies) const
1982
+ {
1983
+ // See http://stackoverflow.com/questions/6108207/definite-guide-to-valid-cookie-values
1984
+ // for syntax grammar.
1985
+ vector<StaticString> parts;
1986
+ vector<StaticString>::const_iterator it, it_end;
1987
+
1988
+ split(header, ';', parts);
1989
+ cookies.reserve(parts.size());
1990
+ it_end = parts.end();
1991
+
1992
+ for (it = parts.begin(); it != it_end; it++) {
1993
+ const char *begin = it->data();
1994
+ const char *end = it->data() + it->size();
1995
+ const char *sep;
1996
+
1997
+ skipLeadingWhitespaces(&begin, end);
1998
+ skipTrailingWhitespaces(begin, &end);
1999
+
2000
+ // Find the separator ('=').
2001
+ sep = (const char *) memchr(begin, '=', end - begin);
2002
+ if (sep != NULL) {
2003
+ // Valid cookie. Otherwise, ignore it.
2004
+ const char *nameEnd = sep;
2005
+ const char *valueBegin = sep + 1;
2006
+
2007
+ skipTrailingWhitespaces(begin, &nameEnd);
2008
+ skipLeadingWhitespaces(&valueBegin, end);
2009
+
2010
+ cookies.push_back(make_pair(
2011
+ StaticString(begin, nameEnd - begin),
2012
+ StaticString(valueBegin, end - valueBegin)
2013
+ ));
2014
+ }
2015
+ }
2016
+ }
2017
+
1963
2018
  void setStickySessionId(const ClientPtr &client) {
1964
2019
  ScgiRequestParser &parser = client->scgiParser;
1965
- if (parser.getHeader("PASSENGER_STICKY_SESSION") == "true") {
2020
+ if (parser.getHeader("PASSENGER_STICKY_SESSIONS") == "true") {
1966
2021
  // TODO: This is not entirely correct. Clients MAY send multiple Cookie
1967
2022
  // headers, although this is in practice extremely rare.
1968
2023
  // http://stackoverflow.com/questions/16305814/are-multiple-cookie-headers-allowed-in-an-http-request
1969
- StaticString cookie = parser.getHeader("HTTP_COOKIE");
2024
+ StaticString cookieHeader = parser.getHeader("HTTP_COOKIE");
1970
2025
  StaticString cookieName = getStickySessionCookieName(client);
1971
- vector<StaticString> parts;
2026
+ vector< pair<StaticString, StaticString> > cookies;
2027
+ pair<StaticString, StaticString> cookie;
1972
2028
 
1973
2029
  client->stickySession = true;
1974
- split(cookie, ';', parts);
1975
- foreach (StaticString part, parts) {
1976
- const char *begin = part.data();
1977
- const char *end = part.data() + part.size();
1978
- const char *sep;
1979
-
1980
- // Skip leading whitespace in the name.
1981
- while (begin < end && *begin == ' ') {
1982
- begin++;
1983
- }
1984
- part = StaticString(begin, end - begin);
1985
-
1986
- // Find the separator ('=').
1987
- sep = (const char *) memchr(begin, '=', end - begin);
1988
- if (sep != NULL) {
1989
- StaticString name(begin, sep - begin);
1990
- if (name == cookieName) {
1991
- // This cookie matches the one we're looking for.
1992
- StaticString value(sep + 1, end - (sep + 1));
1993
- client->options.stickySessionId = stringToUint(value);
1994
- return;
1995
- }
2030
+ parseCookieHeader(cookieHeader, cookies);
2031
+ foreach (cookie, cookies) {
2032
+ if (cookie.first == cookieName) {
2033
+ // This cookie matches the one we're looking for.
2034
+ client->options.stickySessionId = stringToUint(cookie.second);
2035
+ return;
1996
2036
  }
1997
2037
  }
1998
2038
  }
1999
2039
  }
2000
2040
 
2001
2041
  StaticString getStickySessionCookieName(const ClientPtr &client) const {
2002
- StaticString value = client->scgiParser.getHeader("PASSENGER_STICKY_SESSION_COOKIE_NAME");
2042
+ StaticString value = client->scgiParser.getHeader("PASSENGER_STICKY_SESSIONS_COOKIE_NAME");
2003
2043
  if (value.empty()) {
2004
- return StaticString("_passenger_route", sizeof("_passenger_route") - 1);
2044
+ return StaticString(DEFAULT_STICKY_SESSIONS_COOKIE_NAME,
2045
+ sizeof(DEFAULT_STICKY_SESSIONS_COOKIE_NAME) - 1);
2005
2046
  } else {
2006
2047
  return value;
2007
2048
  }
@@ -246,6 +246,20 @@ u_char int_buf[32], *end, *buf, *pos;
246
246
  }
247
247
 
248
248
 
249
+
250
+ if (conf->sticky_sessions != NGX_CONF_UNSET) {
251
+ len += 26;
252
+ len += conf->sticky_sessions ? sizeof("true") : sizeof("false");
253
+ }
254
+
255
+
256
+
257
+ if (conf->sticky_sessions_cookie_name.data != NULL) {
258
+ len += 38;
259
+ len += conf->sticky_sessions_cookie_name.len + 1;
260
+ }
261
+
262
+
249
263
 
250
264
  /* Create string */
251
265
  buf = pos = ngx_pnalloc(cf->pool, len);
@@ -604,6 +618,32 @@ buf = pos = ngx_pnalloc(cf->pool, len);
604
618
  }
605
619
 
606
620
 
621
+
622
+ if (conf->sticky_sessions != NGX_CONF_UNSET) {
623
+ pos = ngx_copy(pos,
624
+ "PASSENGER_STICKY_SESSIONS",
625
+ 26);
626
+ if (conf->sticky_sessions) {
627
+ pos = ngx_copy(pos, "true", sizeof("true"));
628
+ } else {
629
+ pos = ngx_copy(pos, "false", sizeof("false"));
630
+ }
631
+ }
632
+
633
+
634
+
635
+ if (conf->sticky_sessions_cookie_name.data != NULL) {
636
+ pos = ngx_copy(pos,
637
+ "PASSENGER_STICKY_SESSIONS_COOKIE_NAME",
638
+ 38);
639
+ pos = ngx_copy(pos,
640
+ conf->sticky_sessions_cookie_name.data,
641
+ conf->sticky_sessions_cookie_name.len);
642
+ *pos = '\0';
643
+ pos++;
644
+ }
645
+
646
+
607
647
 
608
648
  conf->options_cache.data = buf;
609
649
  conf->options_cache.len = pos - buf;