boazsegev / plezi

Plezi - the Ruby framework for realtime web-apps, websockets and RESTful HTTP

Home Page:www.plezi.io

Geek Repo:Geek Repo

Github PK Tool:Github PK Tool

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

commented

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

commented

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 
commented

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.

commented

@jerryshen ,

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?

commented

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.

commented

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'
commented

@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
commented

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.

commented

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.

commented

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!

commented

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.