Manually invoking stream action in server sent event - ruby-on-rails

How do I send update notifications to clients using server sent event?
What I want to accomplish is that when a client ajax calls an action, the server then would send relevant data to all connecting clients through my stream action.
I'm trying to know if this would be possible without websockets or pub/subs.

From what I can gather, you're looking for a generalized approach, rather than specific code?
--
SSE's
Server Sent Events are an HTML5 technology, meaning that if you do it correctly, it shouldn't matter whether you use Rails or another framework -- they should just work
One drawback to SSE's is they act very similar to Ajax long-polling, meaning they send constant "pings" / requests to your server, relaying back any response they find. And they'll still use the pub/sub pattern too
-
Simply, SSE's are when you have a Javascript "event listener", which will listen to an "endpoint" (URL). The endpoint, in the case of Rails, will be a controller#action, from which you can send the relevant text/event-stream updates, which is what ActionController::Live::SSE is there to do
--
Setup
#config/routes.rb
resources :your_controller do
collection do
get :endpoint
end
end
#app/assets/javascripts/application.js
var source = new EventSource('your_controller/endpoint');
source.addEventListener('message', function(e) {
console.log(e.data);
}, false);
#app/controllers/your_controller.rb
Class YourController < ActionController::Base
include ActionController::Live
def endpoint
response.headers['Content-Type'] = 'text/event-stream'
sse = SSE.new(response.stream, retry: 300, event: "event-name")
sse.write({ name: 'John'})
ensure
sse.close
end
end
This will send the relevant updates for you every time

Related

Rails ActionCable and Ember CLI app - Resource Bottlenecks

We've successfully implemented real time updates in our app using ActionCable in Rails and implemented the consumer as a client service in Ember CLI, but am looking for a better, less-expensive approach.
app/models/myobj.rb
has_many :child_objs
def after_commit
ActionCable.server.broadcast("obj_#{self.id}", model: "myobj", id: self.id)
self.child_objs.update_all foo: bar
end
app/models/child_obj.rb
belongs_to :myobj
def change_job
self.job = 'foo'
self.save
ActionCable.server.broadcast("obj_#{self.myobj.id}", model: "child_obj", id: self.id)
end
frontend/app/services/stream.js
Here we're taking the model and id data from the broadcast and using it to reload from the server.
import Ember from 'ember';
export default Ember.Service.extend({
store: Ember.inject.service(),
subscribe(visitId) {
let store = this.get("store")
MyActionCable.cable.subscriptions.create(
{channel: "ObjChannel", id: objId}, {
received(data) {
store.findRecord(data.model, data.id, {reload: true});
}
}
);
},
});
This approach "works" but feels naïve and is resource intensive, hitting our server again for each update, which requires re-authenticating the request, grabbing data from the database, re-serializing the object (which could have additional database pulls), and sending it across the wire. This does in fact cause pool and throttling issues if the number of requests are high.
I'm thinking we could potentially send the model, id, and changeset (self.changes) in the Rails broadcast, and have the Ember side handle setting the appropriate model properties. Is this the correct approach, or is there something else anyone recommends?
You should be fine with sending whole entity payload with your change event via sockets. Later you can push payload to the store - create new records or update existing. This way you'll avoid additional server requests.

How to receive data in ActionCable Channel without JS?

I'm writing a Rails application that uses WebSockets to communicate with other machines (no browser and client side logic in this process). I have a channel:
class MachinesChannel < ApplicationCable::Channel
def subscribed
...
end
def unsubscribed
...
end
def handle_messages
...
end
end
To receive the data the only way I know about is the JavaScript client:
ActionCable.createConsumer('/cable').subscriptions.create 'MachinesChannel',
received: (message) ->
#perform('handle_messages')
I can call server side methods from JS via #perform() method.
Is there any way to omit the JS part and somehow directly handle the incoming data in MachinesChannel?
The ideal situation would be to have the handle_messages method accept a data argument and have this metod called on incoming data.
After looking into ActionCable source code I got the following solution. You just have to create a method in MachinesChannel that you want to be called, e.g. handle_messages(data). Then, in the client that connects to your websocket, you need to send a message in the following format (example in ruby):
id = { channel: 'MachinesChannel' }
ws = WebSocket::Client::Simple.connect(url)
ws.send(JSON.generate(command: 'message', identifier: JSON.generate(id), data: JSON.generate(action: 'handle_messages', foo: 'bar', biz: 'baz')))
action has to be the name of the method you want to be called in MachinesChannel. The rest of key-values are whatever you want. This the date you can receive in the ActionCable channel.
Recently a gem action_cable_client has been release which seems exactly perfect for this kind of usage. I haven't used it, so I don't know how it really works.
Instead of:
def handle_messages
...
end
This works for me:
def receive(data)
puts data
...
end

The stratigy of build a talk-to-talk system using em-websocket in rails?

Maybe it is a good example for server push system. There are many users in the system, and users can talk with each other. It can be accomplished like this: one user sends message(through websocket) to the server, then the server forward the message to the other user. The key is to find the binding between the ws(websocket object) and the user. The example code like below:
EM.run {
EM::WebSocket.run(:host => "0.0.0.0", :port => 8080, :debug => false) do |ws|
ws.onopen { |handshake|
# extract the user id from handshake and store the binding between user and ws
}
ws.onmessage { |msg|
# extract the text and receiver id from msg
# extract the ws_receiver from the binding
ws_receiver.send(text)
}
end
}
I want to figure out following issues:
The ws object can be serialized so it can be stored into disk or database? Otherwise I can only store the binding into memory.
What the differences between em-websocket and websocket-rails?
Which gem do you recommend for websocket?
You're approaching a use case that websockets are pretty good for, so you're on the right track.
You could serialize the ws object with Marshal, but think of websocket objects as being a bit like http request objects in that they are abstractions for a type of communication. You are probably best off marshaling/storing the data.
em-websocket is a lower(ish) lever websocket library built more or less directly on web-machine. websocket-rails is a higher level abstraction on websockets, with a lot of nice tools built in and pretty ok docs. It is built on top of faye-websocket-rails which is itself built on web machine. *Note, action cable which is the new websocket library for Rails 5 is built on faye.
I've use websocket-rails in the past and rather like it. It will take care of a lot for you. However, if you can use Rails 5 and Action Cable, do that, its the future.
The following is in addition to Chase Gilliam's succinct answer which included references to em-websocket, websocket-rails (which hadn't been maintained in a long while), faye-websocket-rails and ActionCable.
I would recommend the Plezi framework. It works both as an independent application framework as well as a Rails Websocket enhancement.
I would consider the following points as well:
do you need the message to persist between connections (i.e. if the other user if offline, should the message wait in a "message box"? for how long should the message wait?)...?
Do you wish to preserve message history?
These points would help yo decide if to use a persistent storage (i.e. a database) for the messages or not.
i.e., to use Plezi with Rails, create an init_plezi.rb in your application's config/initializers folder. use (as an example) the following code:
class ChatDemo
# use JSON events instead of raw websockets
#auto_dispatch = true
protected #protected functions are hidden from regular Http requests
def auth msg
#user = User.auth_token(msg['token'])
return close unless #user
# creates a websocket "mailbox" that will remain open for 9 hours.
register_as #user.id, lifetime: 60*60*9, max_connections: 5
end
def chat msg, received = false
unless #user # require authentication first
close
return false
end
if received
# this is only true when we sent the message
# using the `broadcast` or `notify` methods
write msg # writes to the client websocket
end
msg['from'] = #user.id
msg['time'] = Plezi.time # an existing time object
unless msg['to'] && registered?(msg['to'])
# send an error message event
return {event: :err, data: 'No recipient or recipient invalid'}.to_json
end
# everything was good, let's send the message and inform
# this will invoke the `chat` event on the other websocket
# notice the `true` is setting the `received` flag.
notify msg['to'], :chat, msg, true
# returning a String will send it to the client
# when using the auto-dispatch feature
{event: 'message_sent', msg: msg}.to_json
end
end
# remember our route for websocket connections.
route '/ws_chat', ChatDemo
# a route to the Javascript client (optional)
route '/ws/client.js', :client
Plezi sets up it's own server (Iodine, a Ruby server), so remember to remove from your application any references to puma, thin or any other custom server.
On the client side you might want to use the Javascript helper provided by Plezi (it's optional)... add:
<script src='/es/client.js' />
<script>
TOKEN = <%= #user.token %>;
c = new PleziClient(PleziClient.origin + "/ws_chat") // the client helper
c.log_events = true // debug
c.chat = function(event) {
// do what you need to print a received message to the screen
// `event` is the JSON data. i.e.: event.event == 'chat'
}
c.error = function(event) {
// do what you need to print a received message to the screen
alert(event.data);
}
c.message_sent = function(event) {
// invoked after the message was sent
}
// authenticate once connection is established
c.onopen = function(event) {
c.emit({event: 'auth', token: TOKEN});
}
// // to send a chat message:
// c.emit{event: 'chat', to: 8, data: "my chat message"}
</script>
I didn't test the actual message code because it's just a skeleton and also it requires a Rails app with a User model and a token that I didn't want to edit just to answer a question (no offense).

Can I delay HTTP response in rails?

I'm new in Rails 4. I want to delay HTTP response.
I thought the 'respond_to' method is enable to respond to HTTP request.
However, When I removed the 'respond_to' method, The rails controller automatically responded to request.
Below is my step for this.
Send HTTP Request in view
[index.html.erb]
<script>
var ready = function() {
alert('this is function');
}
var aquery = function() {
$.ajax({
type : "POST",
url : "app/subaction",
});
}
$(document).on('ready page:load', aquery);
</script>
Receive HTTP Request in Controller
class AppController < ApplicationController
def subaction
(Nothing here...)
end
end
subaction.js.erb
$('#div_id').empty().append("Complete response ...");
In this step, response was executed automatically although there is not "respond_to" method.
Can I delay the response ??? Can you explain request-response operation in rails ?
Thank you...
The main reason Rails renders a response by default is because the documentation says so. Rails follows a philosophy of 'convention over configuration', which means it tries to help you out in ways that keep the code you write to a minimum. Most of the time you want your controller actions to render a view, so Rails does that for you.
You can use a number of techniques to delay responses. The simplest is to use Ruby's sleep method to introduce a delay:
class AppController < ApplicationController
def subaction
sleep 3 # Wait for 3 seconds before responding
end
end
This might be useful when testing how your app behaves over a slow internet connection, but should probably be avoided in production code. Fast apps make happy users.
You could also use the ActionController::Live module, introduced in Rails 4.0.2. It allows you to stream data to the client, but consuming the stream can be tricky. jQuery waits for the response to complete before firing callbacks, so you'll have to use something else to process the stream.
This is similar to Websockets, an emerging streaming standard. There's some support available for websockets in Rails, but it's not universally supported by browsers.
Another alternative is to switch the delay to the frontend. You can use JavaScript's setTimeout or setInterval to call some code after a delay:
setTimeout(function() {
alert("I run once, after 4 seconds");
}, 4000);
setInterval(function() {
alert("I run every two seconds");
}, 2000);
If you're trying to check for updates, you might be tempted to use setInterval, but you may find it more flexible to use setTimeout to schedule a one-off check of the server. You can then include a time delay from the server which specifies how long to wait before asking again.

Rails: sleep until there is data to respond with (streaming + multithreading)

I am building a Rails/Javascript application which is supposed to support real-time notifications. JavaScript is:
var chat;
$(document).ready(function(){
chat = $('#chat');
chat.append('mmm');
(function poll(){
$.ajax({ url: "http://localhost:3000/get_messages", success: function(data){
//Update your dashboard gauge
chat.append(data);
}, dataType: "json", complete: poll, timeout: 30000 });
})();
});
The route:
match 'get_messages', to: 'real_time_notifs#get_messages', :via => :get
Here is the controller's method:
def get_messages
# sleep ??? will it stop the whole application?
render :json => ['message body']
end
I want that JavaScript will receive an answer only if there is something to display (for example, new message appeared in database table) without making a whole application to stop. Could you suggest how to organize get_messages method?
I need the solution which will not block the rest of application while waiting.
There are a number of ways to achieve this
Although I don't have huge experience, you should be thinking about it from another perspective (not just sending Ajax poll requests):
SSE's (Server Sent Events)
I'd recommend you use SSE's
The sent updates are not in the usual HTTP scope (uses its own mime type -- text/event-stream), which I believe means they are completely asynchronous (doesn't matter what you're doing in the app etc)
SSE's are basically done through the front-end by deploying a JS listener. This polls the server for any updates, but unlike Ajax, only listens for the text/event-stream mime):
var source = new EventSource("demo_sse.php");
source.onmessage = function(event) {
alert(event.data);
};
The efficient part is that you can then update this with ActionController::Live::SSE in Rails. I don't have any experience with this, but it basically allows you to send updates via the text/event-stream mime type
WebSockets
Websockets basically open a perpetual connection with your server, allowing you to receive content above the normal HTTP scope
My experience does not extend to "native" websockets (we've successfully used Pusher, and are working on our own websock implementation); but I can say that it's basically a more in-depth version of SSE's
You'll have to use JS to authenticate the client-server connection, and once connected, the browser will listen for updates. I'm not sure about the mime-type for this, but reading up on ActionController::Live will give you some insight into how it works
Either one of these methods will do as you need (only send / receive updates as they are available)

Resources