Bidirektionale Verbindungen für
Webanwendungen
JAX 2012, Mainz

Marco Rico Gomez
@mricog
Weigle Wilczek GmbH

Roman Roelofsen
@romanroe
Agenda
➔

Warum … ?

➔

Wie … ?


Techniken



Bibliotheken

➔

Live Demo

➔

What's next …
Warum … ?
➔

moderne Webanwendungen ähneln immer mehr
RichClient-Applikationen




für derartige Anwendungen ist das HTTP Protokoll
nicht gemacht
es braucht „Hacks“ bzw. Erweiterungen
AJAX & Comet
AJAX (Asynchronous JavaScript and XML)

5
Comet (Server push)

6
Comet - Long-Polling
➔

Browser setzt eine AJAX Anfrage ab






bleibt geöffnet bis neue Daten vom Server gesendet
werden
Daten werden in der „onreadystatechange“ CallbackFunktion verarbeitet
neue Verbindung wird aufgebaut

7
Comet - Streaming
➔

eine persistente HTTP Verbindung

➔

Daten werden inkrementell verarbeitet


➔

ohne die Verbindung zu schließen

zwei Varianten:


„hidden iframe“



XMLHttpRequest
●

Daten werden in der „onreadystatechange“ CallbackFunktion verarbeitet

8
Comet - „Hacks“
➔

Nur 2 gleichzeitige persistente Verbindungen je Client
zu einem Server erlaubt (HTTP 1.1)


➔

Komplex


➔

seit 2008 haben die meisten Browser jedoch auf 6
Verbindungen erweitert
Bidirektionale Verbindung muss über zwei Kanäle simuliert
werden

HTTP–Overhead


Request-/ Response-Header
9
HTML5 Server-Sent Event (SSE)

10
HTML5 Server-Sent Event (SSE)
➔

Unidirektionale Kommunikation Server->Client

➔

Client abonniert einen Stream

➔

➔

Bei neuen Ereignissen wird der Client
benachrichtigt
Verbindung bleibt geöffnet


geschlossene Verbindungen werden automatisch wieder
geöffnet

11
SSE – JavaScript API
if (!!window.EventSource) {
var source = new EventSource('sse-stream.html');
} else {
// Result to xhr polling :(
}
// new message callback function
source.addEventListener('message', function(e) {
console.log(e.data);
}, false);
// register open callback (optional)
source.addEventListener('open', function(e) { ... }, false);
// register error callback (optional)
source.addEventListener('error', function(e) { ... }, false);
// cancel
source.close();

12
SSE - Server-response
HTTP/1.1 200 OK
Content-Type: text/event-stream
Cache-Control: no-cache
Connection: keep-alive
id: my-unique-connection-id
event: foo
data: This is the 1st line
data: Here comes the 2nd line

13
SSE – Jetty EventSourceServlet
class StockEventServlet extends EventSourceServlet {
def newEventSource(request: HttpServletRequest) = new
StockEventSource()
}
class StockEventSource extends EventSource {
private var emitter: Emitter
def onOpen(emitter: Emitter) {
this.emitter = emitter
}
def emitEvent(data: String) {
emitter.data(data)
}
def onClose() { /* NOP */ }
}

14
HTML5 WebSockets

15
HTML5 WebSockets
➔

➔

➔

„TCP für das Web“
bidirektionale Kommunikation über eine TCP
Verbindung
eliminiert „HTTP-Overhead“

16
WebSocket – Server (Java)

17
WebSocket – Handshake
➔

WebSocket Verbindung anfordern

➔

HTTP-Kompatibel


einfacher HTTP GET Request

18
WebSocket – Handshake (Client)
GET /chat HTTP/1.1
Host: server.example.com
Upgrade: websocket
Connection: Upgrade
Sec-WebSocket-Key: dGhlIHNhbXBsZSBub25jZQ==
Origin: http://example.com
Sec-WebSocket-Protocol: foo, bar
Sec-WebSocket-Version: 13

19
WebSocket – Handshake (Server)
HTTP/1.1 101 Switching Protocols
Upgrade: websocket
Connection: Upgrade
Sec-WebSocket-Accept: s3pPLMBiTxaQ9kYGzzhZRbK+xOo=
Sec-WebSocket-Protocol: bar

20
WebSocket – JavaScript API
var websocket = new WebSocket('ws://ws.example.com/chat', ['foo', 'bar']);
websocket.onopen = function () {
websocket.send('Ping'); // Send the message 'Ping' to the server
};
// process new message from the server
websocket.onmessage = function (e) {
console.log('Server: ' + e.data);
};
// register error callback function
websocket.onerror = function (error) { ... }

21
WebSocket – Jetty WebSocketServlet
class ChatWebSocketServlet extends WebSocketServlet {
def doGet(request: HttpServletRequest, response: HttpServletResponse) {}
def doPost(request: HttpServletRequest, response: HttpServletResponse) {}
def doWebSocketConnect(request: HttpServletRequest, protocol: String) =
new ChatWebSocket()
// shared resource (thread-safe !!!)
private val members = new CopyOnWriteArraySet<ChatWebSocket>()

}

class ChatWebSocket extends WebSocket {
...
}

22
WebSocket – Jetty WebSocketServlet
class ChatWebSocket extends WebSocket {
private var outbound: Outbound = null
def onConnect(outbound: Outbound) {
this.outbound = outbound
}
def onMessage(frame: Byte, data: String) {
for(member <- members) {
member.outbound.sendMessage(data)
}
}

}

def onDisconnect() {
members.remove(this)
}

23
WebSocket – Probleme
➔

Wird (noch) nicht von allen Browsern unterstützt


➔

WebSocket-Handshake wird von einigen ProxyServern unterbunden


➔

daher Frameworks wie „Atmosphere“ verwenden, die
einen automatischen „Fallback“ auf Comet bieten.

„Connection: Upgrade“ wird nicht verstanden

Low-Level API




Verbindungsabbrüche müssen selbst erkannt und
repariert werden
keine garantierte Verarbeitung von Nachrichten

24
Live Demo „Hello World“

25
Live Demo - Architektur

26
leon.io
27
What's next …

28
SPDY „http 2.0?“

29
SPDY

30
SPDY – spdy-jetty-http Modul
➔

SPDY wird von Jetty ab Version 7.6.2 und 8.1.2
unterstützt

➔

setzt OpenJDK 1.7+ voraus

➔

kann transparent aktiviert werden


keine Änderung der Applikation nötig

31
SPDY – jetty-spdy.xml
<Configure id="Server" class="org.eclipse.jetty.server.Server">
...
<Call name="addConnector">
<Arg>
<New class="org.eclipse.jetty.spdy.http.HTTPSPDYServerConnector">
<Arg>
<Ref id="sslContextFactory" />
</Arg>
<Set name="Port">8443</Set>
</New>
</Arg>
</Call>
</Configure>

32
Q &A

33

Bidirektionale Verbindungen für Webanwendungen

  • 1.
    Bidirektionale Verbindungen für Webanwendungen JAX2012, Mainz Marco Rico Gomez @mricog Weigle Wilczek GmbH Roman Roelofsen @romanroe
  • 2.
    Agenda ➔ Warum … ? ➔ Wie… ?  Techniken  Bibliotheken ➔ Live Demo ➔ What's next …
  • 3.
    Warum … ? ➔ moderneWebanwendungen ähneln immer mehr RichClient-Applikationen   für derartige Anwendungen ist das HTTP Protokoll nicht gemacht es braucht „Hacks“ bzw. Erweiterungen
  • 4.
  • 5.
  • 6.
  • 7.
    Comet - Long-Polling ➔ Browsersetzt eine AJAX Anfrage ab    bleibt geöffnet bis neue Daten vom Server gesendet werden Daten werden in der „onreadystatechange“ CallbackFunktion verarbeitet neue Verbindung wird aufgebaut 7
  • 8.
    Comet - Streaming ➔ einepersistente HTTP Verbindung ➔ Daten werden inkrementell verarbeitet  ➔ ohne die Verbindung zu schließen zwei Varianten:  „hidden iframe“  XMLHttpRequest ● Daten werden in der „onreadystatechange“ CallbackFunktion verarbeitet 8
  • 9.
    Comet - „Hacks“ ➔ Nur2 gleichzeitige persistente Verbindungen je Client zu einem Server erlaubt (HTTP 1.1)  ➔ Komplex  ➔ seit 2008 haben die meisten Browser jedoch auf 6 Verbindungen erweitert Bidirektionale Verbindung muss über zwei Kanäle simuliert werden HTTP–Overhead  Request-/ Response-Header 9
  • 10.
  • 11.
    HTML5 Server-Sent Event(SSE) ➔ Unidirektionale Kommunikation Server->Client ➔ Client abonniert einen Stream ➔ ➔ Bei neuen Ereignissen wird der Client benachrichtigt Verbindung bleibt geöffnet  geschlossene Verbindungen werden automatisch wieder geöffnet 11
  • 12.
    SSE – JavaScriptAPI if (!!window.EventSource) { var source = new EventSource('sse-stream.html'); } else { // Result to xhr polling :( } // new message callback function source.addEventListener('message', function(e) { console.log(e.data); }, false); // register open callback (optional) source.addEventListener('open', function(e) { ... }, false); // register error callback (optional) source.addEventListener('error', function(e) { ... }, false); // cancel source.close(); 12
  • 13.
    SSE - Server-response HTTP/1.1200 OK Content-Type: text/event-stream Cache-Control: no-cache Connection: keep-alive id: my-unique-connection-id event: foo data: This is the 1st line data: Here comes the 2nd line 13
  • 14.
    SSE – JettyEventSourceServlet class StockEventServlet extends EventSourceServlet { def newEventSource(request: HttpServletRequest) = new StockEventSource() } class StockEventSource extends EventSource { private var emitter: Emitter def onOpen(emitter: Emitter) { this.emitter = emitter } def emitEvent(data: String) { emitter.data(data) } def onClose() { /* NOP */ } } 14
  • 15.
  • 16.
    HTML5 WebSockets ➔ ➔ ➔ „TCP fürdas Web“ bidirektionale Kommunikation über eine TCP Verbindung eliminiert „HTTP-Overhead“ 16
  • 17.
  • 18.
    WebSocket – Handshake ➔ WebSocketVerbindung anfordern ➔ HTTP-Kompatibel  einfacher HTTP GET Request 18
  • 19.
    WebSocket – Handshake(Client) GET /chat HTTP/1.1 Host: server.example.com Upgrade: websocket Connection: Upgrade Sec-WebSocket-Key: dGhlIHNhbXBsZSBub25jZQ== Origin: http://example.com Sec-WebSocket-Protocol: foo, bar Sec-WebSocket-Version: 13 19
  • 20.
    WebSocket – Handshake(Server) HTTP/1.1 101 Switching Protocols Upgrade: websocket Connection: Upgrade Sec-WebSocket-Accept: s3pPLMBiTxaQ9kYGzzhZRbK+xOo= Sec-WebSocket-Protocol: bar 20
  • 21.
    WebSocket – JavaScriptAPI var websocket = new WebSocket('ws://ws.example.com/chat', ['foo', 'bar']); websocket.onopen = function () { websocket.send('Ping'); // Send the message 'Ping' to the server }; // process new message from the server websocket.onmessage = function (e) { console.log('Server: ' + e.data); }; // register error callback function websocket.onerror = function (error) { ... } 21
  • 22.
    WebSocket – JettyWebSocketServlet class ChatWebSocketServlet extends WebSocketServlet { def doGet(request: HttpServletRequest, response: HttpServletResponse) {} def doPost(request: HttpServletRequest, response: HttpServletResponse) {} def doWebSocketConnect(request: HttpServletRequest, protocol: String) = new ChatWebSocket() // shared resource (thread-safe !!!) private val members = new CopyOnWriteArraySet<ChatWebSocket>() } class ChatWebSocket extends WebSocket { ... } 22
  • 23.
    WebSocket – JettyWebSocketServlet class ChatWebSocket extends WebSocket { private var outbound: Outbound = null def onConnect(outbound: Outbound) { this.outbound = outbound } def onMessage(frame: Byte, data: String) { for(member <- members) { member.outbound.sendMessage(data) } } } def onDisconnect() { members.remove(this) } 23
  • 24.
    WebSocket – Probleme ➔ Wird(noch) nicht von allen Browsern unterstützt  ➔ WebSocket-Handshake wird von einigen ProxyServern unterbunden  ➔ daher Frameworks wie „Atmosphere“ verwenden, die einen automatischen „Fallback“ auf Comet bieten. „Connection: Upgrade“ wird nicht verstanden Low-Level API   Verbindungsabbrüche müssen selbst erkannt und repariert werden keine garantierte Verarbeitung von Nachrichten 24
  • 25.
    Live Demo „HelloWorld“ 25
  • 26.
    Live Demo -Architektur 26
  • 27.
  • 28.
  • 29.
  • 30.
  • 31.
    SPDY – spdy-jetty-httpModul ➔ SPDY wird von Jetty ab Version 7.6.2 und 8.1.2 unterstützt ➔ setzt OpenJDK 1.7+ voraus ➔ kann transparent aktiviert werden  keine Änderung der Applikation nötig 31
  • 32.
    SPDY – jetty-spdy.xml <Configureid="Server" class="org.eclipse.jetty.server.Server"> ... <Call name="addConnector"> <Arg> <New class="org.eclipse.jetty.spdy.http.HTTPSPDYServerConnector"> <Arg> <Ref id="sslContextFactory" /> </Arg> <Set name="Port">8443</Set> </New> </Arg> </Call> </Configure> 32
  • 33.

Hinweis der Redaktion

  • #6 seit 2005 wird die Technologie unter diesem Namen verwendet Konzept der asynchronen Datenübertragung ermöglicht es, HTTP-Anfragen durchzuführen, während eine HTML-Seite angezeigt wird. Somit kann die Seite verändert werden, ohne sie komplett neu zu laden. Restriktion: Nur Client kann aktiv Anfragen durchführen.
  • #7 Begriff wurde erstmals 2006 von Alex Russell geprägt erste Comet Applikationen wie Google Chat und später Google Docs.
  • #18 Jetty 7.x und 8.x (via WebSocketServlet) Tomcat 7.x (via WebSocketServlet) jWebSocket Server (standalone)