AJAX long polling request

From a client perspective, the AJAX long polling request looks similar to normal one. An important difference between them on the server side is that if the server doesn't have any information available or the client, it holds the request and waits for information to become available or for a suitable timeout, after which a completed response is sent to the client. The long polling requests reduces the amount of data that needs to be sent because the server only sends data when it is really available.

The long pooling request is useful in the following cases:

  • When your solution must work in old web browsers
  • When the data traffic between the client and server is low
  • When the implementation is very simple

The advantages of using the long pooling request are as follows:

  • It works across all browsers
  • It is easy to develop and perfectly fits in the legacy code without significant changes and effort
  • It can detect a connection failure quickly and resume a session to avoid data loss
  • It has a strong immunity against IP address changes for free, because requests are short-lived and their state is stored independently
  • It performs well for most real-time applications and reduces bandwidth consumption and server resources utilization

The disadvantages of using this request are as follows:

  • The client constantly has to establish connections; if the server sends the information back to the client and the connection is closed, it must wait until the client sends the next request to open a new connection
  • The client needs to create an extra connection to send data to the server
  • There are problems with parallel requests
  • The volume of data can make the next update from the server quite excessive if the data comes to the server until then it waits for the new connection from the client

The following browsers are supported by the long pooling request:

  • Chrome 1.0+
  • Firefox 0.6+
  • Opera 8.0+
  • Safari 1.2+
  • Internet Explorer 5.0+

To implement a long-lasting HTTP connection, you need to make changes on the client side and the server side. We introduce the longLasting method to manage requests and register it instead of using processParams:

//…
main() {
  final allUrls = new RegExp('/(.*)'),

  HttpServer.bind(urls.serverAddress, urls.serverPort)
  .then((server) {
    print("Server runs on ${server.address.host}:${server.port}");
    new Router(server)
        ..serve(urls.dataUrl, method: 'GET').listen(longLasting)
        ..serve(allUrls).listen(serveDirectory('', as: '/'))
        ..defaultStream.listen(send404);
  });
}
// Next function was changed to return data only:
String fetchData() {
  return dataGenerator.nextInt(100).toString();
}

The new longLasting function is shown in the following code. It runs two periodic timers. The first one emulates the process based on the request timeout. If no data was collected every 30 seconds, it resets the second timer and response with an empty string. The second one is managing the data availability. If the data is available in a second's interval, it resets the first timer and responds with the collected data:

longLasting(HttpRequest request) {
  Timer reqTimer, dataReadyTimer;

  reqTimer = new Timer.periodic(
    new Duration(seconds:30), (Timer t) {
    dataReadyTimer.cancel();
    processParams(request);
  });

  dataReadyTimer = new Timer.periodic(
    new Duration(seconds:1), (Timer t) {
    if (ready.nextBool()) {
      reqTimer.cancel();
      processQueryParams(request, ready:true);
    }
  });
}

The updated version of the processQueryParams function is illustrated in the following code. This function returns data from fetchData or returns an empty string depending on the value of the ready attribute. The code is as follows:

processQueryParams(HttpRequest request, {bool ready:false}) {
  request.response.write(
      "${request.method}: ${request.uri.path}");
  if (ready) {
    request.response.write(", Data:" + fetchData());
  } else {
    request.response.write(", Data:");
  }
  if (request.uri.queryParameters.length > 0) {
    request.response.write(", Params:" + 
        request.uri.queryParameters.toString());
  }
  request.response.close();
}

Let's take a look at how we change the AJAX polling request example to organize the long polling request at the client side. You can initiate the long polling request by calling the longPolling function as follows:

void main() {
  DivElement log = querySelector("#log");
  int num = 0;
  longPolling(log, num);
}

In this function, we send the AJAX request and wait for the response from the server. When the response is available, we call the responseHandler function and start the new request immediately:

longPolling(DivElement log, int num) {
  String query = "number=${num++}";
  HttpRequest.getString("$url?$query")
    .then((response) {
    responseHandler(log, response);
    longPolling(log, num);
  });
}

Create a Dartium launch configuration, run the server, and open the index.html file in the web browser to see the following result:

GET: /data, Data:12, Params:{number: 0} (51:0)
GET: /data, Data:9, Params:{number: 1} (51:2)
GET: /data, Data:60, Params:{number: 2} (51:3)
GET: /data, Data:41, Params:{number: 3} (51:5)
GET: /data, Data:15, Params:{number: 4} (51:7)
GET: /data, Data:0, Params:{number: 5} (51:8)

For now, we reduced the consumption of bandwidth and server utilization; hence, the clients will mostly receive only valid data.

Server-Sent Events

Another AJAX-based technique is Server-Sent Events (SSE), which is also known as Server Push or HTTP Streaming. In this, the client opens a connection to the server via an initial HTTP request, and the server sends events to the client when there is new information available. So, if the usual functions of your clients are similar to stock tickers or news feeds and they need updates from the server with time, then the SSE technique is the ideal solution for you. By the way, if a client has new information to send to the server, it can send it through a new HTTP request.

SSE is useful in the following cases:

  • The client is oriented towards receiving large volume of data
  • The solution must work in old web browsers

The advantages of SSE are as follows:

  • The server implementation is simple enough
  • A web browser can automatically reconnect to the server
  • The format of exchanging messages is flexible enough
  • The solution is based on one permanent connection to the server
  • The solution well enough for a real-time application
  • Clients don't need to establish a new connection after every response
  • Server solutions can be based on the event loop

The disadvantages of SSE are as follows:

  • It works only from a server to client
  • Internet Explorer doesn't support it
  • It can be blocked by proxy servers
  • It is impossible to connect to the server from another domain

The following browsers are supported:

  • Chrome 6.0+
  • Firefox 6.0+
  • Opera 9.0+
  • Safari 5.0+

The EventSource class is used to receive SSE. This class connects via a specified URL to a server over HTTP and receives server events without closing the connection. The events come in a text/event-stream format. To open a connection to a server and start receiving events, we create a new instance of the EventSource class via a factory constructor and pass the URL of the resource that generates the events through it. Once the connection is established, we wait for the messages.

Note

The URL of the resource that generates the events must match the origin of the calling page.

On the server side, each message is sent as a block of text terminated by a pair of new lines. The text data is encoded with UTF-8. Each message consists of one or more lines of text listing the fields for that message. Each field is represented by the field name, followed by a colon and the text data for that field's value. Here is an example of data-only messages:

; this is a comment
data: some text

data: another text

In the preceding code, the first line is just a comment. All the text messages starting with a colon character are comments. A comment could be useful as Keep-Alive if the messages are not sent regularly. The second and the third line contains just a data field with a text value. The third line contains an extra new line character terminating a message. Several events could be sent via a message. Each event has a name specified in the event field and data field whose values are any string. Data could also be in a JSON format as shown in the following code:

event: userLogon
data: {"username": "John", "time": "01:22:45", "text": "Hello World"}

event: userMessage
data: "Any data"

You can use unnamed events in messages without the event field. The SSE implementation uses message as a name for unnamed events. Each event might have an id. In this scenario, data can be combined as follows:

id: 123
data: some text
data: {"text": "Another text}

Let's take a look at the server side of our example:

      //…
main() {
  final allUrls = new RegExp('/(.*)'),

  HttpServer.bind(urls.serverAddress, urls.serverPort)
  .then((server) {
    print("Server runs on ${server.address.host}:${server.port}");
    new Router(server)
        ..serve(urls.dataUrl, method: 'GET').listen(processSSE)
        ..serve(allUrls).listen(serveDirectory('', as: '/'))
        ..defaultStream.listen(send404);
  });
}

String fetchData() {
  return dataGenerator.nextInt(100).toString();
}

The EventSource class instance, which is created on the client side, opens a connection that submits the HTTP request with the text/event-stream value in the accept header. This is a signal for the server to start the SSE communication. In our example, we send the logon event first. The server connection remains open to the client so we can send message events periodically, as shown in the following code:

processSSE(HttpRequest request) {
  if (request.headers.value(HttpHeaders.ACCEPT) == EVENT_STREAM) {
    writeHead(request.response);
    int num = 0;
    
    new Timer.periodic(new Duration(seconds:5), (Timer t) {
      sendMessageEvent(request.response, num++);
    });
    
    sendLogonEvent(request.response);
  }
}

To allow you to send the push events, the output buffering in HttpResponse must be disabled. In addition to this, you need to specify content type, cache control, and the type of connection via the header attributes, as follows:

writeHead(HttpResponse response) {
  response.bufferOutput = false;
  response.headers.set(HttpHeaders.CONTENT_TYPE, EVENT_STREAM);
  response.headers.set(HttpHeaders.CACHE_CONTROL, 'no-cache'),
  response.headers.set(HttpHeaders.CONNECTION, "keep-alive");
}

To send a message, you can use the writeln method of response. It automatically assigns to a newline character to each string, so you need to add only one newline character at the end of your event. Finally, the flush method of the response pushes the event to the client, as shown here:

sendMessageEvent(HttpResponse response, int num) {
  print("Send Message event $num");
  response.writeln('id: 123'),
  response.writeln('data: {"msg": "hello world", "num": $num, "value": ${fetchData()}}
'),
  response.flush();
}

For the logon message, we create a custom-defined userLogon event type. If the connection opened via the EventSource terminates, the web browser will automatically re-establish the connection to the server after three seconds. You can change this value via the retry property of the event. This value must be an integer that specifies the reconnection time in milliseconds:

sendLogonEvent(HttpResponse response) {
  print("Send Logon event");
  response.writeln('event: userlogon'),
  response.writeln('retry: 15000'),
  response.writeln('id: 123'),
  response.writeln('data: {"username": "John", "role": "admin"}
'),
  response.flush();
}

The SSE code implementation at the client side is as follows:

import 'dart:html';
import 'urls.dart' as urls;

var url = "http://${urls.serverAddress}:${urls.serverPort}${urls.dataUrl}";
String get timeStamp {
  DateTime dt = new DateTime.now();
  return " (${dt.minute}:${dt.second})";
}

responseHandler(DivElement log, String data) {
  DivElement item = new DivElement();
  item.text = data + timeStamp;
  log.insertAdjacentElement('beforebegin', item);
}

In the preceding code, we created an instance of EventSource with the specified URL. From now, we will start listening to the events from the server. We add the open and error event listeners. The EventSource class informs the code about the closed connection from the server side via changes in the readyState property. The message listener handles all the unknown events. A special event listener for the userlogon event handles the sort of events that could be added via the addEventListener method of the EventSource class. The event keeps the message information in the data property. An event identifier assigned on the server side is available via the lastEventId property, as shown in the following code:

main() {
  DivElement log = querySelector("#log");
  EventSource sse = new EventSource(url);
  sse.onOpen.listen((Event e) {
    responseHandler(log, "Connected to server: ${url}");
  });

  sse.onError.listen((Event e) {
    if (sse.readyState == EventSource.CLOSED) {
      responseHandler(log, "Connection closed");
    } else {
      responseHandler(log, "Error: ${e}");
    }
  });
  sse.onMessage.listen((MessageEvent e) {
    responseHandler(log, 
      "Event ${e.lastEventId}: ${e.data}");
  });
  sse.addEventListener("userlogon", (Event e) {
    responseHandler(log, 
      "User Logon: ${(e as MessageEvent).data}");
  }, false);
}

Now, create a Dartium launcher, run the server, and open index.html in the web browser to see the following result:

Connected to server: http://localhost:8080/data (43:33)
User Logon: {"username": "John", "role": "admin"} (43:33)
Event 123: {"msg": "hello world", "num": 0, "value": 45} (43:38)
Event 123: {"msg": "hello world", "num": 1, "value": 1} (43:43)
Event 123: {"msg": "hello world", "num": 2, "value": 65} (43:48)
Event 123: {"msg": "hello world", "num": 3, "value": 10} (43:53)
//…

Let's take a look at the server log to see the server-generated messages:

Server runs on 127.0.0.1:8080
Send Logon event
Send Message event 0
Send Message event 1
Send Message event 2
Send Message event 3
//…
..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
3.138.179.100