Difference between revisions of "WebSockets"

From BaseX Documentation
Jump to navigation Jump to search
Line 1: Line 1:
 
This page presents one of the [[Web Application]] services. It describes how to use the WebSockets API of BaseX. WebSocket is a communication protocol for providing full-duplex communication.  
 
This page presents one of the [[Web Application]] services. It describes how to use the WebSockets API of BaseX. WebSocket is a communication protocol for providing full-duplex communication.  
  
 +
=Introduction=
 +
 +
==WebSocket Protocol==
 
Use WebSockets if you have to exchange data with a high frequency or if you have to send messages from the server to the client without techniques like Polling.
 
Use WebSockets if you have to exchange data with a high frequency or if you have to send messages from the server to the client without techniques like Polling.
 +
In contrast to REST the WebSockets use a single URL for the whole communication.
  
The WebSocket protocol was standardized in [https://tools.ietf.org/html/rfc6455 RFC 6455] by the IETF. After an initial HTTP-request, all communication takes place over a single TCP connection. In contrast to the HTTP protocol, a connection will be kept alive, and a server can send unsolicited data to the client.
+
The WebSocket protocol was standardized in [https://tools.ietf.org/html/rfc6455 RFC 6455] by the IETF. After an initial HTTP-request, all communication takes place over a single TCP connection. Unlike the HTTP protocol, a connection will be kept alive, and a server can send unsolicited data to the client.
  
For establishing a WebSocket connection, a handshake request is sent by the client. The web server returns a handshake response. If the handshake is successful, the persistent connection will be open until the client or the server closes it, an error occurs or a timeout happens. The timeout can be specified in the <code>web.xml</code> configuration file. It is possible to transmit all kind of data, binary or text.  
+
For establishing a WebSocket connection, a handshake request is sent by the client. The web server returns a handshake response. If the handshake is successful, the persistent connection will be open until the client or the server closes it, an error occurs or a timeout happens. It is possible to transmit all kind of data, binary or text. '''The BaseX-WebServer handles the handshake completely.''' You just have to define some limits of the connection in the <code>web.xml</code> and specify functions for WebSocket events like ''onConnect'' and ''onMessage''.
  
=Introduction=
+
Notice that there is no specification of a message-protocol. The WebSocket protocol just specifies the message-architecture but not how the payload of the messages is formatted. To agree on a format between the server and the client one can use subprotocols.
  
 +
Some older browsers don't support the WebSocket protocol. Therefore you can use fallback options like Ajax. JavaScript clientlibarys like SockJs can be used for building client applications. The library takes care of how to establish the real-time connection. If the WebSocket protocol isn't supported it uses polling. You have to provide server functions for the fallback solutions if you have to support fallbacks.
 
==Preliminaries==
 
==Preliminaries==
  
Line 19: Line 24:
 
=Usage=
 
=Usage=
  
* Enable the WebSocket servlet in the <code>web.xml</code>. You can set the maxIdleTime, maxTextMessageSize and maxBinaryMessageSize here too.
+
* The WebSocket servlet has to be enabled in the <code>web.xml</code> (which is the default case). You can specify the maxIdleTime, maxTextMessageSize and maxBinaryMessageSize here too. Check the standard <code>web.xml</code> for further informations.  
 
* If you get a message that exceeds the maxTextMessageSize/maxBinaryMessageSize or, if not set, the default messageSize of Jetty of 65 536 bytes (64 kB) then the connection will be closed. In this case, the <code>ws:error</code> annotation will be called.  
 
* If you get a message that exceeds the maxTextMessageSize/maxBinaryMessageSize or, if not set, the default messageSize of Jetty of 65 536 bytes (64 kB) then the connection will be closed. In this case, the <code>ws:error</code> annotation will be called.  
 
+
* Annotate your specific XQuery-Functions with WebSocketAnnotations. You have to specify at least one WebSocket function.
<pre class="brush:xml">
 
<servlet>
 
  <servlet-name>wsservlet</servlet-name>
 
  <servlet-class>org.basex.http.ws.WsServlet</servlet-class>
 
  <init-param>
 
    <param-name>maxIdleTime</param-name>
 
    <param-value>100000</param-value>
 
  </init-param>
 
  <init-param>
 
    <param-name>maxTextMessageSize</param-name>
 
    <param-value>3000</param-value>
 
  </init-param>
 
</servlet>
 
<servlet-mapping>
 
  <servlet-name>wsservlet</servlet-name>
 
  <url-pattern>/ws/*</url-pattern>
 
</servlet-mapping>
 
</pre>
 
* Annotate your specific XQuery-Functions with WebSocketAnnotations.
 
  
 
=Annotations=
 
=Annotations=
 +
To write functions that act as WebSocket functions you have to use [[XQuery 3.0#Annotations|annotations]]. The annotation is written after the keyword ''declare'' and before the keyword ''function''.
 +
For the context of WebSockets there are some annotations listed below. Functions which are annotated with a WebSocket annotation will be called if the appropriate event occurs. For example the function annotated with <code>ws:connect('/')</code> will be executed if a client establishes a connection with the path <code>'/'</code>.
 +
By using annotations its easy to provide an API for your WebSocket connection. You just have to specify what to do when a WebSocket Event occurs, annotate it with the corresponding annotation and the Servlet will do the rest for you.
  
 
==ws:connect(path)==
 
==ws:connect(path)==
  
Called directly after a successful WebSocket handshake. The <code>path</code> specifies the path to which a client is connected to.
+
Called directly after a successful WebSocket handshake. The <code>path</code> specifies the path which a client is connected to.
 +
You can specify here how to handle your users, f.e. save a name as a WebSocket attribute. Furthermore, you can check here some of the header-params for validity.  
  
 
==ws:message(path,message)==
 
==ws:message(path,message)==
  
Called when a <code>message</code> arrives at the server. The <code>path</code> specifies the path to which a client is connected to. The <code>message</code> is the <code>message</code> sent by the client. Could be a text-message or a binary-message.
+
Called when a <code>message</code> arrives at the server. The <code>path</code> specifies the path which a client is connected to. The <code>message</code> is the <code>message</code> sent by the client. Could be a text-message or a binary-message. It should be ensured that the format of the message correct.  
  
 
==ws:close(path)==
 
==ws:close(path)==
  
Called when the WebSocket closes. The <code>path</code> specifies the path to which a client is connected to.
+
Called when the WebSocket closes. The <code>path</code> specifies the path which a client is connected to.
The WebSocket is already closed when this annotation is called so there can be no return.
+
The WebSocket is already closed when this annotation is called so there can be no return.  
  
 
==ws:error(path, message)==
 
==ws:error(path, message)==
  
Called when an error has occurred. Usually, this happens because of bad/malformed incoming packets. The <code>path</code> specifies the path to which a client is connected to. The <code>message</code> is the error message. The WebSocket connection gets closed after the error handling.
+
Called when an error has occurred. Usually, this happens because of bad/malformed incoming packets. The <code>path</code> specifies the path which a client is connected to. The <code>message</code> is the error message. The WebSocket connection gets closed after the error handling.
  
==ws:header-param(name,variable[,default]==
+
==ws:header-param(name,variable[,default])==
  
 
For accessing specific parameters like the Http-Version or the Sec-WebSocket-Version.
 
For accessing specific parameters like the Http-Version or the Sec-WebSocket-Version.
  
=Parameters=
+
===Parameters===
 
+
The following list shows the parameters of a WebSocket. You can access the parameters via the annotation <code>%ws:header-param(name,variable[,default])</code>
 
* Http-Version  -> f.e.: <code>%ws:param("Http-Version", "{$version}")</code>
 
* Http-Version  -> f.e.: <code>%ws:param("Http-Version", "{$version}")</code>
 
* Origin
 
* Origin
Line 89: Line 79:
 
The following chapter explains how to create a simple basic webapplication with websockets.  
 
The following chapter explains how to create a simple basic webapplication with websockets.  
 
You can find another example in the BaseX source code.  
 
You can find another example in the BaseX source code.  
First of all include the WsServlet in your <code>web.xml</code>.
+
First of all you have to ensure that the WsServlet is enabled in your <code>web.xml</code>. It will be enabled if you use the standard <code>web.xml</code>.  
 
 
<pre class="brush:xml">
 
<?xml version="1.0" encoding="UTF-8"?>
 
<web-app
 
  xmlns="http://xmlns.jcp.org/xml/ns/javaee"
 
  xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
 
  xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee http://www.oracle.com/webfolder/technetwork/jsc/xml/ns/javaee/web-app_4_0.xsd"
 
  version="4.0">
 
   
 
  <display-name>BaseX: The XML Database and XQuery Processor</display-name>
 
  <description>HTTP Services</description>
 
  <!-- Global session and servlet listener -->
 
  <listener>
 
    <listener-class>org.basex.http.SessionListener</listener-class>
 
  </listener>
 
  <listener>
 
    <listener-class>org.basex.http.ServletListener</listener-class>
 
  </listener>
 
 
 
  <!-- WebSocket Service (can be disabled by removing this entry) -->
 
  <servlet>
 
    <servlet-name>WebSocket</servlet-name>
 
    <servlet-class>org.basex.http.ws.WsServlet</servlet-class>
 
    <init-param>
 
    <param-name>maxTextMessageSize</param-name>
 
    <param-value>3000</param-value>
 
  </init-param>
 
  </servlet>
 
  <servlet-mapping>
 
    <servlet-name>WebSocket</servlet-name>
 
    <url-pattern>/ws/*</url-pattern>
 
  </servlet-mapping>
 
 
 
  <!-- Mapping for static resources (may be restricted to a sub path) -->
 
  <servlet>
 
    <servlet-name>default</servlet-name>
 
    <init-param>
 
      <param-name>useFileMappedBuffer</param-name>
 
      <param-value>false</param-value>
 
    </init-param>
 
  </servlet>
 
  <servlet-mapping>
 
    <servlet-name>default</servlet-name>
 
    <url-pattern>/static/*</url-pattern>
 
  </servlet-mapping>
 
</web-app>
 
</pre>
 
  
For establishing a connection to the WebSocket server it is necessary that the server provides at least a function annotated with <code>%ws:connect("/")</code>.
+
For establishing a connection to the WebSocket server it is necessary that the server provides at least one function annotated with a WebSocket annotation.
The connect function can set WebSocket specific attributes like the id and name, emit a message to other connected users, write database entries, do nothing, ...
+
Lets start by using the annotation <code>%ws:connect('/')</code>.
 +
In the connect function, specific WebSocket attributes like the id and name, emit a message to other connected users, write database entries, do nothing, ... can be set.
 +
 
<pre class="brush:xquery">
 
<pre class="brush:xquery">
 
declare
 
declare

Revision as of 14:13, 4 October 2018

This page presents one of the Web Application services. It describes how to use the WebSockets API of BaseX. WebSocket is a communication protocol for providing full-duplex communication.

Introduction

WebSocket Protocol

Use WebSockets if you have to exchange data with a high frequency or if you have to send messages from the server to the client without techniques like Polling. In contrast to REST the WebSockets use a single URL for the whole communication.

The WebSocket protocol was standardized in RFC 6455 by the IETF. After an initial HTTP-request, all communication takes place over a single TCP connection. Unlike the HTTP protocol, a connection will be kept alive, and a server can send unsolicited data to the client.

For establishing a WebSocket connection, a handshake request is sent by the client. The web server returns a handshake response. If the handshake is successful, the persistent connection will be open until the client or the server closes it, an error occurs or a timeout happens. It is possible to transmit all kind of data, binary or text. The BaseX-WebServer handles the handshake completely. You just have to define some limits of the connection in the web.xml and specify functions for WebSocket events like onConnect and onMessage.

Notice that there is no specification of a message-protocol. The WebSocket protocol just specifies the message-architecture but not how the payload of the messages is formatted. To agree on a format between the server and the client one can use subprotocols.

Some older browsers don't support the WebSocket protocol. Therefore you can use fallback options like Ajax. JavaScript clientlibarys like SockJs can be used for building client applications. The library takes care of how to establish the real-time connection. If the WebSocket protocol isn't supported it uses polling. You have to provide server functions for the fallback solutions if you have to support fallbacks.

Preliminaries

There are a bunch of annotations depending to WebSockets for annotating XQuery functions. When a WebSocket message arrives at the server, an XQuery function will be invoked that matches the constraints indicated by its annotations.

If a WebSocket function is requested (like connecting to the path /, sending a message to the path /path, …), the module directory and its sub-directories will be traversed, and all XQuery files will be parsed for functions with WebSocket annotations. Sub-directories that include an .ignore file will be skipped.

To speed up processing, the functions of the existing XQuery modules are automatically cached in main memory. For further information on cache handling, check out the RESTXQ introduction.

Usage

  • The WebSocket servlet has to be enabled in the web.xml (which is the default case). You can specify the maxIdleTime, maxTextMessageSize and maxBinaryMessageSize here too. Check the standard web.xml for further informations.
  • If you get a message that exceeds the maxTextMessageSize/maxBinaryMessageSize or, if not set, the default messageSize of Jetty of 65 536 bytes (64 kB) then the connection will be closed. In this case, the ws:error annotation will be called.
  • Annotate your specific XQuery-Functions with WebSocketAnnotations. You have to specify at least one WebSocket function.

Annotations

To write functions that act as WebSocket functions you have to use annotations. The annotation is written after the keyword declare and before the keyword function. For the context of WebSockets there are some annotations listed below. Functions which are annotated with a WebSocket annotation will be called if the appropriate event occurs. For example the function annotated with ws:connect('/') will be executed if a client establishes a connection with the path '/'. By using annotations its easy to provide an API for your WebSocket connection. You just have to specify what to do when a WebSocket Event occurs, annotate it with the corresponding annotation and the Servlet will do the rest for you.

ws:connect(path)

Called directly after a successful WebSocket handshake. The path specifies the path which a client is connected to. You can specify here how to handle your users, f.e. save a name as a WebSocket attribute. Furthermore, you can check here some of the header-params for validity.

ws:message(path,message)

Called when a message arrives at the server. The path specifies the path which a client is connected to. The message is the message sent by the client. Could be a text-message or a binary-message. It should be ensured that the format of the message correct.

ws:close(path)

Called when the WebSocket closes. The path specifies the path which a client is connected to. The WebSocket is already closed when this annotation is called so there can be no return.

ws:error(path, message)

Called when an error has occurred. Usually, this happens because of bad/malformed incoming packets. The path specifies the path which a client is connected to. The message is the error message. The WebSocket connection gets closed after the error handling.

ws:header-param(name,variable[,default])

For accessing specific parameters like the Http-Version or the Sec-WebSocket-Version.

Parameters

The following list shows the parameters of a WebSocket. You can access the parameters via the annotation %ws:header-param(name,variable[,default])

  • Http-Version -> f.e.: %ws:param("Http-Version", "{$version}")
  • Origin
  • Protocol-Version
  • QueryString
  • IsSecure
  • RequestURI
  • Host
  • Sec-WebSocket-Version
  • offset -> just for binary-Messages
  • len -> just for binary-Messages

Tipps

  • For interacting with other clients or manage specific clients you should check out the WebSocket Module as well.
  • The results of functions annotated with %ws:close or %ws:error will not be transmitted to the client
  • For keeping the connection alive it is possible to implement heart-beats
  • Use wss instead of ws for a secure WebSocket connection
  • If you use a proxy server, check its configuration if WebSocket support is enabled

Example

The following chapter explains how to create a simple basic webapplication with websockets. You can find another example in the BaseX source code. First of all you have to ensure that the WsServlet is enabled in your web.xml. It will be enabled if you use the standard web.xml.

For establishing a connection to the WebSocket server it is necessary that the server provides at least one function annotated with a WebSocket annotation. Lets start by using the annotation %ws:connect('/'). In the connect function, specific WebSocket attributes like the id and name, emit a message to other connected users, write database entries, do nothing, ... can be set.

declare
  %ws:connect('/')
function example:connect() as empty-sequence() {
  ()
};

With the state until now you can create a connection between client and server. For doing sth. senseful with the WebSocket connection you should implement a function annotated with %ws:message("/").

import module namespace ws = 'http://basex.org/modules/ws'
declare
  %ws:message('/', '{$message}')
function example:message(
  $message  as xs:string
) as empty-sequence() {
  ws:emit($message)
};

In the function above the WebSocketModule function emit is used for forwarding the message to all connected clients. Notice that you have to import the WebSocket Module before using it.

It is possible now to write client functions which connect to a WebSocket, send messages to the WebSocket and receive messages from the WebSocket. The following client example provides basic code for handling the WebSocket connection.

var ws = new WebSocket("wss://localhost:8984/ws");
ws.onmessage = function(event) {
  alert(event.data);
};

function send(message) {
  ws.send(message);
};

There are no heart-beats in this example. This means that the connection is terminated if nothing happens for 5 minutes (standard timeout). If you send a message which exceeds the textsize of 3kb defined in the web.xml the connection gets closed too.