Server Push and Server Sockets

This section will describe how to send data to Mozilla from other sources.

Server Push

The term 'server push' generally means that a server pushes content to the browser client. In reality, a browser doesn't allow this directly. However, it may be emulated in a number of ways.

All three techniques may be used in Mozilla. The first technique is fairly simple and is described in a number of places that discuss web page development. Just use a meta-tag refresh or load data using the XMLHttpRequest object. The second technique listed above has been improved in Mozilla 1.7 to work with the XMLHttpRequest object, as well as with normal browser page loads. This additional feature is described below, as it is more unique to Mozilla.

Using multipart/x-mixed-replace

When the XMLHttpRequest object is used with the content type 'multipart/x-mixed-replace', the server can send a series of XML documents which are intended to replace each other. That means that when each new document arrives, the load listener will fire again with a new document. What you do with the new document is application-specific, but you might use it to update the user interface. Note that when a new document arrives, the old one doesn't go away if you still have a reference to it. However, you will not be able to access the old document through the XMLHttpRequest object; instead it will provide access to the new document.

You can create and retrieve a multipart remote URL in a similar way as with other URLs, however you will need to set the multipart property so that the XMLHttpRequest object knows to expect a multipart response.

function handleContent(event)
{
  var result = event.target.responseXML;
}
var xrequest = new XMLHttpRequest();
xrequest.multipart = true;
xrequest.open("GET","http://www.xulplanet.com/ndeakin/tests/multipart.php",false);
xrequest.onload = handleContent;
xrequest.send(null);

In this example, we request the URL 'http://www.xulplanet.com/ndeakin/tests/multipart.php'. The multipart property must be set to true for multipart responses. Since each part will arrive in sequence with possibly a delay in-between, in doesn't make sense to read the content synchronously. In fact, an error will occur if you try to. Instead, the data must be handled asynchronously and a callback registered to handle each part when it arrives. This callback is specified using the onload property. Finally, we send the request. It is important to set the properties and call the methods in the right sequence. The multipart property must be set before the call to the open method.

The load listener, handleContent will be called each time a new part arrives. In this function, you would read the data and store it or update the user interface. After returning from this function, the load listener may be called again when the next part is available. Notice that the XMLHttpRequest object is the target of the load event and we can read the resulting document using its responseXML property.

On the server side, you will need to ensure that the content gets sent using the content type 'multipart/x-mixed-replace' and that the content is sent in multiple parts. The details of this is beyond the scope of this document but here is a simple script in PHP which sends two XML documents.

<?php
  header('Content-type: multipart/x-mixed-replace;boundary="rn9012"');
  print "--rn9012\n";
  print "Content-type: application/xml\n\n";
  print "<?xml version='1.0'?>\n";
  print "<content>First Part</content>\n";
  print "--rn9012\n";
  flush();
  sleep(5);
  print "Content-type: application/xml\n\n";
  print "<?xml version='1.0'?>\n";
  print "<content>Second Part</content>\n";
  print "--rn9012--\n";
?>

Each part is separated by the string 'rn9012', which is a random string specified in the boundary in the header. It doesn't matter what the string is as long as it doesn't occur in the content anywhere. The end is signalled with two hyphens after the random string. Once this point is reached, there are no more parts, and the connection closes. In-between the two parts, we flush the output and wait for five seconds before sending the next part. Make sure to flush the output or the script will not send the content first.

Server Sockets

A server socket is a listener which listens for incoming socket connections. The socket will listen on a specific port and respond when a message is sent to that port. The incoming messages may be from the same machine or another machine. Once you have received an input message, you can read it and output a response. This is the same method used by web servers when they receive requests for web pages from browsers.

Socket communication is intended to be lower level than, for example, HTTP. When using sockets, you will have to interpret and handle the data directly. The use of server sockets requires privileged code and you cannot use them in unprivileged web pages.

The server socket, once created, will call a listener whenever a socket connection is made on the desired port. The listener will respond by reading the incoming data and will then output a response. If another connection is made, the listener will be called again. This will continue until the server socket is closed.

Server sockets listen on a specific port which is identified by a number. You can usually use any port number you wish, assuming it isn't already being used for something else. In addition, port numbers less than 1024 are usually only usable by an administrator process of the machine. You can still send data over these lower numbers, but you won't be able to listen to them, unless the machine is configured to allow such access. In the examples below, we will use the port number 7055.

Creating a server socket is fairly simple. We use the nsIServerSocket interface, as shown below:

var serverSocket = Components.classes["@mozilla.org/network/server-socket;1"]
                     .createInstance(Components.interfaces.nsIServerSocket);
serverSocket.init(7055,false,-1);
serverSocket.asyncListen(listener);

Two methods are needed to start the server socket. The init method is used to initialize the server socket. This method takes three arguments. The first argument is the port to listen to. When a connection is made using this port, the server socket will respond. The server socket will not connect on requests through other ports. The second argument indicates whether connections are allowed from any machine or just the same machine. If this is false, any user anywhere in the world may make a connection through this server socket. If this argument is true, connections may only be made by the same machine as the server socket. If you are using C++, you may also use the initWithAddress method to specify a specific IP address. Finally, the third argument to the init method specifies the length of the queue of incoming connections. The socket will only process one at a time. If more connections arrive above this queue size, they will be ignored. You will usually just use -1 for this argument to use a suitable default value.

The asyncListen method is used to start the socket listening for connections. It is passed a listener object which we'll define in a moment. When a connection arrives, the listener will be called. To stop the listening, call the server socket's close method. Closing the server socket does not stop connections that have already been made, it just prevents any new connections.

serverSocket.close();

The listener implements the nsIServerSocketListener interface. This interface has two methods. A simple example is shown below:

var listener =
{
  onSocketAccepted : function(serverSocket, transport)
  {
    var stream = transport.openOutputStream(0,0,0);
    stream.write("OK",2);
    stream.close();
  },
  onStopListening : function(serverSocket, status){}
};

The onSocketAccepted method will be called whenever a new connection is made. That means that if three connections are made, this method will be called three times. They will be called in sequence; not all at the same time. This method takes two arguments, the server socket and the socket transport. The socket transport is the incoming connection and is described in the previous section. While the first argument to the onSocketAccepted method, the server socket, will be the same for every connection, the transport will be unique to each connection. The transport may be used to determine the host name of the connecting client as well as open the input and output streams. The transport implements the nsISocketTransport interface.

In the example above, we open the transport's output stream using the openOutputStream and then write to it using the write method. We also make sure to close the stream when we are done by using the close method. This example is very simple -- it just outputs 'OK' and closes the stream. In reality, we would want to open the input stream first, read the data sent from the client and output a response based on the input. This process is fairly similar to the example from the previous section. In fact, inside the onSocketAccepted method, reading and writing works just like any other socket, except that the server socket manages opening and closing the connections.

The listener also has a onStopListening method which will be called when the server socket is closed for whatever reason. The listener may use this opportunity to perform some clean up. The socket may be closed for a number of reasons, for instance if the client closed the connection, or if an error occured. The reason that the socket was closed is passed as the status argument to onStopListening method. This value will be an error code.

Here is a complete example:

<script>
var serverSocket;
function start()
{
  var listener =
  {
    onSocketAccepted : function(socket, transport)
    {
      try {
        var outputString = "HTTP/1.1 200 OK\n" +
                           "Content-type: text/plain\n\n" +
                           "Hello there " + transport.host + "\n";
        var stream = transport.openOutputStream(0,0,0);
        stream.write(outputString,outputString.length);
        stream.close();
      } catch(ex2){ dump("::"+ex2); }
    },
    onStopListening : function(socket, status){}
  };
  try {
    serverSocket = Components.classes["@mozilla.org/network/server-socket;1"]
                     .createInstance(Components.interfaces.nsIServerSocket);
    serverSocket.init(7055,false,-1);
    serverSocket.asyncListen(listener);
  } catch(ex){ dump(ex); }
  document.getElementById("status").value = "Started";
}
function stop()
{
  if (serverSocket) serverSocket.close();
  document.getElementById("status").value = "Stopped";
}
</script>
<hbox>
  <button label="Start" oncommand="start();"/>
  <button label="Stop" oncommand="stop();"/>
  <label id="status" value="Stopped"/>
</hbox>

This example creates a very simple web server which always responds with the same string 'Hello there <host>', where <host> is the client's hostname. The Start button may used to start the server and the Stop button may be used to stop the server. Once the server has started, you can open a new browser window or tab and go to the URL 'http://localhost:7055'. You should see the socket's message displayed. Naturally, since it's a socket, this should work on a different browser as well. Also, assuming that your network doesn't prevent usage of the port 7055, you will be able to access the message if you open the URL 'http://<your IP address>:7055' from another machine.

Add a note User Contributed Notes
September 30, 2005, 10:17 pm dakellog at yahoonospam dot com
/**
// This shows asynchronous reads and writes, and mimics
// a web server. It listens on port 6669 and waits for
// a 'GET' command from telnet or a browser.
// To test the sample code below I use "telnet localhost 6669".
// Type "GET / HTTP/1.0\n\n" to receive the web page
// or visit http://localhost:6669 in your web browser.
//
**/

startServer();

var serv = null;

// async_listener waits for a connection and
// responds to input from the network.
// The order of action is listen, accept connection, wait
// for 'GET', deliver payload, quit
var async_listener = {
// called after connection established
onSocketAccepted: function(serv, transport) {
try {
var outstream = transport.openOutputStream(0,0,0);
var stream = transport.openInputStream(0,0,0);
var instream = Components.classes["@mozilla.org/scriptableinputstream;1"]
.createInstance(Components.interfaces.nsIScriptableInputStream);
instream.init(stream);
} catch (e) {
alert("Error "+e);
}
var dataListener = {
data : "",
return_count : 0,
found_get : 0,
onStartRequest: function(request, context){ },
onStopRequest: function(request, context, status) {
instream.close();
outstream.close();
listener.finished(this.data);
},
// called when there are new data
onDataAvailable: function(request, context, inputStream, offset, count) {
this.data = instream.read(count);
alert("data received is \""+this.data+"\"");
if(this.data.match(/GET/)) {
this.found_get++;
}
var re = RegExp("[\n\r]{2}");
if(this.data.match(re)) {
this.return_count++;
if(this.return_count > 0 && this.found_get) {
var webpage = "HTTP/1.0 200 OK\nContent-type: text/html\n\n" +
"<html>\n<head>\n<title>Hello mozilla</title>\n</head>\n" +
"<body>\nhello world!<br>\n</body>\n</html>\n";
// Send webpage to browser/telnet
outstream.write(webpage, webpage.length);
instream.close();
outstream.close();
}
}
}
};
// pump takes in data in chunks asynchronously
var pump = Components.
classes["@mozilla.org/network/input-stream-pump;1"].
createInstance(Components.interfaces.nsIInputStreamPump);
pump.init(stream, -1, -1, 0, 0, false);
pump.asyncRead(dataListener,null);
}
};

function startServer() {
try{
alert("Server started.\nTry: \"telnet localhost 6669\"\n\"GET[return]\"");
var sock = Components.classes["@mozilla.org/network/server-socket;1"];
serv = sock.createInstance();
serv = serv.QueryInterface(Components.interfaces.nsIServerSocket);
serv.init(6669,true,-1);
serv.asyncListen(async_listener);
}
catch (e){
alert("Ex: "+e);
}
}
July 30, 2004, 4:02 am com-b at iperbole dot bologna dot it
Multipart requests: not sleep()-ing...

Just to avoid any of you guys some trouble: if you use Apache, and if you use mod_gzip, you'll run into trouble getting the example above working: Apache waits until the script is completely done before sending anything, so the reult is that the script executes, sleeps a while, finishes and *then* send the data to the cliente, defeating the whole purpose of the multipart thang.
Similar issues might apply to standard PHP output buffering, but that's a little easier to discover...

Copyright © 1999 - 2005 XULPlanet.com