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
A Gift for New Years
The subject of my coffee on 2011-12-31 was a note from my sweetheart lamenting the lack of New Year's Eve kisses we would be sharing. Fortunately, she included a chili pepper with the note so that I could have a hot New Year. Challenge Accepted.
12AM January 1st
New Year's Eve was celebrated at my buddy Joe's place with his family and a bunch of close friends. As the moment approached, I read the note to everyone present; and at the proper time, the chili was chomped and eaten. I think it was a serrano and it set my mouth ablaze.
When eating chilis, make sure your friends are there to share the laughs.
6PM January 1st
16 hours into 2012, I went to the hospital due to a broken collar bone. Bummer. Wow, look at that swelling... and the terrible haircut that I gave myself.
Eventually the bone will heal, and my hair will grow out. In the mean time, there is code to hack and chilis to chomp.
Happy New Year
Heal quick!
@jpope rub it all over that shit, man!
I'd Rather Not Have To Write This
Hey! Where is the blog post about moving hosting providers? Good question. Lamentably, the answer is "pilot error", A.K.A. it is all my fault. During a code push to my new hosting provider, I accidentally clobbered my new database access config file with my old config file. Fortunately, the end result of my mistake, was the loss of just one blog post.
So What Happened?
When the config was clobbered, all database requests pointed to my old hosting provider and when my service time with them expired, they removed my former database from their systems.
As a result, any information that was added to the database after the clobber was lost (with the exception of the RSS PHP post which I still had on my computer in markdown format). sql
So What Was Lost?
The decision making criteria that I used when selecting http://www.webfaction.com as my new hosting provider and http://gandi.net as my new domain registrar is gone. I'll do a quick recap:
webfaction
- multiple websites on one hosting account
- shell access
- runs web apps and not just scripts
How about I make this nice and short and just send you to their features page
Gandi.net
- They are not GoDaddy
- No Bullshit is there motto
- They support project that I like
And who could forget the loss of information about http://googlebutter.com? Not me, that's for sure. Hi Buddy!
What Am I Doing to Limit Loss in the Future?
The real culprit here is me for not making enough database backups. So I had better write a script to make database backups for me. For security reasons, my new provider does not allow mysql connection from other than localhost or 127.0.0.1, I need to:
- Create an SSH tunnel to my new host
- Run a mysqldump and pipe the output to a text file
- tar gzip the text file
Enter the Ruby
require 'date'
require 'open3'
#create variable for database access
db_server = "MYSERVER_IP_ADDRESS"
db_user = "MY_DATABASE_USER"
db_pass = "MY_DATABASE_USER_PASSWORD"
#what day is it?
Ymd = Date.today.strftime("%Y%m%d")
sql_file = "#{Ymd}_dump.sql"
#create an ssh tunnel
sshcmd = "ssh -L 3307:127.0.0.1:3306 #{db_server} -N"
dumpcmd = "mysqldump -A -u#{db_user} -P3307 -h127.0.0.1 -p#{db_pass} > #{sql_file}"
#start the sshcmd
puts "starting ssh tunnel..."
Open3.pipeline_start(sshcmd) do |threads|
#sleep for a bit and give the thread time to connect
sleep 10
#get the first thread
t = threads[0]
puts "ssh tunnel has pid: #{t.pid}"
#run the dump command
puts "dumping database info"
system(dumpcmd)
puts "data dump is complete, killing ssh tunnel pid #{t.pid}"
#kill the ssh tunnel
Process.kill("TERM", t.pid)
end
#tar the sqlfile
tarcmd = "tar -czvf #{sql_file}.tgz #{sql_file}"
system(tarcmd)
system("rm #{sql_file}")
For easy copy paste, the code is in the hoof http://hoof.jezra.net/snip/nT
What I should really do, instead of tunneling the mysqldump and then tar gzipping the data on my machine, Is perform the dump and archive on the server and then transfer the compressed archive to my machine.
Now quit reading, and go backup your data! (Then go see my buddy at http://googlebutter.com) Hi Buddy! hahaha



