📜 ⬆️ ⬇️

Another way to find out who is logged in to the site using faye + redis

Problem


There was an application using Ruby on Rails, and a standard set of gems (like devise). On one of the pages it was necessary to display information about current active users.

Decision


The first thought was to write the time of this request to the current user for each request and, thus, knowing the session timeout, it was possible to calculate who is active and who is not. But the timeout was about 15 minutes, so if the tab is just closed, it will still be “active” during this time. It was impossible to reduce the session timeout. And the option to update the record in the database every time looked a bit crutch, considering that there were about 2k simultaneous active users. One of the fastest and easiest options is implementation using web redis + redis.

Faye vs WebsocketRails


tldr; As a result, was chosen faye.
')
The initial choice was provided in two ways. Googling the answer to the question what did not give better, therefore all the pros and cons - this is what we managed to dig up from the docks and articles.

I did not find any advantages for websocket-rails, but the disadvantages were obvious: the last update was quite long, a separate stream was opened for each connection, which could potentially lead to our already not very powerful server. Faye, in turn, works through the event machine and is completely asynchronous, plus it is constantly updated.

Installation


Gemfile:

gem "hiredis", "~> 0.4.0" gem 'redis' gem 'faye' gem 'faye-rails' 


Customization


In initializers / redis.rb, the initialization of the connection to redis was added:

 Redis.current = Redis.new(host: 'localhost', port: 6379, driver: :hiredis) 

application.rb

 config.middleware.delete Rack::Lock config.middleware.use FayeRails::Middleware, mount: '/faye', timeout: 25 do map '/active_users' => ActiveUsersController add_extension(Inc.new) end 


In this piece, faye is connected via url '/ faye', and an indication of a timeout, which was very important in solving this problem. As well as channel mapping for a specific handler, in my case it was ActiveUsersController. Also added an extension for faye. Its code looks like this:

 class Inc def incoming(message, _request, callback) if message["channel"] == "/active_users" OnlineUsers.new(message["data"]["id"], message["clientId"]).online! end callback.call(message) end end 

This gave me the opportunity to find out who sent the request to '/ faye'. Inside OnlineUsers it was just adding the id and client_ud (which is given to faye, when connected) of the user in the radish inside the hash, something like:

 redis.hset(HASH_KEY, client_id, user_id) 


so that you can get all the active simply by key hash.

Also in the controller, the monitor made an “unsubscribe” event, which in theory was supposed to work when the tab was closed, but in practice it worked once. It also worked when the user clicked on the logout and, after the click, deleted our client's radish and after a timeout, when nothing was heard from the client.

 channel '/channel_name' do monitor :unsubscribe do remove_online_user(client_id) end end 

At the front was a simple script:

 client = new Faye.Client('/faye'); client.subscribe("/active_users", function(message){}) client.publish('/active_users', {id: user_id}); client.disable('autodisconnect'); 

For faye, a separate thin server was raised that listened only to the port that faye was broadcasting on. Thus, it turned out to make it possible to monitor online users with a delta of 30 seconds.

As a result, to get a list of all online users id is enough

 redis.hgetall(HASH_KEY).values.uniq 

Source: https://habr.com/ru/post/256471/


All Articles