About
fv_2007
Agile innovative developer with deep insight into lots of platforms, technologies and protocols. Absolute “early adopter” in Web 2.0 technologies and more. Large professional network and eagerly talking about architecture, strategy, design patterns, restful ressources, object-oriented thinking and modeling languages such as PML. Special interest in programminglanguages constructs, knowledge on languages like Smalltalk, Erlang, Java, Clojure, Scala, Ruby... read more
Comments
Language

HTML5 WebSocket bi-directional communication April 14, 2010 06:17 about 1 year ago

The web is broken! Sætningen dækker over den konversation der foregår mellem en klient applikation og en server applikation over HTTP protokollen. Processen sker i små afbrudte sekvenser og mellem de enkelte kald er konversationen tilstandsløs, dvs at serveren i princippet ikke ved noget om hvad en klient fortager sig eller omvendt.

Der benyttes mange metoder for at lade brugeren i den tro at det er en stateful konversation men det er blot en illusion. Problem bliver synliggjort i en chat applikation hvor mange klienter deler beskeder mellem dem. Når en browser applikation aflevere en besked til en server skal alle andre klienter i teorien henvende sig hos serveren for at få overleveret beskeden. Hvis en klient har en åben forbindelse kan den modtage den nye besked men den kan ikke sende en ny uden at åbne et ny forbindelse.

Teknologier som AJAX, ‘Server Push’, ‘Server Pull’ og ‘Long Alive Connection’ har forbedret den subjektive fornemmelse ved at lade kommunikationen være mere finkornet og granulaitet. Ønsket er at gøre helt op med request, response paradigmet ved at holde forbindelsen åben mellem klient og server i lange perioder. Server Push fx sender ikke en EOF og terminere dermed aldrig. Fordelen er at serveren kan pumpe mere information til klienten efterhånden den findes. Ulempen er at det foregår som en envejskommunikation.

WebSockets er en nyskabelser i HTML5 specifikationen. For mig er det en suveræn teknologi fordi jeg i mange andre tilfælde har konstrueret applikationer der udnytter metoder som HTTP streaming, Reverse Proxy eller AJAX. WebSockets er designet til at være data agnostisk og tilbyder fuld duplex gennem en enkelt forbindelse på samme måde som TCP for netværk. Dette giver en god udnyttelse for både klient og server og den kan operere via SSL og HTTP sammen med eksisterende load balancers og proxies.

Måske tænker du at en browser så kan åbne en forbindelse til et TCP netværk direkte. Men nej, sådan er det heldigvis ikke. WebSockets modellen fungere som en udvidelse på HTTP-protokollen ved at definere et særlig håndtryk som browseren kan operere på. Det kan lade sig gøre at kommunikere med alle andre protokoller men kun ved at indføre en WebSocket server som Man-in-the-Middle.

Platform

Jeg vil lave server delen i JRuby med en WebSocket fordi det er nemt og hurtigt. Jeg tænker jeg vil eksekvere dem med Jruby 1.4.0 fordi jeg er på en Windows maskine men den køre lige så nemt under Matz Ruby. Jeg vil udnytte EventMachine og et lille bibliotek der kan tale WebSocket protokollen, dvs HTTP handshake

EventMachine

EventMachine er et bibliotek for Ruby, C++ og Java applikationer. EventMachine er bygget over et event-driven I/O Reactor Design Pattern. EventMachine er designet til at opfylde to centrale behov.

  • Ekstrem høj skalerbarhed, performance og stabilitet for de mest krævende produktionsmiljøer
  • Et API der fjerner kompleksiteten ved high-performance trådet netværks programmering så udviklere kan koncentrere sig om forretningslogik.

Denne kombination gør EventMachine til et perfekt valg når man designer kritiske netforbundne applikationer herunder web-servere og proxies, e-mail og IM produktionssystemer, autentificering/tilladelse processorer, og meget mere.

Reactor Design Pattern

Dette mønster er bygget op omkring en single tråd der der looper og kaldes “the reactor loop” hvori ingen processer kan blokkere. Det betyder at man kan have en betydelig mængde samtidige klienter hængende i åbne forbindelser på en gang.

JRuby

Hvis Java er på box’en skal man downloadet Jruby og sætte path op til c:\jruby\bin biblioteket. Derefter installeres de nødvendige gems.

research> jruby -S gem install em-websocket 
research> jruby -S gem install eventmachine 

Med omgivelserne på plads er det tid til koden. Så skal ruby kode kende til de respektive gems bindinger med require methoden.

require 'rubygems'
require 'em-websocket'
require 'eventmachine'

@@clients = []

EventMachine::WebSocket.start(:host => "0.0.0.0", :port => 443) do |ws|
end

På denne måde bliver en eventmachine websocket defineret. Jeg definere ligeledes en liste til klienter og notere den som værende global eller fælles for alle eksekverende klienter. Det er med vilje at jeg ikke bruger udtrykket, fælles for alle tråde, idet JRuby supportere native OS tråde mens Ruby bruger “green threads”. Intern kan Java håndtere flere forskellige JRuby runtimes i separate tråde hvor hver objekt har en reference det runtime de tilhøre. Der er ikke intraproces mellem de enkelte runtimes. JRuby tråde kan udnytte udnytte muliticore CPU maskiner på samme måde som Java. Ruby script dele der eksekveres mere end 20 gange bliver precompilation-enable.

  ws.onopen {
    puts "WebSocket connection open"
    @@clients << ws
    @@clients.each { |client|
      client.send "Arriving to the room. #{@@clients.length()} clients online"
    }
  }

Metoden bliver kaldt når en klient ankommer og tilføjes den globale liste. Listen løbes igennem og der sendes en notifikation til alle klienter der har en bidirectional forbindelse til EventMachine processen.

Når en klient sender en besked til server WebSocket processen havner den i onmessage metoden. Listen løbes igennem og beskeden broadcastes til alle klienter. Og her sker faktisk lidt af det sjove. Den forbindelse som før blev brugt til at skrive til serveren bliver nu brugt den anden vej. Alle klienter der står standby får beskeden leveret gennem deres WebSocket.

  ws.onmessage { |msg|
    puts "Recieving: #{msg}"
    @@clients.each { |client|
      client.send "#{msg}"
    }
  }

Når en klient lukker forbindelse, ved at lukke browservinduet eller afbryde kaldes onclose metoden på serveren. Metoden fjerene den aktuelle klient fra den globale liste og sender en notifikation til de resterende klienter.

  ws.onclose { 
    @@clients.delete(ws) 
    @@clients.each { |client|
      client.send "Leaving the room. #{@@clients.length()} clients online"
    }
    puts "WebSocket connection closed" 
  }

Det var den server kode der skal til for en interimistisk chatrobot.

HTM5, CSS3 og javascript

Klienten er en letvægts klient ud af HTML5 og stylesheet CCS3 iblandet lidt Javascript. Når klienten er klar i browseren skabes forbindelse til WebSocket serveren og den holde “alive” indtil klienten lukker vinduet.

Udover det interessante ved nogle af de nye HTML5/CSS3 features er selve syntaksen ved JavaScript prototyperne på WebSocket. De er i komplet harmoni med server implementeringen, virkeligt rart. Når html siden er klar skabes forbindelsen til serveren

<!doctype html>
<title>HTML5 Websockets</title>
<style>
  * {margin:0; padding:0;}
  div {display:none;}
  #section {width:400px; border:solid red 1px; padding:15px; margin:15px;}
  #debug {background-color:red;}
  #msg {padding:5px;}
  #msg p {background:#FFF;}
  #msg p:nth-child(2n+1) {background:#EEE;}
</style>
<script src="http://code.jquery.com/jquery-latest.js"></script>
<script src="http://linutop.frankvilhelmsen.com/browserdetect.js"></script>
<script>
window.onerror=function customHandler(desc,page,line,chr)  {
   alert("Your browser has no native websocket support available")
  return true
}

var debug = function(text) { $("#debug").prepend(text).fadeIn().delay(1000).fadeOut(); }
var msg   = function(text) { $("#msg").prepend(text).show(); }
var user = BrowserDetect.browser+'-'+BrowserDetect.version+'-'+BrowserDetect.OS

$(document).ready(function(){
  $('#section').fadeIn();
  ws = new WebSocket('ws://linutop.frankvilhelmsen.com:443/');
  ws.onmessage = function(evt) { msg("<p>"+evt.data+"</p>") };
  ws.onclose = function() { debug("Socket closed"); };
  ws.onopen = function() { debug("Connected..."); ws.send("You are connected to server"); };

  $("input[type='submit']").bind('click', function() {
    var input = $("input#message").val();
    ws.send(user+":"+input)                
    $('input#message').val("")
  });
});
</script>                                             
<div id="section">
  <input type="text" id="message" placeholder="Send some text"><input type="submit">
  <div id="debug"></div>
  <div id="msg"></div>
</div>
</html>

Jeg har haft klient med åbne forbindelse i døgnvis uden at de mister evnen til at broadcastet en meddelse til de andre klienter.

Same-Origin Policy

Med hensyn til browser sikkerhedspolitik SOP ikke længere. Hvis en WebSocket kun skal kunne kommunikere med samme vært og port, hvor Javascript koden oprinde fra ville det være en begrænsning fordi det vil kræve at en webserver også har en WebSocket port. I definitionen står at default port for WebSocket er port 80, og altså samme port som webserveren selv.

Men protokollen kræver at browseren sender oplysninger om oprindelsen af klienten som serveren kan validere ved at respondere tilbage til klienten og endelig validere klienten at serveren responderede. Ifølge WebSocket protokollen skal response inkludere dette:

  • Oprindelses er protokol, host, port. (http://localhost:80)
  • Lokation er målet for requestet. (ws://localhost:443)
  • Protokollen er en vilkårlig streng der kan identificere som den forventede applikationsprotokol.

Det betyder at klient koden, altså HTML/Javascript filen kan eksekveres lokalt men tilsluttte sig en WebSocket på en remote host og altså dermed uden om SOP. Det kan få ens tanker hen på Cross-Domain enabling med JSONP, her er kommunikationen bare demarkeret som et HTTP request.

Eller er der vel kun at gøre opmærksom på at det er næsten umuligt at finde en browser der er HTML5 enable. Jeg har testet på Google Chrome der som den eneste kan bruge WebSocket løsningen på mine maskiner. Der findes implementationer i fx node.js og et par stykker til men jeg vil ha den ægte vare.


By Frank Vilhelmsen - 4 tags: server html5 css3 jruby - Add comment