The Basics
While researching Push methods that would allow me to push data from a server to a client, I learned about Server Sent Events and decided to go about writing a bit of code to test the technology.
Why?
For a current project with a web based UI, I have an AJAX request update the UI every 2 seconds with data requested from the application. Most of the requested data doesn't change very often, but the data that does change, changes fast enough that 2 seconds causes a lot of latency in UI updates.
The project currently uses libsoup for creating a web server. Unfortunately, I couldn't get my current libsoup code to work the way I needed it to, so for this test, I wrote a fairly simple/limited webserver using GIO sockets.
It isn't a very proper webserver, but it is a decent example of coding in vala with sockets, and it does a fine job of showing off Server Sent Events.
Enter the Vala
//what port should this serve on?
const uint16 PORT = 7777;
//when a request is made, handle the socket connection
bool connection_handler(SocketConnection connection) {
string first_line ="";
size_t size = 0;
string request = "";
//get data input and output strings for the connection
DataInputStream dis = new DataInputStream(connection.input_stream);
DataOutputStream dos = new DataOutputStream(connection.output_stream);
//read the first line from the input stream
try {
first_line = dis.read_line( out size );
request = get_request( first_line );
} catch (Error e) {
stderr.printf(e.message+"\n");
}
//do stuff based on the request
switch( request ) {
case "/" :
try {
// create the html and javascript that will call /sse
string html = "<html>
<head>
<title>Soup Server Sent Event</title>
<script type='text/javascript'>
function init() {
var info_div = document.getElementById('info_div');
var source = new EventSource('/sse');
source.onmessage = function (event) {
info_div.innerHTML = event.data;
};
}
</script>
</head>
<body onload='init();'>
<div id='info_div'></div>
</body>
</html>";
dos.put_string("HTTP/1.1 200 OK\n");
dos.put_string("Server: ValaSocket\n");
dos.put_string("Content-Type: text/html\n");
dos.put_string("Content-Length: %d\n".printf(html.length));
dos.put_string("\n");//this is the end of the return headers
dos.put_string(html);
} catch(Error e) {
stderr.printf(e.message+"\n");
}
break;
case "/sse" :
try {
dos.put_string("HTTP/1.1 200 OK\n");
dos.put_string("Server: ValaSocket\n");
dos.put_string("Content-Type: text/event-stream\n");
dos.put_string("\n");
} catch(Error e) {
stderr.printf(e.message+"\n");
}
string info;
int count = 0;
bool looping = true;
while(looping) {
count++;
if (count > 10 ) {
count = 1;
}
info = @"data: hello world $count\n\n";
try {
dos.put_string(info);
//sleep for $count half seconds
Thread.usleep(count*500000);
}catch(Error e) {
//is the pipe broken?
looping = false;
}
}
break;
default:
//404
try {
dos.put_string("HTTP/1.1 404\n");
dos.put_string("Server: ValaSocket\n");
dos.put_string("Content-Type: text/plain\n");
dos.put_string("\n");
dos.put_string("File Not Found");
} catch(Error e) {
stderr.printf(e.message+"\n");
}
break;
}
return false;
}
// get the 'requested path' portion of a line
string get_request(string line) {
string[] parts = line.split(" ");
return parts[1];
}
public static void main() {
//we need a GLib mainloop to keep this app running
MainLoop loop = new MainLoop();
//make the threaded socket service with hella possible threads
ThreadedSocketService tss = new ThreadedSocketService(150);
//create an IPV4 InetAddress bound to no specific IP address
InetAddress ia = new InetAddress.any(SocketFamily.IPV4);
//create a socket address based on the netadress and set the port
InetSocketAddress isa = new InetSocketAddress(ia, PORT);
//try to add the addess to the ThreadedSocketService
try {
tss.add_address(isa, SocketType.STREAM, SocketProtocol.TCP, null, null);
} catch(Error e) {
stderr.printf(e.message+"\n");
return;
}
//connect the 'run' signal that is emitted when there is a connection
tss.run.connect( connection_handler );
//start listening
stdout.printf(@"Serving on port $PORT\n");
tss.start();
//run the main loop
loop.run();
}
Save the code as "server.vala" and compile with:
run the 'server' and point your browser to http://localhost:7777
If you don't see anything in your browser, there are a few possible reasons.
- Your browser is old and needs to be updated
- For some reason, you are using Internet Explorer (ouch! why do you hate the internet?)
What's Happening?
When the browser requests http://localhost:7777, a very basic webpage is returned, and the webpage contains a small bit of javascript that creates a new EventSource bound to http://localhost:7777/sse and updates a div on the page with date pushed to the EventSource.
Now I just need to strip the old libsoup code out of my project and add a nice socket based web server of my own creation. [enter evil laugh here] MUAHAHAHA