📜 ⬆️ ⬇️

Long Polling A to Z do it yourself

How to implement long polling using Nginx and Javascript in the network a lot of material. But I haven’t met a complete guide yet. Then there are problems with the compilation of the module under Nginx, then the download icon rotates in the browser with long poll requests. Under the cut, the full material is how to do it right.


Compiling Nginx module under linux


To support long polling connections in the Nginx server, a great nginx-push-stream-module module is implemented. Since it is not included in the official delivery, it needs to be downloaded, configured and compiled with Nginx.

Before this, you must have all the necessary packages installed.
apt-get install git apt-get install make apt-get install g++ apt-get install libpcre3 libpcre3-dev libpcrecpp0 libssl-dev zlib1g-dev 

')
Next you need to download the nginx-push-stream-module, nginx module itself and compile them together.

Clone a project from GIT
 git clone http://github.com/wandenberg/nginx-push-stream-module.git 


Download and unpack the latest nginx
 NGINX_PUSH_STREAM_MODULE_PATH=$PWD/nginx-push-stream-module wget http://nginx.org/download/nginx-1.2.6.tar.gz tar xzvf nginx-1.2.6.tar.gz 


Configure and compile nginx with nginx-push-stream-module
 cd nginx-1.2.6 ./configure --add-module=../nginx-push-stream-module make make install 


If there are no compilation errors, everything is ready. Let us verify that we installed exactly that nginx and that now it really does have a module nginx-push-stream-module
 check: /usr/local/nginx/sbin/nginx -v test configuration: /usr/local/nginx/sbin/nginx -c $NGINX_PUSH_STREAM_MODULE_PATH/misc/nginx.conf -t 


After executing these commands, you should see this:
 nginx version: nginx/1.2.6 the configuration file $NGINX_PUSH_STREAM_MODULE_PATH/misc/nginx.conf syntax is ok configuration file $NGINX_PUSH_STREAM_MODULE_PATH/misc/nginx.conf test is successful 


Configuring Nginx for Long Polling Connections


To configure support for long polling, in the Nginx configuration, you need to register at least two controllers. The first is for subscribers (those who will receive messages), the second for publishing messages (those who will send messages).

Omitting the remaining server settings, the /usr/local/nginx/nginx.conf configuration file should look like this:
 ... http { ... server { listen 80; server_name stream.example.com; charset utf-8; location /pub { push_stream_publisher admin; set $push_stream_channel_id $arg_id; allow 1.1.1.1 # ip     } location ~ /sub/(.*) { push_stream_subscriber long-polling; set $push_stream_channels_path $1; push_stream_last_received_message_tag $arg_tag; push_stream_last_received_message_time $arg_time; push_stream_longpolling_connection_ttl 25s; } } } 


In this example, / pub is the address for posting messages, only your server (1.1.1.1), from which events come, should see it, / sub is the address for subscribers, those to whom messages will be sent. The identifier that will identify the subscribers is passed after / sub, and is taken as the id parameter in / pub.

The very important parameters push_stream_last_received_message_tag and push_stream_last_received_message_time will be discussed below when we touch javascript.

An example for understanding the work:
You can create multiple subscribers by calling: stream.example.com/sub/1 , stream.example.com/sub/2 , stream.example.com/sub/3 . Each of them will “hang” on the Nginx server for 25 seconds (push_stream_longpolling_connection_ttl). If we call the POST request on stream.example.com/pub?id=2 and send the message “Hello” in the body, the subscriber “hanging” on / sub / 2 will receive the answer “Hello”. It is convenient to check this in the Poster plugin for FireFox.

Creating Javascript Subscribers


Most likely, you need to use long polling to update any data in the browser, and for this you will need to write a Javascript client.

I tried different methods, but I chose XMLHttpRequest as the standard. Compared with other methods it has the following advantages:


Let the variable subID - stored a unique value for the subscriber
 var LongPolling = { etag: 0, time: null, init: function () { var $this = this, xhr; if ($this.time === null) { $this.time = $this.dateToUTCString(new Date()); } if (window.XDomainRequest) { //  IE,     (-  IE8) setTimeout(function () { $this.poll_IE($this); }, 2000); } else { //  XMLHttpRequest  mcXHR = xhr = new XMLHttpRequest(); xhr.onreadystatechange = xhr.onload = function () { if (4 === xhr.readyState) { //    if (200 === xhr.status && xhr.responseText.length > 0) { //  Etag  Last-Modified  Header  $this.etag = xhr.getResponseHeader('Etag'); $this.time = xhr.getResponseHeader('Last-Modified'); //    $this.action(xhr.responseText); } if (xhr.status > 0) { //       $this.poll($this, xhr); } } }; //  long polling $this.poll($this, xhr); } }, poll: function ($this, xhr) { var timestamp = (new Date()).getTime(), url = 'http://stream.example.com/sub/' + subID + '?callback=?&v=' + timestamp; // timestamp       xhr.open('GET', url, true); xhr.setRequestHeader("If-None-Match", $this.etag); xhr.setRequestHeader("If-Modified-Since", $this.time); xhr.send(); }, //      poll(),   IE poll_IE: function ($this) { var xhr = new window.XDomainRequest(); var timestamp = (new Date()).getTime(), url = 'http://stream.example.com/sub/' + subID + '?callback=?&v=' + timestamp; xhr.onprogress = function () {}; xhr.onload = function () { $this.action(xhr.responseText); $this.poll_IE($this); }; xhr.onerror = function () { $this.poll_IE($this); }; xhr.open('GET', url, true); xhr.send(); }, action: function (event) { //  ,    -  ... }, valueToTwoDigits: function (value) { return ((value < 10) ? '0' : '') + value; }, //     UTC dateToUTCString: function () { var time = this.valueToTwoDigits(date.getUTCHours()) + ':' + this.valueToTwoDigits(date.getUTCMinutes()) + ':' + this.valueToTwoDigits(date.getUTCSeconds()); return this.days[date.getUTCDay()] + ', ' + this.valueToTwoDigits(date.getUTCDate()) + ' ' + this.months[date.getUTCMonth()] + ' ' + date.getUTCFullYear() + ' ' + time + ' GMT'; } } 


It is important to say about the two parameters etag and time.
 xhr.setRequestHeader("If-None-Match", $this.etag); xhr.setRequestHeader("If-Modified-Since", $this.time); 

Without them, long polling did not always work and messages came through again. These two parameters are needed by the nginx-push-stream-module module to identify messages that the subscriber has not yet received. So for stable work, it is just necessary.

In custody


The method described in this topic is used and successfully works in our comment system Cackle . Every day we have about 20 000 - 30 000 parallel subscribers, and we have never observed any errors in the delivery of messages. For production solutions, this is exactly what you need.

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


All Articles