I’m trying to configure a Rails 7.1.3/Turbo 2.0.5 app locally to try out broadcasts_refreshes
for a simple model. On the view template that tries to turbo_stream_from
this model, I’m getting websocket errors, indicating that it is trying to connect.
Here are the errors I am getting:
# in the browser, with or without the above config.action_cable.url
WebSocket connection to 'ws://localhost:3000/cable' failed: There was a bad response from the server.
# in the terminal window, while rails server is running
Started GET "/cable" for ::1 at 2024-04-18 01:14:20 -0700
Started GET "/cable" [WebSocket] for ::1 at 2024-04-18 01:14:20 -0700
Successfully upgraded to WebSocket (REQUEST_METHOD: GET, HTTP_CONNECTION: Upgrade, HTTP_UPGRADE: websocket)
WebSocket error occurred: undefined method `write_nonblock' for nil
::1 - - [18/Apr/2024:01:14:20 PDT] "GET /cable HTTP/1.1" -1 0
WebSocket error occurred: undefined method `write_nonblock' for nil
Main questions:
-
Do the above error messages mean the authentication didn’t pass?
-
If Turbo creates its own channels, does it also create its own websocket connections and handle its own auth logic separately? Or does it use my app’s ActionCable connection authentication logic in the
app/channels/application_cable/connection.rb
file? (Myconnection.rb
file is currently empty, because we don’t use ActionCable for anything else.) -
Does Rails start the redis server for ActionCable automatically with
rails s
?
What I tried that doesn’t seem to work:
- Add to
routes.rb
:
mount ActionCable.server => "/cable"
- Add to
config/environments/development.rb
:
config.action_cable.url = "ws://localhost:3000/cable"
config.action_cable.allowed_request_origins = [/http://*/,/https://*/]
App configuration:
- gem
turbo-rails
version 2.0.5 - gem
redis
installed. I have run./bin/rails turbo:install:redis
cable.yml
configured for redis in development, thanks to this answer.
development:
adapter: redis
url: redis://localhost:6379/1
- model declares
broadcasts_refreshes
class FieldSlip < ApplicationRecord
broadcasts_refreshes
- view template has
turbo_stream_from
:
<%= turbo_stream_from(:field_slip, dom_id(field_slip)) %>
<%= render(partial: "field_slips/row",
locals: { field_slip: field_slip }) %>
Everything on the template side seems to be working, Turbo is generating turbo-cable-stream-source
tags, but I notice there is no connected
attribute:
<turbo-cable-stream-source channel="Turbo::StreamsChannel" signed-stream-name="ImZpZWxkX3NsaXBfam9iX3RyYWNrZXI6ZmllbGRfc2xpcF9qb2JfdHJhY2tlcl8xIg==--bcf536df9f893f646adb2ce946d4d8ffa166eb297cca1a5c7fa093c77b0da878"></turbo-cable-stream-source>
Clearly, the websocket is not working. What am I missing?
2
Answers
In my case it was the authentication issue. It is true that Turbo can connect to the websocket without authentication, but if your app requires authentication at that point, the websocket request needs authentication too. In our case, the whole view where the live updates should occur requires login.
Adjusting
app/channels/application_cable/connection.rb
to use the app's authentication logic (from the cookie) made it so the Action Cable request was authenticated the same way.With this working, the
turbo-cable-stream-source
tag in the rendered page shows aconnected
attribute.I also had the same issue. I noticed that not only did it affect the streams, but any Hotwire feature. I also tried all the things you outlined.
What solved it for me was adding the following tag to the head in my
application.html.erb
:<%= turbo_include_tags %>