如何从 lxc 容器内部服务 websocket 应用程序?
How to serve websocket apps from inside lxc container?
在 lxc
容器中,我正在 运行 安装一个 faye
应用程序。
Gemfile
:
source 'https://rubygems.org'
gem 'faye'
gem 'thin'
config.ru
:
require 'faye'
Faye::WebSocket.load_adapter('thin')
bayeux = Faye::RackAdapter.new(:mount => '/faye', :timeout => 25)
run bayeux
然后
$ thin start
/etc/nginx/sites-available/domain.com
:
server {
server_name domain.com;
location /faye {
proxy_pass http://localhost:3000;
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection 'upgrade';
}
}
在主机上:
/etc/nginx/sites-available/domain.com
:
server {
server_name domain.com;
location / {
proxy_pass http://10.0.0.109:80;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header Host $http_host;
}
}
然后,我尝试从 here (ws://domain.com/faye
) 连接到它,但失败了。我做错了什么?
应用程序说:
Using rack adapter
Thin web server (v1.6.4 codename Gob Bluth)
Maximum connections set to 1024
Listening on 0.0.0.0:3000, CTRL+C to stop
访客nginx
访问日志:
10.0.0.1 - - [09/Dec/2015:11:03:21 +0200] "GET /faye?encoding=text HTTP/1.0" 400 11 "-" "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/46.0.2490.86 Safari/537.36"
主机nginx
访问日志:
11.111.111.111 - - [09/Dec/2015:11:03:21 +0200] "GET /faye?encoding=text HTTP/1.1" 400 21 "-" "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/46.0.2490.86 Safari/537.36"
chrome
的开发者工具控制台:
WebSocket connection to 'ws://domain.com/faye?encoding=text' failed: Error during WebSocket handshake: Unexpected response code: 400
我试过 运行 Myst 在客人上推荐的应用程序。
Gemfile
:
source 'https://rubygems.org'
gem 'plezi'
app.rb
:
#!/usr/bin/env ruby
# require the gems
require 'bundler'
Bundler.require(:default, ENV['ENV'].to_s.to_sym)
# handle requests
class MyController
# Http
def index
Iodine.log request_data_string
end
# Websockets
def on_message data
write ERB::Util.html_escape(data)
end
def pre_connect
puts Iodine.log(request_data_string)
true
end
def on_open
write 'Welcome!'
end
# formatting the request data
protected
def request_data_string
out = String.new
out << "Request headers:\n"
out << (request.headers.to_a.map {|p| p.join ': '} .join "\n")
out << "\n\nRequest cookies:\n"
out << (request.cookies.to_a.map {|p| p.join ': '} .join "\n")
out << "\n\nAll request data:\n"
out << (request.to_a.map {|p| p.join ': '} .join "\n")
out
end
end
route '*', MyController
# # you can also set up logging to a file:
# Plezi.logger = Logger.new("filename.log")
然后
$ ruby app.rb
/etc/nginx/sites-available/domain.com
:
server {
server_name domain.com;
location / {
proxy_pass http://localhost:3000;
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection 'upgrade';
}
}
有了这个,当我连接到 ws://domain.com/
时,应用程序说:
Iodine 0.1.19 is listening on port 3000
Plezi is feeling optimistic running version 0.12.21.
Press ^C to stop the server.
Request headers:
connection: upgrade
host: localhost:3000
x-forwarded-for: 11.111.111.111
pragma: no-cache
cache-control: no-cache
origin: http://www.websocket.org
sec-websocket-version: 13
user-agent: Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/46.0.2490.86 Safari/537.36
accept-encoding: gzip, deflate, sdch
accept-language: en-US,en;q=0.8
sec-websocket-key: Qs2LMnJ12SjclOxlrYKwlg==
sec-websocket-extensions: permessage-deflate; client_max_window_bits
Request cookies:
All request data:
io: #<Iodine::Http::Http1:0x007fd5a0006b08>
cookies: {}
params: {:encoding=>"text"}
method: GET
query: /?encoding=text
version: 1.1
time_recieved: 2015-12-09 11:24:16 +0200
connection: upgrade
host: localhost:3000
x-forwarded-for: 11.111.111.111
pragma: no-cache
cache-control: no-cache
origin: http://www.websocket.org
sec-websocket-version: 13
user-agent: Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/46.0.2490.86 Safari/537.36
accept-encoding: gzip, deflate, sdch
accept-language: en-US,en;q=0.8
sec-websocket-key: Qs2LMnJ12SjclOxlrYKwlg==
sec-websocket-extensions: permessage-deflate; client_max_window_bits
headers_complete: true
client_ip: 11.111.111.111
scheme: http
host_name: localhost
port: 3000
path:
original_path: /
query_params: encoding=text
host_settings: {:index_file=>"index.html", :assets_public=>"/assets", :public=>nil, :assets_public_regex=>/^\/assets\//i, :assets_public_length=>8, :assets_refuse_templates=>/(erb|coffee|scss|sass|\.\.\/)$/i}11.111.111.111 [2015-12-09 09:24:16 UTC] "GET / http/1.1" 200 1659 0.6ms
访客nginx
访问日志:
10.0.0.1 - - [09/Dec/2015:10:55:44 +0200] "GET /?encoding=text HTTP/1.0" 200 1526 "-" "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/46.0.2490.86 Safari/537.36"
主机nginx
访问日志:
11.111.111.111 - - [09/Dec/2015:10:55:44 +0200] "GET /?encoding=text HTTP/1.1" 200 1526 "-" "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/46.0.2490.86 Safari/537.36"
chrome
的开发者工具控制台:
WebSocket connection to 'ws://domain.com/?encoding=text' failed: Error during WebSocket handshake: Unexpected response code: 200
这不是答案,只是我对如何调试问题的想法。
将请求的 header 输出到日志将提供有关该问题的更多信息(应用程序看到的 header 代理 header 更改后,可能与预期不同)。
这是一个使用 Plezi 的小应用程序,它既是一个 websocket Echo 服务器,应该将 headers 打印到日志中(默认日志是 STDOUT,但你也可以使用文件),允许您查看 nginx 设置和请求 headers:
gemfile
:
gem plezi
重要的是不要使用thin
或任何其他服务器,因为 Plezi 使用它自己的 Iodine 服务器。
app.rb
文件:
#!/usr/bin/env ruby
# require the gems
require 'bundler'
Bundler.require(:default, ENV['ENV'].to_s.to_sym)
# handle requests
class MyController
# Http
def index
Iodine.log request_data_string
end
# Websockets
def on_message data
write ERB::Util.html_escape(data)
end
def pre_connect
puts Iodine.log(request_data_string)
true
end
def on_open
write 'Welcome!'
end
# formatting the request data
protected
def request_data_string
out = String.new
out << "Request headers:\n"
out << (request.headers.to_a.map {|p| p.join ': '} .join "\n")
out << "\n\nRequest cookies:\n"
out << (request.cookies.to_a.map {|p| p.join ': '} .join "\n")
out << "\n\nAll request data:\n"
out << (request.to_a.map {|p| p.join ': '} .join "\n")
out
end
end
route '*', MyController
# # you can also set up logging to a file:
# Plezi.logger = Logger.new("filename.log")
开始:
ruby app.rb
或(使用任何端口号,默认为 3000):
./app.rb -p 3000
这应该是一个足够好的应用程序来测试和调试任何 headers。
Post 您问题中的 header 数据以获得进一步的帮助。可能是 header 没有正确解析 websocket 连接。
编辑
我注意到 Plezi 给出的响应是 200 OK 状态代码 - 这意味着请求被假定为 Http 请求。
这意味着这可能是 header 的问题,因为该请求未被识别为 websocket 升级请求。
编辑 2
在我的机器上,以下设置允许我代理 Http 请求和 Websocket 请求(但我使用的是 Plezi,它允许我使用 Websockets、Plezi 的 Http 和 Rack 应用程序(即 Rails ) 在同一端口上):
location / {
proxy_pass http://localhost:3000;
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection $http_connection;
}
...但您的配置也有效(只是 messed-up Http 的 keep-alive,而不是 websockets)...
...所以我怀疑我是否发现了问题。
可能更简单的设置方法是从主机 nginx
直接代理到应用程序:
/etc/nginx/sites-available/domain.com
(主持人):
server {
server_name domain.com;
location / {
proxy_pass http://10.0.0.109:3000;
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection 'upgrade';
}
}
但是如果你想拥有相同的 nginx 配置,无论应用程序是 运行 在主机上还是来宾上,你可以用两个 nginx
.
代理它
/etc/nginx/sites-available/domain.com
(主持人):
server {
server_name domain.com;
location / {
proxy_pass http://10.0.0.109:80;
proxy_set_header Host $http_host;
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection 'upgrade';
}
}
在这里传递 Host
header 是至关重要的。否则 guest nginx
可能会让错误的虚拟主机处理请求。这是我很长一段时间都没有意识到的。
/etc/nginx/sites-available/domain.com
(来宾):
server {
server_name domain.com;
location / {
proxy_pass http://localhost:3000;
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection 'upgrade';
}
}
在 lxc
容器中,我正在 运行 安装一个 faye
应用程序。
Gemfile
:
source 'https://rubygems.org'
gem 'faye'
gem 'thin'
config.ru
:
require 'faye'
Faye::WebSocket.load_adapter('thin')
bayeux = Faye::RackAdapter.new(:mount => '/faye', :timeout => 25)
run bayeux
然后
$ thin start
/etc/nginx/sites-available/domain.com
:
server {
server_name domain.com;
location /faye {
proxy_pass http://localhost:3000;
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection 'upgrade';
}
}
在主机上:
/etc/nginx/sites-available/domain.com
:
server {
server_name domain.com;
location / {
proxy_pass http://10.0.0.109:80;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header Host $http_host;
}
}
然后,我尝试从 here (ws://domain.com/faye
) 连接到它,但失败了。我做错了什么?
应用程序说:
Using rack adapter
Thin web server (v1.6.4 codename Gob Bluth)
Maximum connections set to 1024
Listening on 0.0.0.0:3000, CTRL+C to stop
访客nginx
访问日志:
10.0.0.1 - - [09/Dec/2015:11:03:21 +0200] "GET /faye?encoding=text HTTP/1.0" 400 11 "-" "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/46.0.2490.86 Safari/537.36"
主机nginx
访问日志:
11.111.111.111 - - [09/Dec/2015:11:03:21 +0200] "GET /faye?encoding=text HTTP/1.1" 400 21 "-" "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/46.0.2490.86 Safari/537.36"
chrome
的开发者工具控制台:
WebSocket connection to 'ws://domain.com/faye?encoding=text' failed: Error during WebSocket handshake: Unexpected response code: 400
我试过 运行 Myst 在客人上推荐的应用程序。
Gemfile
:
source 'https://rubygems.org'
gem 'plezi'
app.rb
:
#!/usr/bin/env ruby
# require the gems
require 'bundler'
Bundler.require(:default, ENV['ENV'].to_s.to_sym)
# handle requests
class MyController
# Http
def index
Iodine.log request_data_string
end
# Websockets
def on_message data
write ERB::Util.html_escape(data)
end
def pre_connect
puts Iodine.log(request_data_string)
true
end
def on_open
write 'Welcome!'
end
# formatting the request data
protected
def request_data_string
out = String.new
out << "Request headers:\n"
out << (request.headers.to_a.map {|p| p.join ': '} .join "\n")
out << "\n\nRequest cookies:\n"
out << (request.cookies.to_a.map {|p| p.join ': '} .join "\n")
out << "\n\nAll request data:\n"
out << (request.to_a.map {|p| p.join ': '} .join "\n")
out
end
end
route '*', MyController
# # you can also set up logging to a file:
# Plezi.logger = Logger.new("filename.log")
然后
$ ruby app.rb
/etc/nginx/sites-available/domain.com
:
server {
server_name domain.com;
location / {
proxy_pass http://localhost:3000;
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection 'upgrade';
}
}
有了这个,当我连接到 ws://domain.com/
时,应用程序说:
Iodine 0.1.19 is listening on port 3000
Plezi is feeling optimistic running version 0.12.21.
Press ^C to stop the server.
Request headers:
connection: upgrade
host: localhost:3000
x-forwarded-for: 11.111.111.111
pragma: no-cache
cache-control: no-cache
origin: http://www.websocket.org
sec-websocket-version: 13
user-agent: Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/46.0.2490.86 Safari/537.36
accept-encoding: gzip, deflate, sdch
accept-language: en-US,en;q=0.8
sec-websocket-key: Qs2LMnJ12SjclOxlrYKwlg==
sec-websocket-extensions: permessage-deflate; client_max_window_bits
Request cookies:
All request data:
io: #<Iodine::Http::Http1:0x007fd5a0006b08>
cookies: {}
params: {:encoding=>"text"}
method: GET
query: /?encoding=text
version: 1.1
time_recieved: 2015-12-09 11:24:16 +0200
connection: upgrade
host: localhost:3000
x-forwarded-for: 11.111.111.111
pragma: no-cache
cache-control: no-cache
origin: http://www.websocket.org
sec-websocket-version: 13
user-agent: Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/46.0.2490.86 Safari/537.36
accept-encoding: gzip, deflate, sdch
accept-language: en-US,en;q=0.8
sec-websocket-key: Qs2LMnJ12SjclOxlrYKwlg==
sec-websocket-extensions: permessage-deflate; client_max_window_bits
headers_complete: true
client_ip: 11.111.111.111
scheme: http
host_name: localhost
port: 3000
path:
original_path: /
query_params: encoding=text
host_settings: {:index_file=>"index.html", :assets_public=>"/assets", :public=>nil, :assets_public_regex=>/^\/assets\//i, :assets_public_length=>8, :assets_refuse_templates=>/(erb|coffee|scss|sass|\.\.\/)$/i}11.111.111.111 [2015-12-09 09:24:16 UTC] "GET / http/1.1" 200 1659 0.6ms
访客nginx
访问日志:
10.0.0.1 - - [09/Dec/2015:10:55:44 +0200] "GET /?encoding=text HTTP/1.0" 200 1526 "-" "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/46.0.2490.86 Safari/537.36"
主机nginx
访问日志:
11.111.111.111 - - [09/Dec/2015:10:55:44 +0200] "GET /?encoding=text HTTP/1.1" 200 1526 "-" "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/46.0.2490.86 Safari/537.36"
chrome
的开发者工具控制台:
WebSocket connection to 'ws://domain.com/?encoding=text' failed: Error during WebSocket handshake: Unexpected response code: 200
这不是答案,只是我对如何调试问题的想法。
将请求的 header 输出到日志将提供有关该问题的更多信息(应用程序看到的 header 代理 header 更改后,可能与预期不同)。
这是一个使用 Plezi 的小应用程序,它既是一个 websocket Echo 服务器,应该将 headers 打印到日志中(默认日志是 STDOUT,但你也可以使用文件),允许您查看 nginx 设置和请求 headers:
gemfile
:
gem plezi
重要的是不要使用thin
或任何其他服务器,因为 Plezi 使用它自己的 Iodine 服务器。
app.rb
文件:
#!/usr/bin/env ruby
# require the gems
require 'bundler'
Bundler.require(:default, ENV['ENV'].to_s.to_sym)
# handle requests
class MyController
# Http
def index
Iodine.log request_data_string
end
# Websockets
def on_message data
write ERB::Util.html_escape(data)
end
def pre_connect
puts Iodine.log(request_data_string)
true
end
def on_open
write 'Welcome!'
end
# formatting the request data
protected
def request_data_string
out = String.new
out << "Request headers:\n"
out << (request.headers.to_a.map {|p| p.join ': '} .join "\n")
out << "\n\nRequest cookies:\n"
out << (request.cookies.to_a.map {|p| p.join ': '} .join "\n")
out << "\n\nAll request data:\n"
out << (request.to_a.map {|p| p.join ': '} .join "\n")
out
end
end
route '*', MyController
# # you can also set up logging to a file:
# Plezi.logger = Logger.new("filename.log")
开始:
ruby app.rb
或(使用任何端口号,默认为 3000):
./app.rb -p 3000
这应该是一个足够好的应用程序来测试和调试任何 headers。
Post 您问题中的 header 数据以获得进一步的帮助。可能是 header 没有正确解析 websocket 连接。
编辑
我注意到 Plezi 给出的响应是 200 OK 状态代码 - 这意味着请求被假定为 Http 请求。
这意味着这可能是 header 的问题,因为该请求未被识别为 websocket 升级请求。
编辑 2
在我的机器上,以下设置允许我代理 Http 请求和 Websocket 请求(但我使用的是 Plezi,它允许我使用 Websockets、Plezi 的 Http 和 Rack 应用程序(即 Rails ) 在同一端口上):
location / {
proxy_pass http://localhost:3000;
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection $http_connection;
}
...但您的配置也有效(只是 messed-up Http 的 keep-alive,而不是 websockets)...
...所以我怀疑我是否发现了问题。
可能更简单的设置方法是从主机 nginx
直接代理到应用程序:
/etc/nginx/sites-available/domain.com
(主持人):
server {
server_name domain.com;
location / {
proxy_pass http://10.0.0.109:3000;
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection 'upgrade';
}
}
但是如果你想拥有相同的 nginx 配置,无论应用程序是 运行 在主机上还是来宾上,你可以用两个 nginx
.
/etc/nginx/sites-available/domain.com
(主持人):
server {
server_name domain.com;
location / {
proxy_pass http://10.0.0.109:80;
proxy_set_header Host $http_host;
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection 'upgrade';
}
}
在这里传递 Host
header 是至关重要的。否则 guest nginx
可能会让错误的虚拟主机处理请求。这是我很长一段时间都没有意识到的。
/etc/nginx/sites-available/domain.com
(来宾):
server {
server_name domain.com;
location / {
proxy_pass http://localhost:3000;
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection 'upgrade';
}
}