Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

WebSocket connection to 'ws://localhost:3000/ws' failed: Error during WebSocket handshake: Unexpected response code: 200 #21

Closed
jerryshen opened this issue Sep 22, 2017 · 21 comments
Labels

Comments

@jerryshen
Copy link

could you please have a look at this error, I can't figure it out

@boazsegev
Copy link
Owner

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?

@jerryshen
Copy link
Author

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

@jerryshen
Copy link
Author

I'm using Plezi with rails5 application, following the document from http://www.plezi.io/docs/hello_chat

@boazsegev
Copy link
Owner

boazsegev commented Sep 22, 2017

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 ...

@jerryshen
Copy link
Author

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 

@boazsegev
Copy link
Owner

boazsegev commented Sep 22, 2017

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

@jerryshen
Copy link
Author

hey thanks very much,
this is the source code from github
https://github.com/jerryshen/hg

@jerryshen
Copy link
Author

I think it should be the integration issue with rails, but can not figure it out.

@boazsegev
Copy link
Owner

@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)?

@jerryshen
Copy link
Author

yes, I'm running thin for development

@jerryshen
Copy link
Author

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

@jerryshen
Copy link
Author

is there any way to use unicorn for rails app and iodine for plezi together?

@boazsegev
Copy link
Owner

boazsegev commented Sep 22, 2017

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.

@boazsegev
Copy link
Owner

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.

@jerryshen
Copy link
Author

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'

@boazsegev
Copy link
Owner

@jerryshen , thank you for pointing this out.

I would like to investigate this concern. Which script fails to render?

@jerryshen
Copy link
Author

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

@boazsegev
Copy link
Owner

boazsegev commented Sep 22, 2017

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.

@boazsegev
Copy link
Owner

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.

@boazsegev
Copy link
Owner

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!

@boazsegev
Copy link
Owner

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.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Projects
None yet
Development

No branches or pull requests

2 participants