WebSocket connection to 'ws://localhost:3000/ws' failed: Error during WebSocket handshake: Unexpected response code: 200
jerryshen opened this issue · comments
could you please have a look at this error, I can't figure it out
Hi @jerryshen ,
Could you provide more details. I'm not sure I can track down the issue without a minimal working example that reproduces the error.
As a side-note: did you route the "/ws"
route to a controller that includes an on_message
callback?
require 'plezi'
class ChatServer
def chat_auth event
if params[:nickname] || (::ERB::Util.h(event[:nickname]) == "Me")
# Not allowed (double authentication / reserved name)
close
return
end
# set our ID and subscribe to the chatroom's channel
params[:nickname] = ::ERB::Util.h event[:nickname]
subscribe channel: :chat
# publish the new client's presence.
publish channel: :chat, message: {event: 'chat_login',
name: params[:nickname],
user_id: id}.to_json
# if we return an object, it will be sent to the websocket client.
nil
end
def chat_message msg
# prevent false identification
msg[:name] = params[:nickname]
msg[:user_id] = id
# publish the chat message
publish channel: :chat, message: msg.to_json
nil
end
def on_close
# inform about client leaving the chatroom
publish channel: :chat, message: {event: 'chat_logout',
name: params[:nickname],
user_id: id}.to_json
end
end
# setup route to html/javascript client
# Plezi.templates = Root.join('views').to_s
# connect route to controller
Plezi.route '/ws', ChatServer
I'm using Plezi with rails5 application, following the document from http://www.plezi.io/docs/hello_chat
On a first look, the code you sent is missing the Websocket auto-dispatch authorization flag event though it uses the auto-dispatch pattern.
Auto-dispatch invokes an internal on_message
callback that routes the event
JSON property to a controller method (as well as handles ACK responses, if requested).
Without an authorization flag, auto-dispatch is disabled and protects the public controller methods from being accessed through Websocket connections.
The flag is set using:
@auto_dispatch = true
i.e.:
require 'plezi'
class ChatServer
@auto_dispatch = true
# ... def chat_auth event ...
form code below
form#monitor
p
input#input placeholder="Your message" type="text"
input type="submit" value="Broadcast"
ul#output
I'v added auto dispatch, but it returns the same error, it says System: Connection Lost.
on the output screen, it keeps reconnect and return same errors.
WebSocket connection to 'ws://localhost:3000/ws' failed: Error during WebSocket handshake: Unexpected response code: 200
Hi @jerryshen ,
Thank you for letting me know.
I added the auto-dispatch flag to the code you sent and tested the issue.
It's working as expected on my machine.
However, the test was using Plezi directly, without Rails. So I wonder if it's a Rails integration issue.
Is there any way you could share a link to an example project? It isn't super important, but it would speed things up.
Thanks!
P.S.
The code I used:
require 'plezi'
class ChatServer
# the Controller's (class) auto-dispatch authorization flag
@auto_dispatch = true
def chat_auth event
if params[:nickname] || (::ERB::Util.h(event[:nickname]) == "Me")
# Not allowed (double authentication / reserved name)
close
return
end
# set our ID and subscribe to the chatroom's channel
params[:nickname] = ::ERB::Util.h event[:nickname]
subscribe channel: :chat
# publish the new client's presence.
publish channel: :chat, message: {event: 'chat_login',
name: params[:nickname],
user_id: id}.to_json
# if we return an object, it will be sent to the websocket client.
nil
end
def chat_message msg
# prevent false identification
msg[:name] = params[:nickname]
msg[:user_id] = id
# publish the chat message
publish channel: :chat, message: msg.to_json
nil
end
def on_close
# inform about client leaving the chatroom
publish channel: :chat, message: {event: 'chat_logout',
name: params[:nickname],
user_id: id}.to_json
end
end
# setup route to html/javascript client
# Plezi.templates = Root.join('views').to_s
# connect route to controller
Plezi.route '/ws', ChatServer
hey thanks very much,
this is the source code from github
https://github.com/jerryshen/hg
I think it should be the integration issue with rails, but can not figure it out.
I tested the code within Rails (and fixed a typo that occurred when I copied and pasted the code).
It runs properly on my machine.
The only thing I can think of is that you're somehow running a different server instead of iodine
(the server used by Plezi).
Are you starting the app using puma
or rails s
... or maybe thin
(like the procfile
)?
yes, I'm running thin for development
ok. when I use iodine to start the server, it works, but I'm running unicorn on production server. so I have to use iodine instead of unicorn, is that correct?
thanks
is there any way to use unicorn for rails app and iodine for plezi together?
I have to use iodine instead of unicorn, is that correct?
Yes,m that is correct.
Plezi leverages the Websocket Rack Specification Draft as well as some iodine
specific extensions.
This is a key factor in Plezi's Websocket performance and features, and not only because iodine is written in C and was optimized to defer entry into the Ruby GIL whenever possible (allowing for better concurrency during network events, parsing etc').
For example, iodine
supports native pub/sub without the need for Redis. iodine
also includes a Redis integration engine, allowing for horizontal scaling with a single line.
These features are at the core of Plezi's design, so Plezi Websockets can't be used with other servers.
When you're using iodine
in development, I recommend that you limit the number of processes (workers) to 1. For example:
$ iodine -w 1 -t 16
If left unspecified, iodine
will test the system and choose it's own number of threads and cores, slightly preferring concurrency over performance.
is there any way to use unicorn for rails app and iodine for plezi together?
Not on the same domain and sub-domain combination.
When a request arrives at the server, it's routed to your application and answered by a single listening socket according to the server's domain name and sub-domain.
The server controlling that socket (thin, puma, unicorn, iodine, etc') is the one that manages the connection.
To leverage iodine's native Websocket support, it must control the connection.
However, if you use ws.example.com
for web socket connections and example.com
or www.example.com
for HTTP, it's possible to run two servers side by side.
I think iodine server has conflicts with coffee script, when I have coffee script code on views, it raise 500 server error, however it's working on rails server.
11:57:56 web.1 | undefined method `success?' for nil:NilClass
11:57:56 web.1 | /Users/jerry/.rvm/gems/ruby-2.4.1@hg/gems/execjs-2.7.0/lib/execjs/external_runtime.rb:216:in `exec_runtime'
11:57:56 web.1 | /Users/jerry/.rvm/gems/ruby-2.4.1@hg/gems/execjs-2.7.0/lib/execjs/external_runtime.rb:39:in `exec'
11:57:56 web.1 | /Users/jerry/.rvm/gems/ruby-2.4.1@hg/gems/execjs-2.7.0/lib/execjs/external_runtime.rb:21:in `eval'
11:57:56 web.1 | /Users/jerry/.rvm/gems/ruby-2.4.1@hg/gems/execjs-2.7.0/lib/execjs/external_runtime.rb:46:in `call'
11:57:56 web.1 | /Users/jerry/.rvm/gems/ruby-2.4.1@hg/gems/coffee-script-2.4.1/lib/coffee_script.rb:78:in `compile'
@jerryshen , thank you for pointing this out.
I would like to investigate this concern. Which script fails to render?
when it has coffee script code in views, it raise 500 error, but it's not 100% happens. seems strange.
my code below
.login-box
// html code
coffee:
$('form#new_user').on 'ajax:error', (xhr, status, error) ->
$('.error-msg').show()
console.log event
I wonder if this is actually a Rails issue related to concurrency (due to the line here), that ignores possible multi-threaded invocations for IO.popen
)...
For now, consider using Iodine in a single thread mode:
$ iodine -t 1
I hope this helps as a workaround.
P.S.
you can still use multi-processes. i.e.:
$ iodine -t 1 -w 8
This is similar to the Unicorn model, but isn't as performant as a hybrid approach (such as the one used by Puma, Iodine and Passenger).
EDIT
I discovered there's an open issue on the Rails/execjs repo regarding the possible concurrency flaw, but I'm not sure this is the root cause. I'll keep hunting.
Update:
I managed to replicate the issue when using a number of threads. The issue doesn't occur when running in single thread mode.
Puma (in multi-thread mode) doesn't seem to be affected by the issue... but I can't pinpoint the root cause for this difference.
I couldn't test passenger (where the enterprise edition's architecture is closer to iodine's, with a multi-threaded multi-process hybrid approach, and it's written in C++)... so I'm not sure if it's an iodine only issue.
As a side-note:
It seems that adding coffee-script to the template significantly effects performance. My test application experienced a 50% drop in req/sec speeds (twice as slow) just by adding the coffee-script code to the template.
Update:
I tracked down the issue to the child process reaping and opened an issue with the Rails team.
I will be writing a workaround that disables iodine
's child reaping behavior. Although child reaping should be performed by long running processes (such as web servers), it's interfering with the coffee-script reliance on the IO#close
method (that reaps the child process).
Thank you very much for bringing this to my attention!
I released an updated iodine
version (v.0.4.9) with a patch for the issue.
Thank you for opening this issue. If you have any other questions or concerns, please let me know.