passenger 4.0.36 → 4.0.37

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

Potentially problematic release.


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

Files changed (41) hide show
  1. checksums.yaml +8 -8
  2. checksums.yaml.gz.asc +7 -7
  3. data.tar.gz.asc +7 -7
  4. data/NEWS +51 -0
  5. data/README.md +3 -1
  6. data/build/integration_tests.rb +5 -1
  7. data/build/test_basics.rb +2 -2
  8. data/dev/run_travis.sh +3 -0
  9. data/doc/Users guide Nginx.txt +3 -3
  10. data/ext/common/ApplicationPool2/Group.h +2 -1
  11. data/ext/common/ApplicationPool2/Implementation.cpp +30 -1
  12. data/ext/common/ApplicationPool2/Pool.h +26 -3
  13. data/ext/common/ApplicationPool2/Process.h +39 -10
  14. data/ext/common/Constants.h +1 -1
  15. data/ext/common/MultiLibeio.cpp +4 -0
  16. data/ext/common/ServerInstanceDir.h +1 -1
  17. data/ext/common/Utils.cpp +29 -0
  18. data/ext/common/Utils.h +7 -1
  19. data/ext/common/Utils/BufferedIO.h +13 -0
  20. data/ext/common/agents/HelperAgent/Main.cpp +6 -2
  21. data/ext/common/agents/HelperAgent/RequestHandler.h +32 -1
  22. data/helper-scripts/meteor-loader.rb +126 -10
  23. data/helper-scripts/node-loader.js +5 -3
  24. data/helper-scripts/wsgi-loader.py +23 -11
  25. data/lib/phusion_passenger.rb +1 -1
  26. data/lib/phusion_passenger/config/detach_process_command.rb +96 -0
  27. data/lib/phusion_passenger/config/main.rb +1 -0
  28. data/lib/phusion_passenger/request_handler.rb +11 -4
  29. data/node_lib/phusion_passenger/httplib_emulation.js +20 -14
  30. data/test/cxx/RequestHandlerTest.cpp +80 -0
  31. data/test/integration_tests/apache2_tests.rb +57 -0
  32. data/test/integration_tests/nginx_tests.rb +62 -0
  33. data/test/node/httplib_emulation_spec.js +137 -5
  34. data/test/node/spec_helper.js +13 -0
  35. data/test/stub/node/app.js +125 -0
  36. data/test/stub/node/public/.gitignore +0 -0
  37. data/test/stub/node/tmp/.gitignore +0 -0
  38. data/test/stub/rack/config.ru +19 -0
  39. data/test/stub/wsgi/passenger_wsgi.py +37 -1
  40. metadata +6 -2
  41. metadata.gz.asc +7 -7
@@ -30,7 +30,7 @@ module PhusionPassenger
30
30
 
31
31
  PACKAGE_NAME = 'passenger'
32
32
  # Run 'rake ext/common/Constants.h' after changing this number.
33
- VERSION_STRING = '4.0.36'
33
+ VERSION_STRING = '4.0.37'
34
34
 
35
35
  PREFERRED_NGINX_VERSION = '1.4.4'
36
36
  NGINX_SHA256_CHECKSUM = '7c989a58e5408c9593da0bebcd0e4ffc3d892d1316ba5042ddb0be5b0b4102b9'
@@ -0,0 +1,96 @@
1
+ # Phusion Passenger - https://www.phusionpassenger.com/
2
+ # Copyright (c) 2014 Phusion
3
+ #
4
+ # "Phusion Passenger" is a trademark of Hongli Lai & Ninh Bui.
5
+ #
6
+ # Permission is hereby granted, free of charge, to any person obtaining a copy
7
+ # of this software and associated documentation files (the "Software"), to deal
8
+ # in the Software without restriction, including without limitation the rights
9
+ # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
10
+ # copies of the Software, and to permit persons to whom the Software is
11
+ # furnished to do so, subject to the following conditions:
12
+ #
13
+ # The above copyright notice and this permission notice shall be included in
14
+ # all copies or substantial portions of the Software.
15
+ #
16
+ # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
17
+ # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
18
+ # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
19
+ # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
20
+ # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
21
+ # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
22
+ # THE SOFTWARE.
23
+
24
+ require 'optparse'
25
+ PhusionPassenger.require_passenger_lib 'constants'
26
+ PhusionPassenger.require_passenger_lib 'admin_tools/server_instance'
27
+ PhusionPassenger.require_passenger_lib 'config/command'
28
+ PhusionPassenger.require_passenger_lib 'config/utils'
29
+
30
+ module PhusionPassenger
31
+ module Config
32
+
33
+ class DetachProcessCommand < Command
34
+ include PhusionPassenger::Config::Utils
35
+
36
+ def self.description
37
+ return "Detach an application process from the process pool"
38
+ end
39
+
40
+ def run
41
+ parse_options
42
+ select_passenger_instance
43
+ @admin_client = connect_to_passenger_admin_socket(:role => :passenger_status)
44
+ perform_detach
45
+ end
46
+
47
+ private
48
+ def self.create_option_parser(options)
49
+ OptionParser.new do |opts|
50
+ nl = "\n" + ' ' * 37
51
+ opts.banner = "Usage: passenger-config detach-process [OPTIONS] <PID>\n"
52
+ opts.separator ""
53
+ opts.separator " Remove an application process from the #{PROGRAM_NAME} process pool."
54
+ opts.separator " Has a similar effect to killing the application process directly with"
55
+ opts.separator " `kill <PID>`, but killing directly may cause the HTTP client to see an"
56
+ opts.separator " error, while using this command guarantees that clients see no errors."
57
+ opts.separator ""
58
+
59
+ opts.separator "Options:"
60
+ opts.on("--instance INSTANCE_PID", Integer, "The #{PROGRAM_NAME} instance to select") do |value|
61
+ options[:instance] = value
62
+ end
63
+ opts.on("-h", "--help", "Show this help") do
64
+ options[:help] = true
65
+ end
66
+ end
67
+ end
68
+
69
+ def help
70
+ puts @parser
71
+ end
72
+
73
+ def parse_options
74
+ super
75
+ if @argv.empty?
76
+ abort "Please pass a PID. " +
77
+ "See --help for more information."
78
+ elsif @argv.size == 1
79
+ @pid = @argv[0].to_i
80
+ elsif @argv.size > 1
81
+ help
82
+ abort
83
+ end
84
+ end
85
+
86
+ def perform_detach
87
+ if @admin_client.pool_detach_process(@pid)
88
+ puts "Process #{@pid} detached."
89
+ else
90
+ abort "Could not detach process #{@pid}."
91
+ end
92
+ end
93
+ end
94
+
95
+ end # module Config
96
+ end # module PhusionPassenger
@@ -28,6 +28,7 @@ module PhusionPassenger
28
28
  # Core of the `passenger-config` command. Dispatches a subcommand to a specific class.
29
29
  module Config
30
30
  KNOWN_COMMANDS = [
31
+ ["detach-process", "DetachProcessCommand"],
31
32
  ["restart-app", "RestartAppCommand"],
32
33
  ["validate-install", "ValidateInstallCommand"],
33
34
  ["about", "AboutCommand"]
@@ -95,6 +95,11 @@ class RequestHandler
95
95
  "analytics_logger",
96
96
  "pool_account_username"
97
97
  )
98
+
99
+ @force_http_session = ENV["_PASSENGER_FORCE_HTTP_SESSION"] == "true"
100
+ if @force_http_session
101
+ @connect_password = nil
102
+ end
98
103
  @thread_handler = options["thread_handler"] || ThreadHandler
99
104
  @concurrency = 1
100
105
  if options["pool_account_password_base64"]
@@ -114,7 +119,7 @@ class RequestHandler
114
119
  @server_sockets[:main] = {
115
120
  :address => @main_socket_address,
116
121
  :socket => @main_socket,
117
- :protocol => :session,
122
+ :protocol => @force_http_session ? :http_session : :session,
118
123
  :concurrency => @concurrency
119
124
  }
120
125
 
@@ -125,7 +130,7 @@ class RequestHandler
125
130
  :protocol => :http,
126
131
  :concurrency => 1
127
132
  }
128
-
133
+
129
134
  @owner_pipe = owner_pipe
130
135
  @options = options
131
136
  @previous_signal_handlers = {}
@@ -308,7 +313,7 @@ private
308
313
  # is still bugged as of version 1.7.0. They can
309
314
  # cause unexplicable freezes when used in combination
310
315
  # with threading.
311
- return ruby_engine != "jruby"
316
+ return !@force_http_session && ruby_engine != "jruby"
312
317
  end
313
318
 
314
319
  def create_unix_socket_on_filesystem
@@ -400,7 +405,9 @@ private
400
405
  main_socket_options = common_options.merge(
401
406
  :server_socket => @main_socket,
402
407
  :socket_name => "main socket",
403
- :protocol => :session
408
+ :protocol => @server_sockets[:main][:protocol] == :session ?
409
+ :session :
410
+ :http
404
411
  )
405
412
  http_socket_options = common_options.merge(
406
413
  :server_socket => @http_socket,
@@ -1,6 +1,6 @@
1
1
  /*
2
2
  * Phusion Passenger - https://www.phusionpassenger.com/
3
- * Copyright (c) 2013 Phusion
3
+ * Copyright (c) 2013-2014 Phusion
4
4
  *
5
5
  * "Phusion Passenger" is a trademark of Hongli Lai & Ninh Bui.
6
6
  *
@@ -68,11 +68,15 @@ function inferHttpVersion(protocolDescription) {
68
68
  }
69
69
  }
70
70
 
71
- function mayHaveRequestBody(headers) {
72
- return headers['REQUEST_METHOD'] != 'GET' || headers['HTTP_UPGRADE'];
73
- }
74
-
75
71
  function createIncomingMessage(headers, socket, bodyBegin) {
72
+ /* Node's HTTP parser simulates an 'end' event if it determines that
73
+ * the request should not have a request body. Currently (Node 0.10.18),
74
+ * it thinks GET requests without an Upgrade header should not have a
75
+ * request body, even though technically such GET requests are allowed
76
+ * to have a request body. For compatibility reasons we implement the
77
+ * same behavior as Node's HTTP parser.
78
+ */
79
+
76
80
  var message = new http.IncomingMessage(socket);
77
81
  setHttpHeaders(message.headers, headers);
78
82
  message.cgiHeaders = headers;
@@ -81,7 +85,14 @@ function createIncomingMessage(headers, socket, bodyBegin) {
81
85
  message.url = headers['REQUEST_URI'];
82
86
  message.connection.remoteAddress = headers['REMOTE_ADDR'];
83
87
  message.connection.remotePort = parseInt(headers['REMOTE_PORT']);
84
- message._mayHaveRequestBody = mayHaveRequestBody(headers);
88
+ message.upgrade = !!headers['HTTP_UPGRADE'];
89
+
90
+ if (message.upgrade) {
91
+ // Emit end event as described above.
92
+ message.push(null);
93
+ return message;
94
+ }
95
+
85
96
  message._emitEndEvent = IncomingMessage_emitEndEvent;
86
97
  resetIncomingMessageOverridedMethods(message);
87
98
 
@@ -95,14 +106,7 @@ function createIncomingMessage(headers, socket, bodyBegin) {
95
106
  message.emit('timeout');
96
107
  });
97
108
 
98
- /* Node's HTTP parser simulates an 'end' event if it determines that
99
- * the request should not have a request body. Currently (Node 0.10.18),
100
- * it thinks GET requests without an Upgrade header should not have a
101
- * request body, even though technically such GET requests are allowed
102
- * to have a request body. For compatibility reasons we implement the
103
- * same behavior as Node's HTTP parser.
104
- */
105
- if (message._mayHaveRequestBody) {
109
+ if (headers['REQUEST_METHOD'] != 'GET') {
106
110
  if (bodyBegin.length > 0) {
107
111
  message.push(bodyBegin);
108
112
  }
@@ -112,6 +116,7 @@ function createIncomingMessage(headers, socket, bodyBegin) {
112
116
  }
113
117
  }
114
118
  } else {
119
+ // Emit end event as described above.
115
120
  message.push(null);
116
121
  }
117
122
 
@@ -139,6 +144,7 @@ function IncomingMessage_on(event, listener) {
139
144
  }
140
145
  this._orig_on.call(this, event, listener);
141
146
  resetIncomingMessageOverridedMethods(this);
147
+ return this;
142
148
  }
143
149
 
144
150
  function IncomingMessage_emitEndEvent() {
@@ -6,6 +6,7 @@
6
6
  #include <Utils/json.h>
7
7
  #include <Utils/IOUtils.h>
8
8
  #include <Utils/Timer.h>
9
+ #include <Utils/BufferedIO.h>
9
10
 
10
11
  #include <boost/shared_array.hpp>
11
12
  #include <string>
@@ -51,6 +52,7 @@ namespace tut {
51
52
  setPrintAppOutputAsDebuggingMessages(true);
52
53
 
53
54
  agentOptions.passengerRoot = resourceLocator->getRoot();
55
+ agentOptions.defaultRubyCommand = DEFAULT_RUBY;
54
56
  agentOptions.defaultUser = testConfig["default_user"].asString();
55
57
  agentOptions.defaultGroup = testConfig["default_group"].asString();
56
58
  root = resourceLocator->getRoot();
@@ -972,6 +974,84 @@ namespace tut {
972
974
  ensure(containsSubstring(response, "Counter: 2\n"));
973
975
  }
974
976
 
977
+ TEST_METHOD(53) {
978
+ set_test_name("It supports switching protocols when communicating over application session sockets");
979
+
980
+ init();
981
+ connect();
982
+ sendHeaders(defaultHeaders,
983
+ "PASSENGER_APP_ROOT", wsgiAppPath.c_str(),
984
+ "PATH_INFO", "/switch_protocol",
985
+ "HTTP_UPGRADE", "raw",
986
+ "HTTP_CONNECTION", "Upgrade",
987
+ NULL
988
+ );
989
+
990
+ BufferedIO io(connection);
991
+ string header;
992
+ bool done = false;
993
+
994
+ ensure_equals(io.readLine(), "HTTP/1.1 101 Switching Protocols\r\n");
995
+
996
+ do {
997
+ string line = io.readLine();
998
+ done = line.empty() || line == "\r\n";
999
+ if (!done) {
1000
+ header.append(line);
1001
+ }
1002
+ } while (!done);
1003
+
1004
+ ensure("(1)", containsSubstring(header, "Upgrade: raw\r\n"));
1005
+ ensure("(2)", containsSubstring(header, "Connection: Upgrade\r\n"));
1006
+
1007
+ writeExact(connection, "hello\n");
1008
+ ensure_equals(io.readLine(), "Echo: hello\n");
1009
+ }
1010
+
1011
+ TEST_METHOD(54) {
1012
+ set_test_name("It supports switching protocols when communication over application http_session sockets");
1013
+
1014
+ init();
1015
+ connect();
1016
+ sendHeaders(defaultHeaders,
1017
+ "_PASSENGER_FORCE_HTTP_SESSION", "true",
1018
+ "PASSENGER_APP_ROOT", rackAppPath.c_str(),
1019
+ "PASSENGER_APP_TYPE", "rack",
1020
+ "REQUEST_URI", "/switch_protocol",
1021
+ "PATH_INFO", "/switch_protocol",
1022
+ "HTTP_UPGRADE", "raw",
1023
+ "HTTP_CONNECTION", "Upgrade",
1024
+ NULL
1025
+ );
1026
+
1027
+ BufferedIO io(connection);
1028
+ string header;
1029
+ bool done = false;
1030
+ vector<ProcessPtr> processes;
1031
+
1032
+ ensure_equals(io.readLine(), "HTTP/1.1 101 Switching Protocols\r\n");
1033
+ processes = pool->getProcesses();
1034
+ {
1035
+ LockGuard l(pool->syncher);
1036
+ ProcessPtr process = processes[0];
1037
+ ensure_equals(process->sessionSockets.top()->protocol, "http_session");
1038
+ }
1039
+
1040
+ do {
1041
+ string line = io.readLine();
1042
+ done = line.empty() || line == "\r\n";
1043
+ if (!done) {
1044
+ header.append(line);
1045
+ }
1046
+ } while (!done);
1047
+
1048
+ ensure("(1)", containsSubstring(header, "Upgrade: raw\r\n"));
1049
+ ensure("(2)", containsSubstring(header, "Connection: Upgrade\r\n"));
1050
+
1051
+ writeExact(connection, "hello\n");
1052
+ ensure_equals(io.readLine(), "Echo: hello\n");
1053
+ }
1054
+
975
1055
  // Test small response buffering.
976
1056
  // Test large response buffering.
977
1057
  }
@@ -158,6 +158,63 @@ describe "Apache 2 module" do
158
158
  end
159
159
  end
160
160
 
161
+ describe "a Node.js app running on the root URI" do
162
+ before :all do
163
+ create_apache2_controller
164
+ @server = "http://1.passenger.test:#{@apache2.port}"
165
+ @stub = NodejsStub.new('node')
166
+ @apache2 << "RailsMaxPoolSize 1"
167
+ @apache2.set_vhost("1.passenger.test", "#{@stub.full_app_root}/public")
168
+ @apache2.start
169
+ end
170
+
171
+ after :all do
172
+ @stub.destroy
173
+ @apache2.stop if @apache2
174
+ end
175
+
176
+ before :each do
177
+ @stub.reset
178
+ end
179
+
180
+ it_should_behave_like "an example web app"
181
+ end
182
+
183
+ describe "a Node.js app running in a sub-URI" do
184
+ before :all do
185
+ create_apache2_controller
186
+ @server = "http://1.passenger.test:#{@apache2.port}/subapp"
187
+ @stub = NodejsStub.new('node')
188
+ @apache2 << "RailsMaxPoolSize 1"
189
+ @apache2.set_vhost("1.passenger.test", File.expand_path("stub")) do |vhost|
190
+ vhost << %Q{
191
+ Alias /subapp #{@stub.full_app_root}/public
192
+ <Location /subapp>
193
+ PassengerBaseURI /subapp
194
+ PassengerAppRoot #{@stub.full_app_root}
195
+ </Location>
196
+ }
197
+ end
198
+ @apache2.start
199
+ end
200
+
201
+ after :all do
202
+ @stub.destroy
203
+ @apache2.stop if @apache2
204
+ end
205
+
206
+ before :each do
207
+ @stub.reset
208
+ end
209
+
210
+ it_should_behave_like "an example web app"
211
+
212
+ it "does not interfere with the root website" do
213
+ @server = "http://1.passenger.test:#{@apache2.port}"
214
+ get('/').should == "This is the stub directory."
215
+ end
216
+ end
217
+
161
218
  describe "compatibility with other modules" do
162
219
  before :all do
163
220
  create_apache2_controller
@@ -163,6 +163,68 @@ describe "Phusion Passenger for Nginx" do
163
163
  end
164
164
  end
165
165
 
166
+ describe "a Node.js app running on the root URI" do
167
+ before :all do
168
+ create_nginx_controller
169
+ @server = "http://1.passenger.test:#{@nginx.port}"
170
+ @stub = NodejsStub.new('node')
171
+ @nginx.add_server do |server|
172
+ server[:server_name] = "1.passenger.test"
173
+ server[:root] = "#{@stub.full_app_root}/public"
174
+ end
175
+ @nginx.start
176
+ end
177
+
178
+ after :all do
179
+ @stub.destroy
180
+ @nginx.stop if @nginx
181
+ end
182
+
183
+ before :each do
184
+ @stub.reset
185
+ end
186
+
187
+ it_should_behave_like "an example web app"
188
+ end
189
+
190
+ describe "a Node.js app running in a sub-URI" do
191
+ before :all do
192
+ create_nginx_controller
193
+ @server = "http://1.passenger.test:#{@nginx.port}/subapp"
194
+ @stub = NodejsStub.new('node')
195
+ @nginx.add_server do |server|
196
+ server[:server_name] = "1.passenger.test"
197
+ server[:root] = "#{PhusionPassenger.source_root}/test/stub"
198
+ server << %Q{
199
+ location ~ ^/subapp(/.*|$) {
200
+ alias #{@stub.full_app_root}/public$1;
201
+ passenger_base_uri /subapp;
202
+ passenger_document_root #{@stub.full_app_root}/public;
203
+ passenger_app_root #{@stub.full_app_root};
204
+ passenger_enabled on;
205
+ }
206
+ }
207
+ end
208
+ @nginx.start
209
+ end
210
+
211
+ after :all do
212
+ @stub.destroy
213
+ @nginx.stop if @nginx
214
+ end
215
+
216
+ before :each do
217
+ @stub.reset
218
+ end
219
+
220
+ it_should_behave_like "an example web app"
221
+
222
+ it "does not interfere with the root website" do
223
+ @server = "http://1.passenger.test:#{@nginx.port}"
224
+ get('/').should == "This is the stub directory."
225
+ end
226
+ end
227
+
166
228
  describe "various features" do
167
229
  before :all do
168
230
  create_nginx_controller