This feature will *help* in stopping people from spamming the chat room. It isn't very complex, but it will give you the idea of what rate limiting can do. The algorithm for rate limiting is very simple, if a user sends too many messages in a certain interval, stop them from sending. At the top of chat_room.erl I've added the following:
I've just randomly chosen these values for now, and it means if you try to send more than 10 messages in 3 seconds, you will be rate limited. The check for rate limiting will go in the chat_message function:
We've just added a check that calls should_rate_limit and based on what it returns, the user is rate limited(and we notify them), or they are not, and the message can be sent. Also, you may notice the handle_command function, we will discuss this in a little.
To implement rate limiting, I've updated our client's state with last_msg and msg_count:
Finally, the implementation of should_rate_limit. This function implements our simple algorithm.
This feature allows the administrator exercise some control over the chat server, with features such as kicking/banning users. The admin features are only protected by a password. We define the admin password as so:
It is probably a good idea to change this password if you are running it on a public server.
I have updated the client state to contain another field, called admin. When a user joins, this value defaults to false. To become an admin, (and to change a client's state to admin=true), the user must authenticate. To handle authentication, and various other commands, we will use an IRC style /command param1 param2 param3..paramN. If you want to add spaces for a parameter, surround it with quotes.
You probably noticed in the previous section that we added a handle_command check in the chat message function. This will allow us to parse and handle any possible commands. It looks like the following:
It takes a chat message, and tries to parse out a command. If it is valid, we then process the command further. process_command takes the command (value after the slash), and a list of parameters (along with the client state, and server state). Using Erlang's pattern matching, we can easily handle any command we want by just matching the string. For now, process_command handles two commands, /help, and /auth.
/help just prints out help. All users can access this command. The auth command is what will give us administrator access. Using pattern matching, we can check to see if the correct password was entered. If it was, we update the client state and notify all the other users that this user has became an admin.
Once a user is authenticated, the Generic Admin Handler function will match, and the process_admin_command function will be called.
Now that the user is logged in, we want to add some admin functionality. In this tutorial, we add the following commands:
/admin kick user reason
/admin ban user reason seconds
/admin unban host
/admin info user
Kicking a user
To kick a user, the admin will specify a user, and a reason. The code is:
First, we lookup the user. If the user exists, we *make* them leave the chat room, and specify the reason. That's it.
Banning a user
This is a little more complicated, as we want to persist the ban between server restarts. To do this, we use a DETS table, which is basically ETS(Erlang's in memory database), persisted to disk. Erlang stores the database in a file. We use the file name "bans.dets". The table is created/opened in our chat_room init function:
Each item in the bans_table will be defined by the following record:
Okay, now for the fun part. First, let's see the function to ban a user:
This function looks up the user, and if the user exists, we let them know that they have been banned(and disconnect them) and store an item in the DETS table describing the ban.
Now that the user has been disconnected, we need to make sure they can't login until the ban is up. This check is implemented in our join function.
We have updated this function to call the function, is_banned, which determines if a user is indeed banned. If they are, we return an error, and the message is displayed to the user.
The ban function simply looks up a host/ip address in the bans table, and determines if it is still active.
Finally, we've added a simple check in the join function, called can_connect, which checks to see if a user has the maximum connections allowed open. This is defined as:
The number of connections for a host is stored in an ETS table. We don't need to persist this because if the server crashes, all connections will be gone anyway.
When a user joins, we update the connections table.
When a user leaves, we update the connections table.
Now we have a more functional server, but it still needs lots of work(that's for you to decide). The source is available here. Try it out locally, by doing:
If anyone has any comments/suggestions/requests please let me know, I'd be happy to get some feedback on this. And also, you can try a demo here, although don't think that demopass will get you admin control :).