【问题标题】:How to serve websocket apps from inside lxc container?如何从 lxc 容器内部提供 websocket 应用程序?
【发布时间】:2015-12-08 20:43:46
【问题描述】:

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 的开发者工具控制台:

与“ws://domain.com/faye?encoding=text”的 WebSocket 连接失败:WebSocket 握手期间出错:意外响应代码: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 的开发者工具控制台:

与“ws://domain.com/?encoding=text”的 WebSocket 连接失败:WebSocket 握手期间出错:意外响应代码:200

【问题讨论】:

  • 我不确定,但我认为 Faye 需要一个特定的 javascript 客户端,并且我认为可能涉及一些连接头,并且在 websocket 之前发出 XHR 请求(但我可能错了)。 ..
  • 使用 faye 是我知道的设置 websocket 服务器的最简单方法。当移动到主机时,我可以从the page I mentioned 连接到应用程序。
  • 所以这不是客户问题...很高兴知道。它可能是来自 nginx 代理的请求标头。您是否尝试使用Plezi 将标头打印到 STDOUT 或日志文件?我发布了一个小脚本,可以将标头打印到 STDOUT...也许它有助于找到问题。
  • 由于某种原因没有打印标题。
  • 好吧...我的错(对不起)...请在我的回答中查看更新的index 方法(注意我更改了request_data 方法名称)...索引方法现在应该是显示Iodine.log request_data 的单行...原因是Http 路径没有记录标头,并且由于标头有问题,因此假定请求是Http 而不是Websockets。

标签: ruby nginx websocket lxc


【解决方案1】:

这不是答案,只是我对如何调试问题的想法。

将请求的标头输出到日志将提供有关问题的更多信息(在代理标头更改后,应用看到的标头可能与预期不同)。

这是一个使用 Plezi 的小应用程序,它既是 websocket Echo 服务器,应该将标头打印到日志中(默认日志是 STDOUT,但您也可以使用文件),允许您查看 nginx 设置和请求头:

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

这应该是一个足以测试和调试任何标头的应用程序。

在您的问题中发布标题数据以获得进一步的帮助。可能是没有为 websocket 连接正确解析标头。


编辑

我注意到 Plezi 给出的响应是 200 OK 状态码 - 这意味着该请求被假定为 Http 请求。

这意味着它可能是标头的问题,因为该请求未被识别为 websocket 升级请求。


编辑 2

在我的机器上,以下设置允许我代理 Http 请求和 Websocket 请求(但我使用的是 Plezi,它允许我在同一个端口上同时使用 Websocket、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;
    }

...但是您的配置也有效(只是搞砸了 Http 的 keep-alive,而不是 websockets)...

...所以我怀疑我是否发现了问题。

【讨论】:

  • 我想通了。我没有通过 Host 标头。见my answer。感谢您对我的包容。
【解决方案2】:

可能更简单的设置方法是从主机 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 标头至关重要。否则来宾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';
    }
}

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2011-05-07
    • 2017-10-27
    • 2016-05-02
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多