• Share
  • Email
  • Embed
  • Like
  • Save
  • Private Content
Html5 : stockage local & synchronisation
 

Html5 : stockage local & synchronisation

on

  • 2,944 views

 

Statistics

Views

Total Views
2,944
Views on SlideShare
2,495
Embed Views
449

Actions

Likes
10
Downloads
46
Comments
0

11 Embeds 449

http://www.paris-web.fr 188
http://news.humancoders.com 169
https://twitter.com 53
http://speakerrate.com 21
http://us-w1.rockmelt.com 7
http://www.techgig.com 6
http://www.php-talks.com 1
http://www.linkedin.com 1
https://si0.twimg.com 1
http://twitter.com 1
http://www.pearltrees.com 1
More...

Accessibility

Categories

Upload Details

Uploaded via as Adobe PDF

Usage Rights

© All Rights Reserved

Report content

Flagged as inappropriate Flag as inappropriate
Flag as inappropriate

Select your reason for flagging this presentation as inappropriate.

Cancel
  • Full Name Full Name Comment goes here.
    Are you sure you want to
    Your message goes here
    Processing…
Post Comment
Edit your comment
  • La difficulté de la tâche dépend de l'appli, en particulier des fonctionnalités devant être utilisables hors connexion (et de celles qui sont difficiles à supporter hors connexion, comme la recherche globale) Difficile de convertir une appli au offline si elle n'est pas conçue à la base comme une appli essentiellement client-side (single page app) avec un backend se comportant en dépôt de données " simple " En fonction de l'application, on choisira une conception " modale " (l'user choisit explicitement de basculer en offline, d'où un chargement local initial des données), notamment si l'appli présente des données hors de contrôle de l'user (ex : feedreader), ou " modeless " (ex : webmail, gestion de tâches...) Dans tous les cas, supporter une utilisation offline complexifie l'appli, il convient donc de s'assurer que ce type d'utilisation est pleinement justifié
  • Le cache d'une appli est limité à 5 Mo (à checker) Diffère de la mise en cache " classique " des navigateurs, qui est plus volatile
  • Une fois que les ressources sont mises en cache, le cache n'est mis à jour que si le manifeste change
  • Autre pb : le navigateur n'essaiera même pas de charger toute ressource qui n'est pas mentionnée dans le manifeste, les URIs type API et autre doivent donc être mentionnées dans la section NETWORK
  • On détecte l'event 'updateready' qui signale une mise à jour du manifeste et donc une MAJ potentielle des ressources pour en avertir l'user et rafraîchir la page, donnant ainsi à l'user l'accès aux nouvelles ressources
  • Plus facile à dire qu'à faire (tout dépend de l'appli) : l'idée est dans un premier temps d'encapsuler les appels XHR dispersés dans l'appli dans un module type Repository : l'appli ne parle plus directement à un serveur mais à un objet dépôt de données qui, pour l'instant, fait des appels XHR
  • Maintenant que l'application est découplée du réseau, nous pouvons faire en sorte que notre objet " dépôt " persiste localement les données chargées depuis le serveur
  • API permettant de lire/écrire des reps et fichiers dans une portion " sandboxée ", par application, du FS Uniquement pour Chrome >= 13.0 pour toutes les fonctionnalités Tous les objets sont préfixés par webkit
  • Lorsque window. requestFileSystem est appellé pour la première fois, le bac à sable est créé. Il peut être temporaire (et potentiellement supprimé par le browser à sa discrétion) ou persistant, ce qui nécessite de demander l'autorisation à l'utilisateur Il existe une API pour checker le statut et l'utilisation du quota
  • SessionStorage supprime les données lorsque le navigateur est fermé
  • Le quota de 5Mo est mentionné dans la spec W3C, dans la pratique cela dépend des versions des navigateurs... Pas de transactions → risque de pb d'accès concurrent (race condition) si l'appli est utilisé dans 2 onglets par exemple
  • Si le support d'IE 7 est requis, il existe un polyfill simple qui va stocker les données dans le cookie
  • Inconvénients : - nbreuses requêtes - charge sur les serveurs - latence entre 2 polls (pas de vrai temps réel à moins de poller toutes les secondes)
  • Plus proche du temps réel, mais plus complexe à mettre en place. Dans le cas du push de micro-modifs, le nb/poids des requêtes reste conséquent (surtout compte tenu des headers HTTP) Une variante : le push streaming où l'on ne referme pas la connexion à chaque event mais où on envoie une réponse en plusieurs fois ; pb : mal supporté par certains proxys
  • Connexion bidirectionnelle full-duplex à travers une socket TCP Plusieurs connexions possibles sur le même port S'efforce de traverser proxys et firewall Protocole RFCisé
  • Connexion bidirectionnelle full-duplex à travers une socket TCP Plusieurs connexions possibles sur le même port S'efforce de traverser proxys et firewall Protocole RFCisé
  • Socket.IO utilise le meilleur moyen à sa dispo : - websockets - Flash socket - long polling - Ajax streaming - forever iframe - JSONP polling
  • Connexion bidirectionnelle full-duplex à travers une socket TCP Plusieurs connexions possibles sur le même port S'efforce de traverser proxys et firewall Protocole RFCisé
  • Connexion bidirectionnelle full-duplex à travers une socket TCP Plusieurs connexions possibles sur le même port S'efforce de traverser proxys et firewall Protocole RFCisé
  • Connexion bidirectionnelle full-duplex à travers une socket TCP Plusieurs connexions possibles sur le même port S'efforce de traverser proxys et firewall Protocole RFCisé
  • Connexion bidirectionnelle full-duplex à travers une socket TCP Plusieurs connexions possibles sur le même port S'efforce de traverser proxys et firewall Protocole RFCisé
  • Connexion bidirectionnelle full-duplex à travers une socket TCP Plusieurs connexions possibles sur le même port S'efforce de traverser proxys et firewall Protocole RFCisé

Html5 : stockage local & synchronisation Html5 : stockage local & synchronisation Presentation Transcript

  • HTML 5 stockage local& synchronisation Raphaël Rougeron @goldoraf
  • Une mutation est en cours Pages HTML Serveur Client Single-Page Application JSON
  • Form validation GeolocationDrag n Drop API Web Notifications Canvas WebGL Video Audio File API Web Workers Web Sockets
  • Stockage local
  • 1er bénéfice : le offline ?
  • 1er bénéfice : économiser le serveur !Source : HTML5 Web Sockets: A Quantum Leap in Scalability for the Webhttp://soa.sys-con.com/node/1315473
  • 4 étapes
  • Etape 1 Rendre les ressourcesdisponibles hors connexion
  • Application Cache<html manifest="cache.manifest"> <html manifest="cache.manifest"> CACHE MANIFEST CACHE MANIFEST CACHE: CACHE: index.html index.html css/style.css css/style.css img/logo.png img/logo.png js/main.js js/main.js text/cache­manifest
  • !Avoir un manifestechange complètementles règles daccès aux ressources
  • CACHE MANIFEST CACHE MANIFEST# v.1.2­build1234 # v.1.2­build1234CACHE: CACHE:index.html index.htmlcss/style.css css/style.cssimg/logo.png img/logo.pngjs/main.js js/main.js
  • CACHE MANIFEST CACHE MANIFEST# v.1.2­build1234 # v.1.2­build1234CACHE: CACHE:index.html index.htmlcss/style.css css/style.cssimg/logo.png img/logo.pngjs/main.js js/main.jsNETWORK NETWORK/sync /sync/api/* /api/*http://api.twitter.com http://api.twitter.com
  • CACHE MANIFEST CACHE MANIFEST# v.1.2­build1234 # v.1.2­build1234CACHE: CACHE:index.html index.htmlcss/style.css css/style.cssimg/logo.png img/logo.pngjs/main.js js/main.jsNETWORK NETWORK/sync /sync/api/* /api/*http://api.twitter.com http://api.twitter.comFALLBACK FALLBACKimages/large/ images/offline.jpg images/large/ images/offline.jpg*.html /offline.html *.html /offline.html
  • window.applicationCache status UNCACHED IDLE CHECKING DOWNLOADING UPDATEREADY OBSOLETE
  • var appCache = window.applicationCache; var appCache = window.applicationCache;appCache.addEventListener(updateready, function(e) { appCache.addEventListener(updateready, function(e) {    if (appCache.status == appCache.UPDATEREADY) {     if (appCache.status == appCache.UPDATEREADY) {      appCache.swapCache();       appCache.swapCache();      if (confirm(Une nouvelle version est dispo)) {       if (confirm(Une nouvelle version est dispo)) {        window.location.reload();         window.location.reload();      }       }    }     }}, false); }, false);
  • >= 10 ? >= 3.5 >= 4.0 >= 4.0 >= 10.6 >= 3.2 >= 2.1 >= 11.0
  • Etape 2Découpler lapplication du réseau
  • var Todo = Backbone.Model.extend({ var Todo = Backbone.Model.extend({    toggle: function() {     toggle: function() {      this.save({done: !this.get("done")});       this.save({done: !this.get("done")});    },     },    clear: function() {     clear: function() {      this.destroy();       this.destroy();    }     }}); });var TodoList = Backbone.Collection.extend({ var TodoList = Backbone.Collection.extend({    model: Todo,     model: Todo,    done: function() {     done: function() {      return this.filter(function(todo){ return todo.get(done); });       return this.filter(function(todo){ return todo.get(done); });    },     },    remaining: function() {     remaining: function() {      return this.without.apply(this, this.done());       return this.without.apply(this, this.done());    }     }}); });
  • Backbone.Sync● create() POST /collection● read() GET /collection[/id]● update() PUT /collection/id● delete() DELETE /collection/id
  • Etape 3Stocker localement les données
  • 3 options
  • FileSystem API ! seulement
  • window.requestFileSystem  = window.requestFileSystem  window.requestFileSystem  = window.requestFileSystem        || window.webkitRequestFileSystem;       || window.webkitRequestFileSystem;window.requestFileSystem(window.TEMPORARY,  window.requestFileSystem(window.TEMPORARY,    5*1024*1024,   5*1024*1024,                         onInitFs, errorHandler);                          onInitFs, errorHandler);   window.webkitStorageInfo.requestQuota(window.PERSISTENT,  window.webkitStorageInfo.requestQuota(window.PERSISTENT,                                       5*1024*1024,                                       5*1024*1024,                                      initFS, errorHandler);                                       initFS, errorHandler);function initFS(grantedBytes) { function initFS(grantedBytes) {  window.requestFileSystem(PERSISTENT, grantedBytes,    window.requestFileSystem(PERSISTENT, grantedBytes,         onInitFs, errorHandler);        onInitFs, errorHandler);}}
  • FileErrorcodeQUOTA_EXCEEDED_ERRNOT_FOUND_ERRSECURITY_ERRINVALID_MODIFICATION_ERRINVALID_STATE_ERR
  • function onInitFs(fs) { function onInitFs(fs) {  fs.root.getFile(data.txt, {create: true}, function(entry) {   fs.root.getFile(data.txt, {create: true}, function(entry) {    entry.file(function(file) {     entry.file(function(file) {                      var reader = new FileReader();        var reader = new FileReader();       reader.onloadend = function(e) {        reader.onloadend = function(e) {         repository.init(JSON.parse(e.target.result));          repository.init(JSON.parse(e.target.result));       };        };       reader.readAsText(file);        reader.readAsText(file);             }, errorHandler);     }, errorHandler);  }, errorHandler);   }, errorHandler);}}
  • FileEntrynamefullpathisFileisDirectory…file()createWriter()moveTo()copyTo()remove()...
  • function persistData(fs, data) { function persistData(fs, data) {  fs.root.getFile(log.txt, {create: true}, function(entry) {   fs.root.getFile(log.txt, {create: true}, function(entry) {    entry.createWriter(function(writer) {     entry.createWriter(function(writer) {      writer.onwriteend = function(e) {       writer.onwriteend = function(e) {        console.log(Write completed.);         console.log(Write completed.);      };       };      writer.onerror = function(e) {       writer.onerror = function(e) {        console.log(Write failed:  + e.toString());         console.log(Write failed:  + e.toString());      };       };      var bb = new BlobBuilder();       var bb = new BlobBuilder();      bb.append(JSON.stringify(data));       bb.append(JSON.stringify(data));      writer.write(bb.getBlob(text/plain));       writer.write(bb.getBlob(text/plain));    }, errorHandler);     }, errorHandler);  }, errorHandler);   }, errorHandler);}}
  • Web StoragelocalStorage et sessionStorage
  • Un simple dépôt clé-valeurlocalStorage.setItem("foo", "bar"); localStorage.setItem("foo", "bar");localStorage.setItem("tweets", JSON.stringify(tweets); localStorage.setItem("tweets", JSON.stringify(tweets);var tweets = JSON.parse(localStorage.getItem("tweets")); var tweets = JSON.parse(localStorage.getItem("tweets"));
  • Quota de 5 Mo! Pas de transactions Pas dindexation
  • localStorage["tweets:1234"] = "Lorem ipsum..."; localStorage["tweets:1234"] = "Lorem ipsum...";localStorage["tweets:1235"] = "Nam mauris lorem..."; localStorage["tweets:1235"] = "Nam mauris lorem...";localStorage["tags"] = JSON.stringify(["Java", "Python",    localStorage["tags"] = JSON.stringify(["Java", "Python",                                         "Ruby", "PHP"]);                                       "Ruby", "PHP"]);localStorage["tags:Java"] = JSON.stringify([1234, 1235]); localStorage["tags:Java"] = JSON.stringify([1234, 1235]);
  • ➔ Très bien supporté ➔ Performances➔ API simple ➔ API synchrone➔ Mis en avant par de nb ➔ Sérialisation compagnies/projets ➔ Requêtes ➔ Complexité ➔ "indexation" manuelle ➔ Maintien intégrité
  • >= 8 >= 3.5 >= 4.0 >= 4.0 >= 10.5 >= 3.2 >= 2.1 >= 11.0
  • localStorage adapterBackbone.sync = function(method, model, options) { Backbone.sync = function(method, model, options) {  var resp;   var resp;  var store = model.localStorage || model.collection.localStorage;   var store = model.localStorage || model.collection.localStorage;  switch (method) {   switch (method) {    case "read":    resp = model.id ? store.find(model) :      case "read":    resp = model.id ? store.find(model) :    store.findAll();    store.findAll();          break;          break;    case "create":  resp = store.create(model);                                     case "create":  resp = store.create(model);                                         break;          break;    case "update":  resp = store.update(model);                                 case "update":  resp = store.update(model);                                     break;          break;    case "delete":  resp = store.destroy(model);                                case "delete":  resp = store.destroy(model);                                    break;          break;  }   }  if (resp) {   if (resp) {    options.success(resp);     options.success(resp);  } else {   } else {    options.error("Record not found");     options.error("Record not found");  }   }}; };
  • Petit intermède Vous connaissez Redis ?BankersBoxvar bb = new BankersBox(1); var bb = new BankersBox(1);bb.set("foo", "bar"); bb.set("foo", "bar");bb.get("foo"); // returns "bar" bb.get("foo"); // returns "bar"bb.set("count", 10); bb.set("count", 10);bb.incr("count"); // sets "count" to 11, returns 11 bb.incr("count"); // sets "count" to 11, returns 11bb.incr("newcount"); // sets "newcount" to 1, returns 1 bb.incr("newcount"); // sets "newcount" to 1, returns 1bb.lpush("mylist", "hello"); bb.lpush("mylist", "hello");bb.lrange("mylist", 0, ­1); // returns ["hello"] bb.lrange("mylist", 0, ­1); // returns ["hello"]bb.rpush("mylist", "world"); bb.rpush("mylist", "world");bb.lrange("mylist", 0, ­1); // returns ["hello", "world"] bb.lrange("mylist", 0, ­1); // returns ["hello", "world"]bb.sadd("myset", "apple"); bb.sadd("myset", "apple");bb.sadd("myset", "oragne"); bb.sadd("myset", "oragne");
  • IndexedDB
  • Préfixes, préfixes...window.indexedDB = window.indexedDB || window.mozIndexedDB ||      window.indexedDB = window.indexedDB || window.mozIndexedDB ||                        window.webkitIndexedDB;                    window.webkitIndexedDB;window.IDBKeyRange = window.IDBKeyRange ||                         window.IDBKeyRange = window.IDBKeyRange ||                                             window.webkitIDBKeyRange;                      window.webkitIDBKeyRange;window.IDBTransaction = window.IDBTransaction ||                   window.IDBTransaction = window.IDBTransaction ||                                          window.webkitIDBTransaction;                         window.webkitIDBTransaction;
  • API asynchronefunction IndexedDBAdapter() { function IndexedDBAdapter() { this.db = null; this.db = null; var request = indexedDB.open("contactApp"); var request = indexedDB.open("contactApp"); request.onsuccess = function(e) { request.onsuccess = function(e) { this.db = e.target.result; this.db = e.target.result; }.bind(this); }.bind(this); request.onfailure = function(e) { request.onfailure = function(e) { console.log("Could not connect to the database"); console.log("Could not connect to the database"); }}}; };
  • Création dun dépôtIndexedDBAdapter.prototype.create = function(storeName) { IndexedDBAdapter.prototype.create = function(storeName) { var v = "1.0"; var v = "1.0"; var request = this.db.setVersion(v); var request = this.db.setVersion(v); request.onsuccess = function(e) { request.onsuccess = function(e) { var store = this.db.createObjectStore(storeName, { var store = this.db.createObjectStore(storeName, { "keyPath": "id", "keyPath": "id", "autoIncrement": true "autoIncrement": true }); }); }; }; request.onblocked = function(e) { request.onblocked = function(e) { console.log("The database is open in another tab."); console.log("The database is open in another tab."); }; };}; };
  • Persistence dun objetIndexedDBAdapter.prototype.save(storeName, object, callback)  IndexedDBAdapter.prototype.save(storeName, object, callback) {{ var trans = db.transaction([storeName],                    var trans = db.transaction([storeName],                                                 IDBTransaction.READ_WRITE, 0);                               IDBTransaction.READ_WRITE, 0); var store = trans.objectStore(storeName); var store = trans.objectStore(storeName); var request = store.put(object); var request = store.put(object); request.onsuccess = function(e) { request.onsuccess = function(e) { callback(object); callback(object); }; };}; };
  • Requête simpleIndexedDBAdapter.prototype.all(storeName, callback) { IndexedDBAdapter.prototype.all(storeName, callback) { var trans = db.transaction([storeName],                    var trans = db.transaction([storeName],                                                 IDBTransaction.READ_WRITE, 0);                               IDBTransaction.READ_WRITE, 0); var store = trans.objectStore(storeName); var store = trans.objectStore(storeName); var keyRange = IDBKeyRange.lowerBound(0); var keyRange = IDBKeyRange.lowerBound(0); var request = store.openCursor(keyRange); var request = store.openCursor(keyRange); request.onsuccess = function(e) { request.onsuccess = function(e) { var cursor = e.target.result; var cursor = e.target.result; if (cursor) { if (cursor) { callback(cursor.value); callback(cursor.value); cursor.continue(); cursor.continue(); }} }; };}; };
  • Key RangesIDBKeyRange.upperBound(x);            // <= x IDBKeyRange.upperBound(x);            // <= xIDBKeyRange.upperBound(x, true);      // < x IDBKeyRange.upperBound(x, true);      // < xIDBKeyRange.lowerBound(y);            // >= y IDBKeyRange.lowerBound(y);            // >= yIDBKeyRange.lowerBound(y, true);      // > y IDBKeyRange.lowerBound(y, true);      // > yIDBKeyRange.bound(x, y);              // >= x && <= y IDBKeyRange.bound(x, y);              // >= x && <= yIDBKeyRange.bound(x, y, true, false); // > x && <= y IDBKeyRange.bound(x, y, true, false); // > x && <= y
  • Indexvar contacts = [ var contacts = [ { firstname: "John", lastname: "Doe", email: "jdoe@zz.com" }, { firstname: "John", lastname: "Doe", email: "jdoe@zz.com" }, { firstname: "Jane", lastname: "Doe", email: "jane@zz.com" }, { firstname: "Jane", lastname: "Doe", email: "jane@zz.com" }, { firstname: "Johnny", lastname: "Carson", email: "jca@zz.com" } { firstname: "Johnny", lastname: "Carson", email: "jca@zz.com" }]; ];objectStore.createIndex("firstname", "firstname", { unique: false }); objectStore.createIndex("firstname", "firstname", { unique: false });objectStore.createIndex("lastname", "lastname", { unique: false }); objectStore.createIndex("lastname", "lastname", { unique: false });objectStore.createIndex("email", "email", { unique: true }); objectStore.createIndex("email", "email", { unique: true });
  • Indexvar contacts = [ var contacts = [ { firstname: "John", lastname: "Doe", email: "jdoe@zz.com" }, { firstname: "John", lastname: "Doe", email: "jdoe@zz.com" }, { firstname: "Jane", lastname: "Doe", email: "jane@zz.com" }, { firstname: "Jane", lastname: "Doe", email: "jane@zz.com" }, { firstname: "Johnny", lastname: "Carson", email: "jca@zz.com" } { firstname: "Johnny", lastname: "Carson", email: "jca@zz.com" }]; ];var index = objectStore.index("lastname"); var index = objectStore.index("lastname");index.openCursor(IDBKeyRange.only("Doe")).onsuccess = function(e) { index.openCursor(IDBKeyRange.only("Doe")).onsuccess = function(e) { var cursor = e.target.result; var cursor = e.target.result; if (cursor) { if (cursor) { callback(cursor.value); callback(cursor.value); cursor.continue(); cursor.continue(); }}}; };var index = objectStore.index("firstname"); var index = objectStore.index("firstname");index.openCursor(IDBKeyRange.lowerbound("John")).onsuccess =  index.openCursor(IDBKeyRange.lowerbound("John")).onsuccess = function(e) { function(e) { ... ...}; };
  • >= 10 ? >= 8.0 >= 16.0 ? ? ? ? ?
  • Et pourquoi pasWeb SQL Database ?
  • X X >= 4.0 >= 3.1 >= 10.5 >= 3.2 >= 2.1 >= 11.0
  • Le polyfill ultime ? Lawnchair ● localStorage ● indexedDB ● webkit­sqlite ● gears­sqlite ● ie­userdata ● backberry­persistent­store ● window­name
  • Etape 4Développer une stratégie de synchronisation
  • Lexemple dActiveSync
  • POST /Microsoft­Server­ActiveSync?User=jdoe@zz.com&...POST /Microsoft­Server­ActiveSync?User=jdoe@zz.com&...     &Cmd=<Commande>     &Cmd=<Commande>Cmd=FolderSync&SyncKey=123456789Cmd=FolderSync&SyncKey=123456789{{ folders: [ folders: [ contacts: { contacts: { id: 1234, id: 1234, created: 0, created: 0, updated: 2, updated: 2, deleted: 1 deleted: 1 }, }, todos: { todos: { id: 5678, id: 5678, created: 5, created: 5, updated: 0, updated: 0, deleted: 2 deleted: 2 } } ] ]}}
  • Cmd=Sync&SyncKey=123456789&FolderId=5678Cmd=Sync&SyncKey=123456789&FolderId=5678{{ syncKey: 987654321, syncKey: 987654321,   created: [   created: [ { id: 4321, label: "Acheter le pain" }, { id: 4321, label: "Acheter le pain" }, ... ... ], ], updated: [ updated: [ ... ... ], ], deleted: [ deleted: [ 5678, 7890 5678, 7890 ] ]}}
  • Autres pistesMozilla ServicesSyncMLOperational Transformation → ShareJS
  • Etape subsidiaire Le "push"
  • PollingClient Serveur n secondes n secondes
  • Long polling (COMET) Client Serveur Événement côté serveur Événement côté serveur
  • WebSockets
  • HandshakeGET /resource name/ HTTP/1.1Upgrade: WebSocketConnection: UpgradeHost: /server/Origin: /origin/WebSocket­Protocol: /protocol/HTTP/1.1 101 Web Socket Protocol HandshakeUpgrade: WebSocketConnection: UpgradeWebSocket­Origin: /origin/WebSocket­Location: /url/WebSocket­Protocol: /subprotocol/
  • Serveur (avec node.js) var io = require(socket.io).listen(80); var io = require(socket.io).listen(80); io.sockets.on(connection, function (socket) { io.sockets.on(connection, function (socket) { socket.emit(news, { hello: world }); socket.emit(news, { hello: world }); socket.on(my other event, function (data) { socket.on(my other event, function (data) { console.log(data); console.log(data); }); }); }); });Client var socket = io.connect(http://localhost); var socket = io.connect(http://localhost); socket.on(news, function (data) { socket.on(news, function (data) { console.log(data); console.log(data); socket.emit(my other event, { my: data }); socket.emit(my other event, { my: data }); }); });
  • >= 10 ? >= 6.0 >= 14.0 >= 5.0 >= 11.0 >= 4.2 ? >= 11.0
  • Avec Socket.IO>= 5.5 >= 3.0 >= 4.0 >= 3.0 >= 10.61 >= 3.2 >= 2.1 >= 11.0
  • Server-Sent Events
  • HTTP traditionnelGET /myAppStreamContent­Type: text/event­stream
  • if (window.EventSource) {if (window.EventSource) {  var source = new EventSource(myAppStream);  var source = new EventSource(myAppStream);} else {} else {  // fallback to something...  // fallback to something...}}source.addEventListener(message, function(e) {source.addEventListener(message, function(e) {  console.log(e.data);  console.log(e.data);}, false);}, false);source.addEventListener(open, function(e) {source.addEventListener(open, function(e) {    }, false);}, false);source.addEventListener(error, function(e) {source.addEventListener(error, function(e) {  if (e.readyState == EventSource.CLOSED) {  if (e.readyState == EventSource.CLOSED) {          }  }}, false);}, false);
  • Format des messagesdata: Hello worldnnMulti-lignedata: Hellondata: worldnnJSONdata: {ndata: "msg": "Hello world",ndata: "id": 12345ndata: }nn
  • Format des messagesUtilisation des IDsid: 12345ndata: {"user": "goldo", "msg": "Hello !"}nnUtilisation des eventsdata: {"msg": "Hello !"}nnevent: loginndata: {"user": "goldo"}nnevent: updatendata: {"user": "goldo", "status": "away"}nn
  • AvantagesReconnexion automatiqueIdsEvents
  • Questions / réponses