WebSockets: Can connect but cannot subscribe to channel - ruby-on-rails

I have a rails API-only app running in nginx with passenger. I'm trying to use Action Cable to set up a simple chat application in Flutter but I can't seem to get it to work correctly.
I am able to connect using the following:
channel = IOWebSocketChannel.connect("wss://my.domain/cable")
and see the following in my production.log when that happens:
Started GET "/cable" for [redacted] at 2019-03-14 19:44:09 +0000
Started GET "/cable" [WebSocket] for [redacted] at 2019-03-14 19:44:09 +0000
Successfully upgraded to WebSocket (REQUEST_METHOD: GET, HTTP_CONNECTION: upgrade, HTTP_UPGRADE: websocket)
Doorkeeper::AccessToken Load (0.3ms) SELECT `oauth_access_tokens`.* FROM `oauth_access_tokens` WHERE `oauth_access_tokens`.`token` = '[redacted]' LIMIT 1
User Load (0.2ms) SELECT `users`.* FROM `users` WHERE `users`.`id` = 2 LIMIT 1
Registered connection (Z2lkOi8vYWdlbnQyNHg3L1VzZXIvMg)
So the connection seems to be made and successfully upgraded
I am then sending a subscribe command
channel.sink.add(json.encode({
"command": "subscribe",
"identifier": {"channel": "ConversationChannel", "conversation_id": "${conversation.id}"}
}));
I'm not sure if I should be seeing anything in the log files after this command is executed, but I don't. Nothing happens and there is no indication of any error in my production.log or my nginx error log. I set up some logging in the subscribe method of the ConversationChannel so it would at least output something to the production log when it is called, so I could test if it's at least even calling the subscribe command
class ConversationChannel < ApplicationCable::Channel
def subscribed
Rails.logger.info "test conversation subscribe"
reject && return unless current_user
stream_from "conversation_#{params[:conversation_id]}_channel"
end
...
end
but this does not appear in production.log so it would seem that the subscription command is not getting to where it is supposed to go.
I then tried using dart WebSocket using the code located here, which came from a conversation on github regarding connection states, to see if I could determine if the connection was coming back okay.
When I run that code with my domain I see the same successful connection in the production.log, but the connection actually times out with onerror TimeoutException after 0:00:15.000000: Future not completed which seems to indicate that the connection actually doesn't succeed after all, despite what it says in the log file.
What am I doing wrong here?

If I remember, when you're attempting to connect to a channel, your identifier has to be JSON stringified, as well as your whole JSON object. Take a look at this link:
https://thoughtbot.com/blog/talking-to-actioncable-without-rails
In particular:
const msg = {
command: 'subscribe',
identifier: JSON.stringify({
channel: 'SomeChannel',
}),
};
socket.send(JSON.stringify(msg));
Rails does the whole JSON parsing in the backend. See if this helps you out. I've had similar issues like this before, when attempting to connect the WebSockets, without the usage of their ActionCable npm library.

Related

WebSocket connection failed: Error during WebSocket handshake: Sent non-empty 'Sec-WebSocket-Protocol' header

I am setting up a chat feature using ActionCable in a Rails API / React project, and trying to figure out the best solution to authenticate connections.
The project uses the devise-jwt gem, so my Authorization is passed as a header. Since Web Sockets does not allow headers, I've been searching for the best alternative. Solutions I have found suggest one of the following (with various caveats):
sending the JWT as a query parameter in the url
hijacking the HTTP_SEC_WEBSOCKET_PROTOCOL & sending it that way
Neither of these solutions feel particular nice, but I've tried to go with the second because sending sensitive information in the url (even an encoded JWT) just feels wrong.
Here is the code in my connection.rb:
module ApplicationCable
class Connection < ActionCable::Connection::Base
identified_by :current_user
def connect
self.current_user = find_verified_user
end
private
def find_verified_user
begin
jwt = request.env['HTTP_SEC_WEBSOCKET_PROTOCOL']
decoded_jwt = JWT.decode(jwt, Rails.application.credentials.devise_jwt_secret_key)[0]
id = decoded_jwt['sub']
case decoded_jwt['scp']
when 'admin'
Admin.where(id: id).first
when 'hero'
Hero.where(id: id).first
when'villain'
Villain.where(id: id).first
else
reject_unauthorized_connection
end
rescue
reject_unauthorized_connection
end
end
end
end
I have three different User models: Admin, (and let's call them) Hero & Villain, because reasons.
Now, my actual probelm: In the server I can get the JWT, decode it, and find my Admin, but in the chrome console when I try to create the socket I get: WebSocket connection to 'ws://localhost:3000/cable' failed: Error during WebSocket handshake: Sent non-empty 'Sec-WebSocket-Protocol' header but no response was received
That is in response to running the following code in the chrome console:
token = "jwt.etc.blah..."
socket = new WebSocket(url, [token]);
The server itself seems alright with things:
Started GET "/cable" for ::1 at 2020-07-22 18:26:02 +0100
Started GET "/cable/" [WebSocket] for ::1 at 2020-07-22 18:26:02 +0100
Successfully upgraded to WebSocket (REQUEST_METHOD: GET, HTTP_CONNECTION: Upgrade, HTTP_UPGRADE: websocket)
Finished "/cable/" [WebSocket] for ::1 at 2020-07-22 18:26:02 +0100
Admin Load (0.2ms) SELECT "admins".* FROM "admins" WHERE "admins"."id" = $1 ORDER BY "admins"."id" ASC LIMIT $2 [["id", 1], ["LIMIT", 1]]
↳ app/channels/application_cable/connection.rb:19:in `find_verified_user'
Registered connection (Z2lkOi8vYXBpNTUtbWFpcy9BZG1pbi8x)
The error seems to be telling that I need some kind of confirmation or information about the extra protocol that I've hackily snuck my JWT into... but I've no idea how to go about doing this.
Any help would be greatly appreciated! Is it even possible to send additional protocols with rails ActionCable? Or am I going to have to resort to using a query param? Or is there an even neater way that I can tackle this problem?
First time I've tried anything with web sockets, so thanks in advance for help!
Passing credentials via Sec-WebSocket-Protocol is a common workaround for WebSocket authentication for web based applications since browsers do not support sending custom headers while connection initiation.
If you are passing a value via Sec-WebSocket-Protocol header, your server has to return the same header/value pair back to the client.
Since Sec-WebSocket-Protocol header is not intended for passing credentials to the WebSocket server, you have to play by its rules and treat it like a protocol and fulfill its requirements.

Rails: Microsoft Graph Webhook - 400 Subscription Validation Request Timed Out

I am having some trouble working with the MicrosoftGraph Webhook. I think that I am not properly returning HTTP response, because when I put a binding.pry into my controller, after making the request it hits the controller.
However, the request seems to timeout before hitting my controller (see stack trace below). Here is my controller code and subscription request code, and stack trace. I really appreciate the help.
def self.create_subscription(user)
callback = Proc.new do |r|
r.headers['Authorization'] = "Bearer #{user.outlook_token.access_token}"
r.headers['Content-Type'] = 'application/json'
r.headers['X-AnchorMailbox'] = user.email
end
path = 'subscriptions'
data = {
changeType: "created, updated",
notificationUrl: "https://my_url/api/watch/outlookNotification",
resource: "me/mailFolders('Inbox')/messages",
expirationDateTime:"2017-08-19T06:23:45.9356913Z",
clientState: "subscription-identifier"
}
graph = MicrosoftGraph.new(base_url: 'https://graph.microsoft.com/v1.0',
cached_metadata_file: File.join(MicrosoftGraph::CACHED_METADATA_DIRECTORY, 'metadata_v1.0.xml'),
&callback)
response = graph.service.post(path, data.to_json)
end
def outlook_webhook
#token = params[:validationToken]
head 200, content_type: "text/plain", content_length: 7
response.body = #token
end
Completed 500 Internal Server Error in 14613ms (ActiveRecord: 478.4ms)
OData::ClientError (400 InvalidRequest: "Subscription validation
request timed out." from
"https://graph.microsoft.com/v1.0/subscriptions"):
app/models/outlook_wrapper.rb:44:in create_subscription'
app/controllers/business/messages_controller.rb:18:inindex'
Rendered
/Users/.rbenv/versions/2.1.2/lib/ruby/gems/2.1.0/gems/actionpack-4.2.4/lib/action_dispatch/middleware/templates/rescues/_source.erb
(7.9ms) Rendered
/Users/.rbenv/versions/2.1.2/lib/ruby/gems/2.1.0/gems/actionpack-4.2.4/lib/action_dispatch/middleware/templates/rescues/_trace.text.erb (1.9ms) Rendered
/Users/.rbenv/versions/2.1.2/lib/ruby/gems/2.1.0/gems/actionpack-4.2.4/lib/action_dispatch/middleware/templates/rescues/_request_and_response.text.erb
(3.8ms) Rendered
/Users/.rbenv/versions/2.1.2/lib/ruby/gems/2.1.0/gems/actionpack-4.2.4/lib/action_dispatch/middleware/templates/rescues/diagnostics.text.erb
(176.2ms)
Started POST
"/api/watch/outlookNotification?validationToken=YTJhMTk5MDQtYjdmOC00ZjYxLWIzOGEtMDczM2FjMTAxZTBj"
for 52.161.110.176 at 2017-08-23 12:29:05 -0400 Processing by
InboundEmailsController#outlook_webhook as HTML Parameters:
{"validationToken"=>"YTJhMTk5MDQtYjdmOC00ZjYxLWIzOGEtMDczM2FjMTAxZTBj"}
Completed 200 OK in 35ms (ActiveRecord: 0.0ms)
A Content-Length of 7 is not large enough to accommodate the validation token.
As far as the request timing out, when testing locally, is notificationUrl a publicly accessible URL? If you're using something like http://localhost/api/watch/outlookNotification, then Microsoft will not know how to access your webhook since localhost is not a publicly accessible host.
Even if you do provide a publicly accessible host, you'll still need to poke a hole in your firewall. My suggestion is to use ngrok when testing locally and use an environment variable to set the host.
Run ngrok http 3000 in a console and it will give you a URL that looks something like https://d83375e5.ngrok.io. While ngrok is running, it will forward HTTP and HTTPS requests from d83375e5.ngrok.io to your computer on port 3000.
If you use a notificationUrl like https://${ENV['WEBHOOK_HOST']}/api/watch/outlookNotification, then in development, set the environment variable WEBHOOK_HOST to the host provided by ngrok. In production, you don't need to run ngrok; just set WEBHOOK_HOST to the publicly accessible host for your server.
I may be a little late to the party, but I ran into this same issue. The development environment in rails blocks multiple requests which is why you are getting a timeout. Since you are waiting for a response from the post while Graph is blasting a request back, the request will be blocked until you receive a response from your first post.
As it seems you have discovered, when running in prod this is not an issue as the production environment settings do not block multiple requests.

Trigger/Subscribe to websocket-rails event from inside a rails runner

I have a Rails Application with websocket-rails gem.
Inside my application there is a Daemon that I launch with rails runner MyDaemon.start
I'm using websocket-rails Synchronization, so my config/initializers/websocket_rails.rb looks like this:
WebsocketRails.setup do |config|
config.log_internal_events = false
config.standalone = false
config.synchronize = true
end
Inside MyDaemon, using synchronization, I can trigger event that will reach both my WebsocketRails::BaseController and my javascript WebSocketRails.
What I'm trying to do is to find a way to bind to events from my MyDaemon.
I've tried to implement a plain WebSocket client using both faye-websocket-ruby and websocket-client-simple, but after banging my head on my keyboard for some time, I figured out that there is some kind of "handshake" process using connection_id from the client_connected message. Basically none of the solutions provided in this other so question works for me.
I need to understand if inside my MyDaemon I can subscribe directly to some WebsocketRails callback, even inside an EventMachine, or how should I implement a Websocket Client in Ruby itself.
My last attempt to have a ruby client can be found in this gist, and this is a sample output:
ruby client.rb ws://localhost:3000/websocket
[:open, {"upgrade"=>"websocket", "connection"=>"Upgrade", "sec-websocket-accept"=>"zNTdGvxFKJeP+1PyGf27T4x2PGo="}]
JSON message is
[["client_connected", {"id"=>nil, "channel"=>nil, "user_id"=>nil, "data"=>{"connection_id"=>"4b7b91001befb160d17b"}, "success"=>nil, "result"=>nil, "token"=>nil, "server_token"=>nil}]]
client id is 4b7b91001befb160d17b
[:message, "[[\"client_connected\",{\"id\":null,\"channel\":null,\"user_id\":null,\"data\":{\"connection_id\":\"4b7b91001befb160d17b\"},\"success\":null,\"result\":null,\"token\":null,\"server_token\":null}]]"]
JSON message is
[["websocket_rails.ping", {"id"=>nil, "channel"=>nil, "user_id"=>nil, "data"=>{}, "success"=>nil, "result"=>nil, "token"=>nil, "server_token"=>nil}]]
Sending ["pong",{}]
[:message, "[[\"websocket_rails.ping\",{\"id\":null,\"channel\":null,\"user_id\":null,\"data\":{},\"success\":null,\"result\":null,\"token\":null,\"server_token\":null}]]"]
[:close, 1006, ""]
While the log of websocket-rails is:
I [2015-06-27 02:08:45.250] [ConnectionManager] Connection opened: #<Connection::2b3dddaf3ec4ed5e3550>
I [2015-06-27 02:08:45.251] [Dispatcher] Started Event: client_connected
I [2015-06-27 02:08:45.251] [Dispatcher] Name: client_connected
I [2015-06-27 02:08:45.251] [Dispatcher] Data: {"connection_id"=>"2b3dddaf3ec4ed5e3550"}
I [2015-06-27 02:08:45.251] [Dispatcher] Connection: #<Connection::2b3dddaf3ec4ed5e3550>
I [2015-06-27 02:08:45.251] [Dispatcher] Event client_connected Finished in 0.000174623 seconds
I [2015-06-27 02:09:05.252] [ConnectionManager] Connection closed: #<Connection::2b3dddaf3ec4ed5e3550>
I [2015-06-27 02:09:05.252] [Dispatcher] Started Event: client_disconnected
I [2015-06-27 02:09:05.252] [Dispatcher] Name: client_disconnected
I [2015-06-27 02:09:05.252] [Dispatcher] Connection: #<Connection::2b3dddaf3ec4ed5e3550>
I [2015-06-27 02:09:05.253] [Dispatcher] Event client_disconnected Finished in 0.000236669 seconds
Probably I'm missing somethig very stupid, so I'm here to ask your help!
You can use Iodine as a websocket client (I'm the author):
require 'iodine/http'
# prevents the Iodine's server from running
Iodine.protocol = :timer
# starts Iodine while the script is still running
Iodine.force_start!
options = {}
options[:on_open] = Proc.new {puts 'Connection Open'; write "Hello World!" }
options[:on_close] = Proc.new {puts 'Connection Closed'}
options[:on_message] = Proc.new {|data| puts "I got: #{data}" }
# connect to an echo server for demo. Use the blocking method:
websocket = Iodine::Http::WebsocketClient.connect "wss://echo.websocket.org/", options
websocket << "sending data"
sleep 0.5
websocket.close
As an aside note, reading around I noticed that the websocket-rails gem isn't being updated all that much. See this question
As an alternative, you can run websockets inside your Rails app by using the Plezi framework (I'm the author).
It's quite easy to use both frameworks at the same time on the same server. This way you can use your Rails model's code inside your Plezi Websocket controller.
Because Plezi will manage the websockets and Rails will probably render the 404 Not Found page, Plezi's routes will take precedence... but as long as your routes don't override each other, you're golden.
Notice that to allow both apps to run together, Plezi will force you to use Iodine server as your Rack server. To avoid this you can use the Placebo API and run Plezi on a different process.
You can read more the framework's README file.

Sending mail in Redmine

I have updated my Remine on servers from 2.3.0 to 2.4.1 and one (and only one of them) stops sending mail. If I was switched it back to 2.3.0 version, all works fine.
I am going to try to debug the code and just wondering which file in source code will be a good starting point to it ?
I have found notified_users(), recipients(), each_notification() in Issue model, but where is a code line which send email ?
Updated:
When I edit issue I get next message on console:
Rendered mailer/_issue.html.erb (2.2ms)
Rendered mailer/issue_edit.html.erb within layouts/mailer (4.8ms)
Sent email "[Redmine - ÐапÑÐ¾Ñ #13757] test" (26ms)
to: mymail#gmcs.ru
Date: Thu, 05 Dec 2013 17:34:07 +0400
....
cG9ydC5nbWNzLnJ1L215L2FjY291bnQ8L2E+PC9wPjwvc3Bhbj4KPC9ib2R5
Pgo8L2h0bWw+Cg==
----==_mimepart_52a080cfa4564_af93f8d53ef7714733eb--
Email delivery error: wrong argument (NilClass)! (Expected kind of OpenSSL::SSL::SSLContext)
(5.9ms) COMMIT
Redirected to http://vm-mecomstracker:90/issues/13757
Completed 302 Found in 574.3ms (ActiveRecord: 33.5ms)
I think you should look at app/models/mailer.
For example notified_users is used here
If update breaks sending emails I suggest to review config files, for example config/configuration.yml.example and check fresh issues on redmine.org

HTTP request during ActiveRecord creation

I am trying to subscribe to a pubsubhubbub (pshb) feed on superfeedr. The way the pshb protocol works is
you Send POST to the hub requesting to subscribe to a feed and provide a callback
hub sends a GET to your callback to verifying your intent to subscribe
you respond saying yes I do want to subscribe
hub responds with subscription verification
I am running the server locally on my dev machine. I have code that can successfully subscribe to a feed, I test it by executing it in the rails console directly. I have now created a Feed ActiveRecord model and want automactially subscribe every time a new Feed record is created. I have added a ActiveRecord callback in the Feed model as such
after_create :subscribe_feed
Now when I create an active record I see the correct HTTP request go out, then there is a long hang (about 5 seconds where nothing happens in the logs), then the response from the pshb server comes in saying my callback could not be reached.
Here are the logs (numbers added by me for reference points)
(1) (0.1ms) begin transaction
(2) SQL (4.4ms) INSERT INTO "feeds" ("created_at", "updated_at", "url") VALUES (?, ?, ?) [["created_at", Fri, 15 Nov 2013 23:08:39 UTC +00:00], ["updated_at", Fri, 15 Nov 2013 23:08:39 UTC +00:00], ["url", "http://push-pub.appspot.com/feed"]]
(3) pshb subscribe parameters: {:headers=>{"Accept"=>"application/json"}, :body=>{"hub.mode"=>"subscribe", "hub.verify"=>"sync", "hub.callback"=>"http://...myserver.../pub_sub/callback", "hub.topic"=>"http://push-pub.appspot.com/feed", "hub.verify_token"=>"superfeedtest", "format"=>"json"}}
(4) # ABOUT 5 SECOND WAIT
(5) Subscribe Response: #<HTTParty::Response:0x7fd2180cd978 parsed_response="Your callback couldn't be reached.\n", #response=#<Net::HTTPUnprocessableEntity 422 Unprocessable Entity readbody=true>, #headers={"server"=>["nginx/0.8.52"], "date"=>["Fri, 15 Nov 2013 23:08:44 GMT"], "content-type"=>["text/plain; charset=utf-8"], "connection"=>["close"], "status"=>["422 Unprocessable Entity"], "x-runtime"=>["10057"], "content-length"=>["35"], "set-cookie"=>["_superfeedr_session=BAh7BzoMdXNlcl9pZGkC%2BIY6D3Nlc3Npb25faWQiJTBiMDExZGYzNzU4Mjk0MTMxNjc4NmE0OTg3MDhlMjJk--85d29756ae4b5ae9464630741372af7656f3894b; path=/; HttpOnly"], "cache-control"=>["no-cache"], "pubsubhubbub-version"=>["0.3"]}>
(6) (7.2ms) commit transaction
Redirected to http://67.180.177.165/feeds/28
Completed 302 Found in 10695ms (ActiveRecord: 11.6ms)
(7) Started GET "/pub_sub/callback?hub.challenge=437c4b3b47aa1dbf4072a2d8abb5c39a&hub.lease_seconds=315360000&hub.mode=subscribe&hub.topic=http%3A%2F%2Fpush-pub.appspot.com%2Ffeed&hub.verify_token=superfeedtest" for 173.255.193.75 at 2013-11-15 15:08:50 -0800
You can see that after the response the commit transaction happens (6), and then the GET from the phsb server asking to verify comes in (7). So the hub can reach my callback, but for some reason the I don't receive the GET from the hub until after it has timed out?
I am new to rails so not sure how things work, but I'm guessing something is going on with the concurrency of requests in the middle of ActiveRecord creation. The subscription works on its own, but not during an ActiveRecord creation as I have set it up in after_create.
What is the correct way to handle HTTP request to 3rd party servers if you want to do it when an ActiveRecord entry is created?
NOTE** in the superfeedr api you can specify the verification intent to happen asynchronously. When I specify it to be async I do get a success response instead, but the subscription still does not seem to be crated on superfeedr's hub. I will contact superfeedr directly about this issue, but in general I want to know how to handle this situation for API's that do not have an async option.
The problem is a well known issue with Rails in the "devlopment" environment which only allows for one single concurrent HTTP request. Since you're executing your after_create :subscribe_feed during one HTTP request, when Superfeedr tries to verify your intent, it will issue a second request to your input during the first one. The verification will hang until the subscription request is done... triggering some kind of deadlock.
The solution is simple: use the hub.verify=async param when subscribing and Superfeedr will first close the subscription request (with a 202 status) and will then issue the verification of intent.
You could also drop the hub.verify param, as it's async by default.

Resources