📜 ⬆️ ⬇️

Pseudo web sockets

Inspired by this article about Socket connections in Web applications, I decided to make a more or less universal module with a user-friendly interface that implements this technology.
In this article, the word "socket" means software interface that provides data exchange between server and client scripts, with the ability of the client to constantly "listen to the port". In other words, as soon as something happens on the server, he can immediately inform the client about it, and vice versa. Of course, in javascript there is no possibility to “listen to ports” and create full-fledged sockets, but then we have matches, electrical tape and plasticine, from which we can make some sort of similarity.
First, I will describe the approximate principle of this system, and then, according to tradition, I will give the code of a primitive chat based on it, with, of course, a link. I would like to see with my own eyes habraeffekt in action. At the end there will be a link to the source repository.


Operating principle


Based on the long poll method. The client module sends a request to the server script, which does not close it in advance for a long time (time depends on the maximum possible time of the script on the server, for example, 20 - 25 seconds). If nothing happened during this time, the script notifies the client about it and stops working. The client, having received such a message, immediately creates a new request. All this continues until some event of interest to the client occurs on the server, the server script immediately starts some predefined your function, which forms the answer to the client in the form of a hash, gives it back, and the answer is immediately sent to the client. At the same time, the client can initiate any event and send information to the server, the rest of the clients “listening” to this port will immediately recognize this event.
The system is implemented as two plug-in modules. One, client, of course, in javascript. The second server is written in perl. The names of the modules themselves, and all the properties and methods exported by them, variables and functions are inspired by, it seemed to me amusing, an analogy-association with a music lover ( ) listening to phonographs in different rooms ("") as soon as a music lover hears that in some The room was replaced with a record - it informs about some of your predetermined function. (all of these synonyms of anology, in spite of my titanic efforts to restore order, chaotically replace each other in the following text and in the comments to the code, so it is worth remembering them at least about :)

Client part:


It connects something like this:
 <script type="text/javascript" src="MWS_meloman.js"></script> 

')
“Listening” is started by the Listen function, it is passed one parameter - a string in the format
< -> : < >
the handler function will start as soon as something arrives at this port, and this “something” will immediately be transferred to it as an object (property: value, ...).
For example,
 meloman.Listen('handler1:5') //  meloman.Listen('handler1:5-7') //    meloman.Listen('handler1:5-7; handler2 : 8-10; handler3 : 11-20'); 


If for some reason you need to stop listening, Listen you must pass an empty string:
 //  meloman.Listen(''); //  : meloman.Listen(); // , ,   :) 


If something the server needs to “know” about, and those who listen to this port, happened to the client, then it “packs” this “something” into the object and passes it to the Change function, specifying the “port” as the first parameter.
For example:
 var some = { 'name' : 'Mr.Smith', 'message' : 'Find Neo'} meloman.Change( 5, some ); 


it is immediately sent to the server and passed to your perl function, which knows what to do with it, in the form of a hash.

There are also several properties-settings:

minReconnectTime - time in milliseconds, which more often than not can send requests to the server,

reconnectTime - time in milliseconds after which a new request is sent to the server,

waitingTimeOut - the maximum waiting time for a response from the server,

waitingTimeOutHandler is a function that handles a situation when, during the waitingTimeOut, there was no response from the server at all,

connectionErrorHandler is a function that handles server errors (if the response from the server is not 200 OK),

routeToChange - the path to the server script to which the object will be passed by the Change function,

routeToListen - the path to the server script that will “listen” to the specified port (s) and send, in which case, information to the client,

ignoreMyChanges - if false, the changes you made to the Change function will be perceived by you as a new event on the server,

of all these settings, only routeToChange and routeToListen are required , the others either have default settings, or they are not important for proper operation.

Server part:



The Patefon.pm module is downloaded, copied somewhere, for example, to ./libs and connected:

 use lib "./.libs"; use Patefon; 


The module exports the & change_the_plate and & listen_the_plate functions and the settings hash % patefons_knobs. (Now the beta version exports a little more (for debugging), but this will be fixed in the next version.)

I recommend making two separate scripts on the server, one for listening, the second for “receiving” information from the client. Although no one would mind if everything is crammed into one script.
The one that “listens to” uses the & listen_the_plate function, it starts without parameters. Before launching, it is necessary to specify handler functions for all necessary ports.

$patefons_knobs{handlers}{< >} = < ->;

For example:
 $patefons_knobs{handlers}{1} = \&handler_1; 


This handler function must be ready to accept the first parameter the number of the room-port in which something has changed, and the second (if it needs it) number of the new playing record. Something with all this (and, perhaps, not only with this) to do, and return the hash, which must be sent to the client.

Before all this, you must specify the path to the folder with "rooms-ports":

 $patefons_knobs{path_to_rooms} = './.rooms/'; 


each folder should have a folder of the same name in this folder, each such folder should have a door file (no matter what is written there, it will completely disappear and 'nothing') it is used to block the “room-port” at the moment something changes the & change_the_plate function (at the moment when something from a client came to the server).
approximate directory scheme ./.rooms:
 ./.rooms/
      ./one/
           ./door
      ./2/
           ./door

etc.

The script that listens uses the & change_the_plate function. Before its launch, you must specify the path to the "port-rooms":
 $patefons_knobs{path_to_rooms} = './.rooms/'; 


and handler functions:
$patefons_knobs{ChngHandlers}{<>} = < ->;

the handler will be passed to the function a reference to the hash that came from the client.

after she does something with him she should return a value that can be interpreted as true, for example 1.

It is also important that all transmitted hashes / objects must be one-dimensional.
 // ,  : var some = { 'name' : 'Mr.Smith', 'message' : 'Find Neo!', 'time' : 'Now!' }; //   : var some = { 'name' : 'Mr.Smith', 'action' : { 'message' : 'Find Neo!', 'time' : 'Now!' } }; 


In the settings hash, in addition to specifying handlers and paths to the “rooms”, you can twist these handles:

patefons_knobs {sample_rate} - time in seconds, after which the state of the port will be polled, by default 1.
patefons_knobs {maxSleeping} - the time in seconds after which the listening script finishes its work by default 20.

There is an array of patefons_knobs {errors} in which all errors are added, the module function that executed without errors returns 1, with errors - 0. This can be used, for example, to record errors in the log. So, for example:

 unless ( &change_the_plate ) { open LOG, ">>log"; $" = "\n"; print LOG qq(ERRORS: @{$patefons_knobs{errors}}); } 


So, below is the promised code for a primitive chat. Note that (without taking into account any whistles in the form of autoscrolling and sound) javascript'a there are only 16 lines (and if the default settings were used, then, in general, 14).

 <!DOCTYPE html> <html> <head> <meta http-equiv="Content-Type" content="text/html; charset=utf-8" /> <title> </title> <link rel="stylesheet" href="chat_style.css" type="text/css" media="screen"/> <script type="text/javascript" src="MWS_meloman.js"></script> <script> var mayScroll = true; meloman.ignoreMyChanges = true; meloman.routeToListen = 'ls.pl'; meloman.reconnectTime = 25000; meloman.routeToChange = 'ch.pl'; meloman.Listen('j3:1'); function j3 ( a ) { var string = ''; for ( var Name in a ) { string += Name + "\t" + a[Name] + "\n" } document.getElementById('chat').innerHTML += a['message']; playsound(); scrollchat(); }; function send_message () { var banderol = {}; banderol['message'] = document.getElementById('message').value; banderol['user'] = document.getElementById('user').value || 'Anonymous'; banderol['color'] = document.getElementById('color').value || '#333333'; if ( /\S+/.test( banderol['message'] ) && banderol['message'].length < 500 ) { meloman.Change( 1, banderol) } document.getElementById('message').value = ''; } function scrollchat () { if ( mayScroll ) document.getElementById('chat').scrollTop = 9999; } function playsound () { if ( document.getElementById('need_sound').checked ) { document.getElementById("snd").volume = 0.4; document.getElementById("snd").play(); } } </script> </head> <body> <div id="settings" > <span class="params">: <input type="text" size="20" id="user" autofocus /></span> <span class="params">: <input type="color" value="#00aa00" id="color" /></span> <span class="params">: <input type="checkbox" id="need_sound" /></span> </div> <div id="cont"> <div id="chat" onmouseover="mayScroll=false;" onmouseout="mayScroll=true"></div> <form onsubmit="send_message();return false;"> <div id="message_cont"> <input id="message" type="text" autocomplete="off" value="" spellcheck="false" /> </div> </form> </div> <audio id="snd"> <source src="beep.ogg" type="audio/ogg; codecs=vorbis"> <source src="beep.mp3" type="audio/mpeg"> </audio> </body> </html> 


And the server part:

"Listening" script:

 #!/usr/bin/perl use strict; use warnings; use lib "./.libs"; use Patefon; $patefons_knobs{handlers}{1} = \&j_1; $patefons_knobs{path_to_rooms} = './.rooms/'; $patefons_knobs{maxSleeping} = 20; listen_the_plate(); sub j_1 { my $new = $_[1]; my $old = ( $new - $_[0] ) < 5 ? $_[0] : ( $new - 5 ); my $unreaden_messages; for ( my $i = ++$old; $i <= $new; $i++ ) { open F, "<utf8", "./general_chat/$i" or next; $unreaden_messages .= <F>; } my %hash = ( 'message' => $unreaden_messages ); return %hash; } 


and “plate changing”:

 #!/usr/bin/perl use strict; use warnings; use lib "./.libs"; use Patefon; $patefons_knobs{path_to_rooms} = './.rooms/'; $patefons_knobs{ChngHandlers}{1} = \&j_1; unless ( &change_the_plate ) { open LOG, ">>log"; $" = "\n"; print LOG qq(ERRORS: @{$patefons_knobs{errors}}); } sub j_1 { my ( $room, $plate, $banderol ) = @_; return 0 if ( ${$banderol}{message} eq '' ); unless ( ${$banderol}{color} =~ /^#[0-9a-f]{3}$|^#[0-9a-f]{6}$/i ) { ${$banderol}{color} = '#FE5590' } # shield < and > for ( ${$banderol}{message}, ${$banderol}{user} ) { s/</</g; s/>/>/g } open F, ">", "./general_chat/$plate"; print F qq(<span class="name" style="color:${$banderol}{color};">${$banderol}{user}</span>: <span class="mess">${$banderol}{message}</span><br />) } 


and the link to the actual chat:
<she was here, but she is no more. you forgive me =)>

In addition to toy chats, these modules can be found, I think, many different applications, I, for example, did all this for a multiplayer admin panel.

This system of “pseudo-web sockets” modules is only beta, still, for sure, very raw, but already quite working. I have not found any bugs yet, I hope for those who will use it :)
Here, the sources are available on bitbucket.org. Use, forge, write bug reports or throw stones. I’m happy to put all the “stones” on and try to tap them somewhere with tape, stick with clay, back up with matches, or glue them with snot.

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


All Articles