WebSockets

From BaseX Documentation
Revision as of 15:57, 1 October 2018 by Johannes Finckh (talk | contribs)
Jump to navigation Jump to search

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.

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. In contrast to 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 web.xml configuration file. It is possible to transmit all kind of data, binary or text.

Introduction

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

  • Enable the WebSocket servlet in the web.xml. You can set the maxIdleTime, maxTextMessageSize and maxBinaryMessageSize here too.
  • 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.
<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>
  • Annotate your specific XQuery-Functions with WebSocketAnnotations.

Annotations

ws:connect(path)

Called directly after a successful WebSocket handshake. The path specifies the path to which a client is connected to.

ws:message(path,message)

Called when a message arrives at the server. The path specifies the path to which a client is connected to. The message is the message sent by the client. Could be a text-message or a binary-message.

ws:close(path)

Called when the WebSocket closes. The path specifies the path to 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 to 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

  • 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 include the WsServlet in your web.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>

For establishing a connection to the WebSocket server it is necessary that the server provides at least a function annotated with %ws:connect("/"). 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, ...

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.