subscribe
Tags:
 
2012
2011
2010
December
November
October
September
August
July
June
May
April
March
February
January
2009
December
November
October
September
August
July
June
May
April
March
February
January
2008
2012-01-16

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

using GLib;
//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_lineout size );
    request get_requestfirst_line );
  catch (Error e) {
    stderr.printf(e.message+"\n");
  }
  //do stuff based on the request
  switchrequest ) {
    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(iaPORT);
  //try to add the addess to the ThreadedSocketService
  try {
    tss.add_address(isaSocketType.STREAMSocketProtocol.TCPnullnull);
  catch(Error e) {
    stderr.printf(e.message+"\n");
    return;
  }
  //connect the 'run' signal that is emitted when there is a connection
  tss.run.connectconnection_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:

valac --pkg gio-2.0 server.vala

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.

  1. Your browser is old and needs to be updated
  2. 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

Comments
Name:
not required
Email:
not required (will not be displayed)
Website:
not required (will link your name to your site)
Comment:
required
Please do not post HTML code or bbcode unless you want it to show up as code in your post. (or if you are a blog spammer, in which case, you probably aren't reading this anyway).
Prove you are human by solving a math problem! I'm sorry, but due to an increase of blog spam, I've had to implement a CAPTCHA.
Problem:
8 minus 3
Answer:
required
2012-01-07

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

Comments
2012-01-08 senshikaze:
Is it a faux pas to +1 the post about you being in the hospital for a broken collar bone? Anyway, get well soon!
2012-01-08 jpope:
Put some GoogleButter on it. :)
Heal quick!
2012-01-08 jezra:
@senshikaze, the hospital visit was a minor bit of the story, so no faux pas.

@jpope rub it all over that shit, man!
2012-01-08 Alison Chaiken:
Ouch, quite a bump!
2012-01-08 Anonymous Daniel:
Get well soon
2012-01-09 thp:
Get well soon! :) At least your beard is still in place! Phew..
Name:
not required
Email:
not required (will not be displayed)
Website:
not required (will link your name to your site)
Comment:
required
Please do not post HTML code or bbcode unless you want it to show up as code in your post. (or if you are a blog spammer, in which case, you probably aren't reading this anyway).
Prove you are human by solving a math problem! I'm sorry, but due to an increase of blog spam, I've had to implement a CAPTCHA.
Problem:
0 plus 3
Answer:
required
  • Tags:
2011-12-29

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

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:

  1. Create an SSH tunnel to my new host
  2. Run a mysqldump and pipe the output to a text file
  3. tar gzip the text file

Enter the Ruby

#!/usr/bin/env 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(sshcmddo |threads|
  #sleep for a bit and give the thread time to connect
  sleep 10
  #get the first thread
  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

Comments
Name:
not required
Email:
not required (will not be displayed)
Website:
not required (will link your name to your site)
Comment:
required
Please do not post HTML code or bbcode unless you want it to show up as code in your post. (or if you are a blog spammer, in which case, you probably aren't reading this anyway).
Prove you are human by solving a math problem! I'm sorry, but due to an increase of blog spam, I've had to implement a CAPTCHA.
Problem:
5 minus 0
Answer:
required