I try to create connection to cable server and subscribe on channel, but I get error with log:
Started GET "/cable" for 172.20.0.1 at 2017-05-27 08:29:39 +0000
Started GET "/cable/" [WebSocket] for 172.20.0.1 at 2017-05-27 08:29:39 +0000
Successfully upgraded to WebSocket (REQUEST_METHOD: GET, HTTP_CONNECTION: upgrade, HTTP_UPGRADE: websocket)
WebSocket error occurred: wrong number of arguments (given 2, expected 1)
My code:
// order_slots.coffee
jQuery(document).ready ->
//some jquery code that call create_channel function
create_channel = (order_id) ->
App.cable.subscriptions.create {
channel: "OrderSlotsChannel",
order_id: order_id
},
connected: ->
# Called when the subscription is ready for use on the server
disconnected: ->
# Called when the subscription has been terminated by the server
received: (data) ->
# Data received
Specific channel:
//order_slots_channel
class OrderSlotsChannel < ApplicationCable::Channel
def subscribed
stream_from "order_slots_#{params[:order_id]}_channel"
end
def unsubscribed; end
end
And ActionCable connection:
# Be sure to restart your server when you modify this file. Action Cable runs in a loop that does not support auto reloading.
module ApplicationCable
class Connection < ActionCable::Connection::Base
identified_by :current_user
def connect
self.current_user = find_verified_user
logger.add_tags 'ActionCable', current_user.email
end
protected
def find_verified_user
verified_user = env['warden'].user
verified_user || reject_unauthorized_connection
end
end
end
ActionCable::Channel::Base - is just empty. I will appreciate any help. Thanks in advance
I solved this problem. The project used Passenger Phusion as application server and 5.0.x version badly combine with rails 5.1 and action cable. You should update passenger up to 5.1.x
Related
Following the hartle tutorial here: https://www.learnenough.com/action-cable-tutorial#sec-upgrading_to_action_cable
When I get to Step 4, adding ActionCable the chat messages are not transmitted and I get the error:
User Load (0.4ms) SELECT "users".* FROM "users" WHERE "users"."id" IS NULL LIMIT ? [["LIMIT", 1]]
An unauthorized connection attempt was rejected
here are the relevant files:
room_channel.rb:
class RoomChannel < ApplicationCable::Channel
def subscribed
stream_from "room_channel"
end
def unsubscribed
# Any cleanup needed when channel is unsubscribed
end
end
messages controller:
class MessagesController < ApplicationController
before_action :logged_in_user
before_action :get_messages
def index
end
def create
message = current_user.messages.build(message_params)
if message.save
ActionCable.server.broadcast 'room_channel',
message: render_message(message)
message.mentions.each do |mention|
ActionCable.server.broadcast "room_channel_user_# {mention.id}",
mention: true
end
end
end
private
def get_messages
#messages = Message.for_display
#message = current_user.messages.build
end
def message_params
params.require(:message).permit(:content)
end
def render_message(message)
render(partial: 'message', locals: { message: message })
end
end
room.coffee:
App.room = App.cable.subscriptions.create "RoomChannel",
connected: ->
# Called when the subscription is ready for use on the server
disconnected: ->
# Called when the subscription has been terminated by the server
received: (data) ->
# Called when there's incoming data on the websocket for this channel
alert data.content
routes.rb:
Rails.application.routes.draw do
root 'messages#index'
resources :users
resources :messages
get '/login', to: 'sessions#new'
post '/login', to: 'sessions#create'
delete '/logout', to: 'sessions#destroy'
mount ActionCable.server, at: '/cable'
end
The reference branch works fine on my machine, but I can't get my tutorial branch to use AC.
Update:
Skipping down to Section 5 of the tutorial, I added connection.rb, which had been blank in the tutorial's beginning repo as follows:
connection.rb:
module ApplicationCable
class Connection < ActionCable::Connection::Base
include SessionsHelper
identified_by :message_user
def connect
self.message_user = find_verified_user
end
private
def find_verified_user
if logged_in?
current_user
else
reject_unauthorized_connection
end
end
end
end
And broadcasting seems to work in one direction. I have two tabs open. but only one works to broadcast messages. In the other, the console shows this error:
Error: Existing connection must be closed before opening action_cable.self-17ebe4af84895fa064a951f57476799066237d7bb5dc4dc351a8b01cca19cce9.js:231:19
Connection.prototype.open
http://localhost:3000/assets/action_cable.self-17ebe4af84895fa064a951f57476799066237d7bb5dc4dc351a8b01cca19cce9.js:231:19
bind/<
http://localhost:3000/assets/action_cable.self-17ebe4af84895fa064a951f57476799066237d7bb5dc4dc351a8b01cca19cce9.js:201:60
In the logs, with the above connection.rb, the search for null user is gone, showing this:
User Load (0.3ms) SELECT "users".* FROM "users" WHERE "users"."id" = ? LIMIT ? [["id", 1], ["LIMIT", 1]]
Registered connection (Z2lkOi8vY2hhdC1hcHAvVXNlci8x)
RoomChannel is transmitting the subscription confirmation
RoomChannel is streaming from room_channel
Started GET "/cable" for ::1 at 2018-12-29 08:04:31 -0500
Started GET "/cable/" [WebSocket] for ::1 at 2018-12-29 08:04:31 -0500
Successfully upgraded to WebSocket (REQUEST_METHOD: GET, HTTP_CONNECTION: keep-alive, Upgrade, HTTP_UPGRADE: websocket)
I can't register, only login, In the server i see this:
An unauthorized connection attempt was rejected Failed to upgrade to WebSocket (REQUEST_METHOD: GET, HTTP_CONNECTION: keep-alive, Upgrade, HTTP_UPGRADE: websocket)
class Connection < ActionCable::Connection::Base
identified_by :current_user
def connect
self.current_user = find_verified_user
logger.add_tags 'ActionCable', current_user.name
end
protected
def find_verified_user
if verified_user = User.find_by(id: cookies.signed['user.id'])
verified_user
else
reject_unauthorized_connection
end
end
end
my routes
devise_for :users, controllers: {
registrations: 'users/registrations',
:omniauth_callbacks => "users/omniauth_callbacks"
}
mount ActionCable.server => '/cable'
Your connection.rb file probably has a line identified_by :current_user, and your def connect calls some variation of a custom find_verified_user method, which requires valid authentication as customized by your app within the same file. Your connection to ActionCable has thus been rejected when you are trying to create a user, which naturally means you were not making the request with whatever is required for valid authentication and user identification. So I guess the real question is: is there a reason you are trying to register a user through WebSocket?
I'm obviously late providing this answer but I'll share what worked for me. The connection.rb referencing cookies.signed[...] worked when I was connecting with ActionCable directly but would be rejected when trying to use the turbo_stream_from element. Here's the connection.rb that works for both, when using Devise:
module ApplicationCable
class Connection < ActionCable::Connection::Base
identified_by :current_user
def connect
self.current_user = find_verified_user
end
protected
def find_verified_user
env["warden"].user || reject_unauthorized_connection
end
end
end
I have been following along the RabbitMQ Work Queues tutorial for Elixir (Elixir Work Queues) which works quite nicely. On top of that I am now trying to get multiple consumers started and monitored by a Supervisor.
This last portion is proving to be a bit tricky. If I run the below code in 2 separate iex sessions, both are getting & handling messages from RabbitMQ.
Client (consumer)
defmodule MT.Client do
require Logger
#host Application.get_env(:mt, :host)
#username Application.get_env(:mt, :username)
#password Application.get_env(:mt, :password)
#channel Application.get_env(:mt, :channel)
def start_link do
MT.Client.connect
end
def connect do
{:ok, connection} = AMQP.Connection.open(host: #host, username: #username, password: #password)
{:ok, channel} = AMQP.Channel.open(connection)
AMQP.Queue.declare(channel, #channel, durable: true)
AMQP.Basic.qos(channel, prefetch_count: 1)
AMQP.Basic.consume(channel, #channel)
Logger.info "[*] Waiting for messages"
MT.Client.loop(channel)
end
def loop(channel) do
receive do
{:basic_deliver, payload, meta} ->
Logger.info "[x] Received #{payload}"
payload
|> to_char_list
|> Enum.count(fn x -> x == ?. end)
|> Kernel.*(1000)
|> :timer.sleep
Logger.info "[x] Done."
AMQP.Basic.ack(channel, meta.delivery_tag)
MT.Client.loop(channel)
end
end
end
Supervisor
defmodule MT.Client.Supervisor do
use Supervisor
require Logger
#name MTClientSupervisor
def start_link do
Supervisor.start_link(__MODULE__, :ok, name: #name)
end
def init(:ok) do
children = [
worker(MT.Client, [], restart: :transient, id: "MTClient01"),
worker(MT.Client, [], restart: :transient, id: "MTClient02"),
worker(MT.Client, [], restart: :transient, id: "MTClient03")
]
supervise(children, strategy: :one_for_one)
end
end
When running that in an iex session:
iex -S mix
MT.Client.Supervisor.start_link
Following is logged:
08:46:50.746 [info] [*] Waiting for messages
08:46:50.746 [info] [x] Received {"job":"TestMessage","data":{"message":"message........"}}
08:46:58.747 [info] [x] Done.
08:46:58.748 [info] [x] Received {"job":"TestMessage","data":{"message":"last........"}}
08:47:06.749 [info] [x] Done.
So clearly there in only 1 consumer active, which is consuming the messages sequentially.
Running the below in 2 iex sessions:
MT.Client.start_link
I'm not adding the logs here, but in this case I get 2 consumes handling messages at the same time
I am sure that I am simply not grasping the required details for Agent/GenServer/Supervisor. Anyone can point out what needs to be changed to MT.Client & MT.Client.Supervisor above to achieve the idea of having multiple consumers active on the same channel?
Also; I'm been experimenting with spawning a Consumer agent and using the resulting pid in AMQP.Basic.consume(channel, #channel, pid) - but that's failing as well.
I'm integrating Bunny gem for RabbitMQ with Rails, should I start Bunny thread in an initializer that Rails starts with application start or do it in a separate rake task so I can start it in a separate process ?
I think if I'm producing messages only then I need to do it in Rails initializer so it can be used allover the app, but if I'm consuming I should do it in a separate rake task, is this correct ?
You are correct: you should not be consuming from the Rails application itself. The Rails application should be a producer, in which case, an initializer is the correct place to start the Bunny instance.
I essentially have this code in my Rails applications which publish messages to RabbitMQ:
# config/initializers/bunny.rb
MESSAGING_SERVICE = MessagingService.new(ENV.fetch("AMQP_URL"))
MESSAGING_SERVICE.start
# app/controllers/application_controller.rb
class ApplicationController
def messaging_service
MESSAGING_SERVICE
end
end
# app/controllers/uploads_controller.rb
class UploadsController < ApplicationController
def create
# save the model
messaging_service.publish_resize_image_request(model.id)
redirect_to uploads_path
end
end
# lib/messaging_service.rb
class MessagingService
def initialize(amqp_url)
#bunny = Bunny.new(amqp_url)
#bunny.start
at_exit { #bunny.stop }
end
attr_reader :bunny
def publish_resize_image_request(image_id)
resize_image_exchange.publish(image_id.to_s)
end
def resize_image_exchange
#resize_image_exchange ||=
channel.exchange("resize-image", passive: true)
end
def channel
#channel ||= bunny.channel
end
end
For consuming messages, I prefer to start executables without Rake involved. Rake will fork a new process, which will use more memory.
# bin/image-resizer-worker
require "bunny"
bunny = Bunny.new(ENV.fetch("AMQP_URL"))
bunny.start
at_exit { bunny.stop }
channel = bunny.channel
# Tell RabbitMQ to send this worker at most 2 messages at a time
# Else, RabbitMQ will send us as many messages as we can absorb,
# which would be 100% of the queue. If we have multiple worker
# instances, we want to load-balance between each of them.
channel.prefetch(2)
exchange = channel.exchange("resize-image", type: :direct, durable: true)
queue = channel.queue("resize-image", durable: true)
queue.bind(exchange)
queue.subscribe(manual_ack: true, block: true) do |delivery_info, properties, payload|
begin
upload = Upload.find(Integer(payload))
# somehow, resize the image and/or post-process the image
# Tell RabbitMQ we processed the message, in order to not see it again
channel.acknowledge(delivery_info.delivery_tag, false)
rescue ActiveRecord::RecordNotFound => _
STDERR.puts "Model does not exist: #{payload.inspect}"
# If the model is not in the database, we don't want to see this message again
channel.acknowledge(delivery_info.delivery_tag, false)
rescue Errno:ENOSPC => e
STDERR.puts "Ran out of disk space resizing #{payload.inspect}"
# Do NOT ack the message, in order to see it again at a later date
# This worker, or another one on another host, may have free space to
# process the image.
rescue RuntimeError => e
STDERR.puts "Failed to resize #{payload}: #{e.class} - #{e.message}"
# The fallback should probably be to ack the message.
channel.acknowledge(delivery_info.delivery_tag, false)
end
end
Given all that though, you may be better off with pre-built gems and using Rails' abstraction, ActiveJob.
I am writing a tcp proxy with Twisted framework and need a simple client failover. If proxy can not connect to one backend, then connect to next one in the list. I used
reactor.connectTCP(host, port, factory) for proxy till I came to this task, but it does not spit out error if it can not connect. How can I catch, that it can not connect and try other host or should I use some other connection method?
You can use a deferred to do that
class MyClientFactory(ClientFactory):
protocol = ClientProtocol
def __init__(self, request):
self.request = request
self.deferred = defer.Deferred()
def handleReply(self, command, reply):
# Handle the reply
self.deferred.callback(0)
def clientConnectionFailed(self, connector, reason):
self.deferred.errback(reason)
def send(_, host, port, msg):
factory = MyClientFactory(msg)
reactor.connectTCP(host, port, factory)
return factory.deferred
d = Deferred()
d.addErrback(send, host1, port1, msg1)
d.addErrback(send, host2, port2, msg2)
# ...
d.addBoth(lambda _: print "finished")
This will trigger the next errback if the first one fails, otherwise goto the print function.