tutorial

This is a beginner tutorial about writing http-based network applications by using xLightweb. If you need more help, don't hesitate to use xLightweb's support forum.

This tutorial also includes the experimental HTML5 support. Please consider that The HTML5 protocol is still in draft. The WebSockets and Server-Sent Events related classes of xlightweb are early implementations of the these protocol draft. Such classes and functionalities are subject to change.


1. Client-side http connection

2. HttpClient

3. HttpServer - Server-side http connection

4. Using xLightweb together with Spring

http protocol support

xLightweb supports writing http-based client and server applications. xLightweb supports synchronous, blocking HTTP as well as asynchronous non blocking HTTP. For a more detailed explanation on asynchronous, non-blocking HTTP programming refer to the article Asynchronous HTTP and Comet architectures

xLightweb is based on xSocket, a NIO, high performance network library. Formerly xLightweb was known as xSocket-http.

The http connection represents xLightweb's key abstraction. This class bases on a xSocket INonBlockingConnection instance to handle the tcp network communication. The integration of the http connection is highly optimized by reducing the overhead of layering.

A INonBlockingConnection can become a http connection at any time. This is true for the client-side as well as for the server-side.

.

HttpConnection.gif

1. Client-side http connection

A new http client-side connection can be established in two ways. Either based on a existing tcp connection by wrapping it

INonBlockingConnection tcpCon = new NonBlockingConnection("www.gmx.com", 80);
IHttpClientEndpoint httpClientConnection = new HttpClientConnection(tcpCon);

... or in a direct way.

IHttpClientEndpoint httpClientConnection = new HttpClientConnection("www.gmx.com", 80); 

The http client connection allows to send requests to the server and to receive responses from the server in a comfortable way. The http connection supports synchronous calls as well as a asynchronous, handler-based communication model. In contrast to the asynchronous approach, the current thread will be suspended within a synchronous call until the response header of the server-side has been received.

Creating a new HttpClientConnection is a synchronous operation. This means the send or call method can be called immediately after creating the connection. A detailed explanation to perform an asynchronous connect can be found in xSocket’s core tutorial. In this case a NonBlockingConnection will be used to setup a connection in an asynchronous way. After finishing the connect procedure an HttpClientConnection will be instantiated based on the NonBlockingConnection instance.

1.1 Synchronous client call

To perform a synchronous call a request object has to be created and the HttpClientConnection's call method has to be performed. xLightweb supports a generic Request object as well as more comfortable, dedicated request objects such as PostRequest or GetRequest.

To request object provides (Servlet API compatible) method to access header data. By sending the request xLightweb adds the HTTP 1.1 mandatory request header parameters, if they are not present. By using the http connection, the host and port parameter within the request URL is optional. If such parameters are not present the remote host or remote port of the underlying connection will be used to add the http request header host field.

IHttpRequest request = new GetRequest("http://www.gmx.com/index.html");

// add request header data
request.setHeader("Accept-Encoding", "gzip,deflate");

// perform the request
IHttpResponse response = httpClientConnection.call(request);
        
// get repsonse header data 
String contentType = response.getContentType();
//... 

The call method sends the request to the server and returns a response object by receiving the response header from the server. The response object provides several convenience methods to retrieve the header data. The body data can be access by using the get body methods. The returned body data source object implements some convenience methods to read the body data such as read<dataType>().

//...

BodyDataSource bodyDataSource = response.getBody();
int i = bodyDataSource.readInt();

//... 

The data source object also implements the ReadableByteChannel interface of the java.nio package. If a InputStream is required instead of a ReadableByteChannel the java.nio.Channels.newInputStream(<readableChannel>) method can be used to wrap the ReadableByteChannel by an InputStream.

//...

ReadableByteChannel bodyDataSource = response.getBody();
InputStream is = Channels.newInputStream(bodyDataSource);

//... 

By returning from the call method, it is not ensured, that the (complete) response body has been received. The method returns immediately after the complete response header has been received. For this reason xLightweb provides two different getBody methods.

1.2 Retrieve the response body data in a blocking mode

A body handle which implements a blocking access manner can be retrieved by calling the getBody method. This method returns a BodyDataSource. By calling the data retrieve methods of the data source object, the method call blocks until enough body data is available. By retrieving more sufficient body data or by reaching the timeout, the method call returns (in the case of timeout by throwing a timeout exception).
BodyDataSource bodyDataSource = response.getBody();
String data = bodyDataSource.readStringByLength(length);

//... 

1.3 Retrieve the body data in a non-blocking mode

In contrast to the blocking access behaviour, a NonBlockingBodyDataSource will return immediately (possible by throwing a BufferUnderflowException if the available data size is to small). A non blocking data source will be retrieved by calling the getNonBlockingBody method.

To get notifications by receiving more body data a IBodyDataHandler can be set at any time for a data source object.

NonBlockingBodyDataSource bodyDataSource = response.getNonBlockingBody();

//...
IBodyDataHandler bodyHandler = new BodyToFileStreamer(file);
response.getNonBlockingBody().setDataHandler(bodyHandler); 

The data handler has to implement a call back method onData, which will be called each time by a worker thread when new data has been received. Using a data handler allows to implement a non blocking streaming of the body data. Especially for large bodies this can increase the efficiency significantly by avoiding blocking reads (which causes wasting resources by suspending threads).

class BodyToFileStreamer implements IBodyDataHandler {

   private final RandomAccessFile;
   private final FileChannel fc;
   private final File file;

   BodyToFileStreamer(File file) throws IOException {
      this.file = file;
      raf = new RandomAccessFile(file, "rw"); 
      fc = raf.getChannel();
   } 

   public boolean onData(NonBlockingBodyDataSource bodyChannel) {
      try {
         int available = bodyChannel.available();
   
         // data to transfer?
         if (available > 0) { 
            bodyChannel.transferTo(fc, available);
            
         // end of stream reached?
         } else if (available == -1) {
            fc.close();
            raf.close();
         }
      } catch (IOException ioe) {
         file.delete();
      }
      return true;
   }
} 

The body handler supports the Execution annotation. The Execution annotation allows to define if the body handler should be called in a MULTITREADED or NONTHREADED mode. The default mode is MULTITREADED. For more information about the execution mode see xSocket’s core tutorial.

class MyBodyHandler implements IBodyDataHandler {

   @Execution(Execution.NONTHREADED) // could also be set on the class level
   public boolean onData(NonBlockingBodyDataSource bodyDataSource) {
      // perform some non I/O bound operations
      // ...
      return true;
   }
} 

Using data handler allows reading the body data in a very efficient way. But by performing a synchronous call, the current thread will be suspended until the response header has been received. To avoid this a asynchronous call could be performed.

1.4 Read a multipart record in a non-blocking mode

It is not predictable how many bytes will be received, if the onData() method is called. This has to be considered by handling multipart records. By handling such a multipart record normally several read methods will be performed. However, by calling a read method such as readInt() or readStringByDelimiter() a BufferUnderflowException can occur at any time.


record.gif

In the example below the read mark will be set before reading the record. By marking the read position the result data of all following read operations will be duplicated and buffered internally. This is necessary to reconstruct the read buffer by resetting to the read mark. If a read method is called, a BufferUnderflowException can occur at any time. If such an exception occurs, it will be catch and the read position will be reset to the origin marked position. If the complete record is read the read mark will be removed. The removeReadMark() method cleans the read mark buffer. The read mark support can be used to implement a lightweight "transaction" (read all or nothing) support

class MultipartRecordHandler implements IBodyDataHandler {

   public boolean onData(NonBlockingBodyDataSource nbc) {

      try {

         ////////////
         // "transaction" start
         //

         // mark the read position
         nbc.markReadPosition();  



         // try to read the header data (BufferUnderflowException can been thrown by any read method)
         byte recordType = nbc.readByte();
         int version = nbc.readInt();
         String data  = nbc.readStringByDelimiter("\r\n");


         // got the complete header (BufferUnderflowException hasn't been thrown) -> remove read mark
         nbc.removeReadMark();

         //
         // "transaction" end
         ///////////////


         if (version == 1) {
            handleRecord(recordType, data);

         // ..
         } else {

         }

         return true;

      } catch (BufferUnderflowException bue) {
         // reset to origin read position  
         nbc.resetToReadMark();


      } catch (IOException ioe) {
         // handle the exception
         nbc.destroy();
      }
      return true;
   }

   private void handleRecord(byte recordType, String data) {
      // ...
   }
}

For more information about the multipart records see also xSocket’s core tutorial.

1.5 Asynchronous client call - Future style

Asynchronous response handling will be supported in two ways. Either you implement a IHttpResponseHandler which will be called if the response is received or you uses IFutureHandler. The IHttpEndpoint supports a send method which returns an IFutureResponse instance. The IFutureResponse supports a getResponse() method to retrieve the response. This method blocks until the response header is received. Beside the getResponse() additional methods such as isDone() or cancel() exsits which are defined by the java.util.concurrent.Future interface.

HttpClientConnection con = new HttpClientConnection(server, port);
GetRequest request = new GetRequest(url);

IFutureResponse futureResponse = con.send(request);
// .. do something else

// get the response of the outstanding request. This method blocks until the request (header) is received
IHttpResponse response = futureResponse.getResponse();  

// ...    

1.6 Asynchronous client call - Callback-Handler style

Beside the Future approach a IHttpResponseHandler can be used which will be called, if the response is recevied. In this case the response handler’s onResponse method will be called by a worker thread. The handler implements also an onException method which will be called, if an exception occurs.

class MyResponseHandler implements IHttpResponseHandler {

   public void onResponse(IHttpResponse response) throws IOException {
      int status = response.getStatus();
      // ...
   }

   public void onException(IOException ioe) {
      //...
   }
}

The request will be send by calling the send method. The method returns immediately.

IHttpResponseHandler responseHandler = new MyResponseHandler();

IHttpRequest request = new GetRequest("http://www.gmx.com/index.html");
httpClientConnection.send(request, responseHandler);        

//... 

The response handler supports the Execution and InvokeOn annotation. The InvokeOn annotation allows defining if the onResponse method should be called by receiving the message header (default) or after receiving the complete message inclusive the body data. The InvokeOn annotation can be set on the class level as well as on the method level.

@Execution(Execution.NONTHREADED) // could be also set on the method level
class MyResponseHandler implements IHttpResponseHandler {

   @InvokeOn(InvokeOn.MESSAGE_RECEIVED)
   public void onResponse(IHttpResponse response) throws IOException {
      NonBlockingBodyDataSource bodyDataSource = response.getNonBlockingBody();
      //...
   }

   public void onException(IOException ioe) {
      //...
   }
}

The asynchronous call feature allows you to perform requests regarding to the HTTP pipelining specification. Here multiple http requests are written out to a single http connection without waiting for the corresponding responses.

1.7 Asynchronous client call by streaming the request body

To support a non blocking streaming of the request body send methods exist which consumes a RequestHeader instead of the complete Request object. This method return a BodyDataSink object which will be used to write the body data in a non blocking way. The data sink object provides convenience methods and implements the WritableByteChannel interface of the java.nio package as well as the Flushable interface of the java.io package.

Calling the send method doesn't send the header data to the server immediately. The header data will be written by the first data sink flush operation. In the same way the body data will only be send by flushing the data sink. Closing the data sink also performs a flush, internally.

If no length parameter will be passed over by calling the send method, the body data will be send in a chunked mode. To send data in the plain mode the body length field has to be passed over. The close method has also to be performed to finish the send procedure.

// create a response handler
IHttpResponseHandler hdl = new MyResponseHandler();

// get the file to transfer
RandomAccessFile raf = new RandomAccessFile("test.txt", "rw";
FileChannel fc = raf.getChannel();
int bodyLength = (int) fc.size();      

// create a new request header
IHttpRequestHeader header = new HttpRequestHeader("POST", "http://server:80/in", "text/plain");

// start sending in non-chunked mode
BodyDataSink bodyDataSink = httpClientConnection.send(header, bodyLength, hdl);

// use the transfer method to write the body data
bodyDataSink.transferFrom(fc);

// finish the send procedure
bodyDataSink.close();

The flush behaviour can be controlled by setting the flush mode (SYNC, ASYNC). For more information about flushing see xSocket’s core tutorial. By default the autoflush is activated. That means, each write operation performs a flush, implicitly (and the header will be written within the first write operation). The autoflush can be deactivated by calling the setAutoflush method.

IHttpResponseHandler responseHandler = new MyResponseHandler();

// create a new request header
IHttpRequestHeader header = new HttpRequestHeader("POST", "http://server:80/in", "text/plain");
header.setHeader("Accept-Encoding", "gzip,deflate");

// start sending in chunked mode
BodyDataSink bodyDataSink = httpClientConnection.send(header, responseHandler);

// set some data sink properties
bodyDataSink.setAutoflush(false);            // deactivate the autoflush 
bodyDataSink.setFlushmode(FlushMode.ASYNC);  // override default flush mode 'SYNC'

// writing and flushing data 
bodyDataSink.write(...);
bodyDataSink.flush();
//...

// finish the send procedure
bodyDataSink.close();

Sometime write streaming is required, but the request should be received in a synchronous mode. This can be done by passing over a FutureResponseHandler. This response handler provides a blocking getResponse() method, which will return if the response header is received.

HttpClient httpClient = new HttpClient();

FutureResponseHandler respHdl = new FutureResponseHandler();
BodyDataSink bodyDataSink = httpClient.send(new HttpRequestHeader("POST", "http://serv/...", "text/plain"), 1000, respHdl);
bodyDataSink.write(...);
// ...

IHttpResponse response = respHdl.getResponse();
// ...

1.8 Handling timeouts

By sending requests 3 types of timeouts can be set:

  • ConnectionTimeout: the max life time of a connection, independent, if messages or data will be received or sent.
  • ResponseTimeout: the max time between the request send procedure is started and the response header is received.
  • BodyDataReceiveTimeout: the max time between receiving data packages of the response body.

ResponseTimeout.gif

If a timeout occurs, the connection will be closed. This is independent of the timeout type. By implementing the IHttpSocketTimeout interface, the onException(SocketTimeoutException) method will be called instead of the common onException(IOException), if a ResponseTimeout or a ConnectionTimeout occurs.

class MyHandler implements IHttpResponseHandler, IHttpSocketTimeoutHandler {

   public void onResponse(IHttpResponse response) throws IOException {
      // ...
   }

   public void onException(IOException ioe) {
      // ...
   }

   public void onException(SocketTimeoutException stoe) {
     // response timeout occured
     // ...
   }
}
        

A BodyDataReceivedTimeout will lead to a ClosedChannelException, if a body data source read method is called.

The timeouts can be set for a connection

HttpClientConnection con = new HttpClientConnection("www.gmx.com", 80);

con.setConnectionTimeoutMillis(24L * 60L * 60L * 1000L);
con.setResponseTimeoutMillis(2L * 60L * 1000L);
con.setBodyDataReceiveTimeoutMillis(30L * 1000L);
 
//...

The body data receive timeout can also be set for a dedicated message (only useful for InvokeOn.HEADER_RECEIVED case)

//...
message.getNonBlockingBody().setBodyDataReceiveTimeoutMillis(30L * 1000L);
        

1.9 (Un)Compressing -Support

Most Request classes such as PostRequest or PutRequest supports creating compressed bodies content. If the constructor parameter compress is set to true, the body content will be (gzip) compressed and the header Content-Encoding: gzip will be added.

PostRequest request = new PostRequest(url, "text/plain", bodyString, true);    
IHttpResponse response = httpClient.call(request);

PostRequest request = new PostRequest(url, myFile, true);    
IHttpResponse response = httpClient.call(request);

 ...        

Responses will be uncompressed by default. If a (gzip) compressed response is received, the body will be uncompressed, automatically. The uncompress support can be deactivated by setting autoUncompress to false.

HttpClientConnection con = new HttpClientConnection("www.gmx.com", 80);
con.setAutoUncompress(false); // deactivate the uncompress support

 ...        

1.10 Client-side HTTP 100-Continue handling

The 100-continue approach is a very efficient approach to avoid sending large bodies, if the server is not willing to perform the request (redirect response, error response, ...). If the client request contains a Expect: 100-continue header, xLightweb will wait for a 100 continue response of the server before sending the request body. If the server does not send an interim 100 continue response, the body will be send after a timeout (default 3 sec).

PostRequest request = new PostRequest(url, "text/plain", data);
request.setHeader("Expect", "100-Continue");
       
IHttpResponse response = con.call(request);

xLightweb does support app-level 100 continue for send(header, ...) methods. For the send(header, ...) methods a Expect: 100-continue header will be removed by xLightweb, if the response handler is not annotated with @Supports100Continue.

@Supports100Continue
class ResponseHandler implements IHttpResponseHandler {

   private BodyDataSink dataSink; 
        
   public void onResponse(IHttpResponse response) throws IOException {
            
      if (response.getStatus() == 100) {
         dataSink.write(data);
         dataSink.close(); 

      } else {

          // ...
      }
   }


   // ...
}


ResponseHandler respHdl = new ResponseHandler();

BodyDataSink dataSink = httpClientConnection.send(request, respHdl);        
respHdl.setDataSink(dataSInk);
dataSink.flush();
        

1.11 Multipart support

Beside request classes such as GetRequest, PutRequest, PostRequest or FormURLEncodedRequest xLightweb also supports a dedicated MultipartRequest class.

MultipartRequest req = new MultipartRequest("POST", uri);
   
Part part = new Part(myFile);
req.addPart(part);
   
Part part2 = new Part("text/plain", myText);
req.addPart(part2);
  
IHttpResponse resp = httpClient.call(req);
// ...

The MultipartFormDataRequest is a specialization of the MultipartRequest class.

MultipartFormDataRequest req = new MultipartFormDataRequest(uri);
        
req.addPart("file", file);
req.addPart("description", "text/plain", "A unsigned ...");

IHttpResponse resp = httpClient.call(req);
// ...

Streaming multiparts is supported by the BodyDataSink writePart(...) methods. In contrast to the MultipartRequest class the content will be streamed immediately.

FutureResponseHandler respHdl = new FutureResponseHandler();
BodyDataSink bodySink = httpClient.send(new HttpRequestHeader("POST", uri), respHdl);
        
BodyDataSink partSink1 = bodySink.writePart(new HttpMessageHeader("text/plain"));
partSink1.write(myText);
partSink1.close();
       
BodyDataSink partSink2 = dataSink.writePart(new HttpMessageHeader("text/html")); 
partSink2.transferFrom(myFileChannel);
myFileChannel.close();
partSink2.close();
        
bodySink.close();


// retrieve the response
IHttpResponse resp = respHdl.getResponse();

//...

Mutlipart handling is also supported for the Response. Multiparts can be access in a blocking way by calling the readParts() or readPart() method.

// ...
IHttpResponse resp = httpClient.call(req);

List<IPart> parts = resp.getBody().readParts();
//...

A non blocking access is also supported through the NonBlockinBody

class MyPartHandler implements IPartHandler {

   public void onPart(NonBlockingBodyDataSource dataSource) throws IOException {
      IPart part = dataSource.readPart();
      //...
   }
}


// ...
IHttpResponse resp = httpClient.call(req);

MyPartHandler partHdl = new MyPartHandler();
resp.getNonBlockingBody().setBodyPartHandler(partHdl);

//...

2. HttpClient

Often a higher level representation is desirable to perform client-side http requests. By using the HttpClient the http connection management will be handled automatically.This means creating, (re)using and deleting of the IHttpClientConnections will be done by the HttpClient. The HttpClient uses a connection pool, internally. To give control over the pooling behaviour the HttpClient implements the IConnectionPool interface. For more information about connection pooling see xSocket’s core tutorial.

HttpClient.gif

Equals to the HttpClientConnection the HttpClient implements the IHttpClientEndpoint interface which defines the client-side call and send methods. The code to send requests is like the code described in the chapters above.

IHttpClientEndpoint httpClient = new HttpClient();

IHttpRequest request = new GetRequest("http://www.gmx.com/index.html");
// request = new GetRequest("/index.html"); wouldn't work here!

// add request header data
request.setHeader("Accept-Encoding", "gzip,deflate");

// perform the request
IHttpResponse response = httpClient.call(request);
        
// get response header data 
String contentType = response.getContentType();
//...

httpClient.close();

After using the HttpClient it should be closed. By closing the HttpClient the internal pool watch dog thread will be terminated.

To call a https based URL an SSLContext object has to be passed over within the HttpClient constructor. The SSLContext object will be used to establish HTTPS connections. In a JVM 1.6 (or higher) environment the default SSLContext will be loaded automatically.

// pass over the SSL context (here the JSE 1.6 getDefault method will be used)
HttpClient httpClient = new HttpClient(SSLContext.getDefault());

// make some configurations// make some configurations
httpClient.setMaxIdle(3);                   // configure the pooling behaviour
httpClient.setFollowsRedirect(true);        // set follow redirects
ConnectionUtils.registerMBean(httpClient);  // register the http client's mbean
httpClient.setAutoHandleCookies(false);     // deactivates auto handling cookies

// create a request


PostRequest request = new PostRequest("https://login.web.de/intern/login/", "application/x-www-form-urlencoded");
request.setParameter("username", "me"); 
request.setParameter("password", "I don not tell you"); 
        
// call it (by following redirects)
IHttpResponse response = httpClient.call(request);

// get the redirected response
BodyDataSource bodyDataSource = response.getBody();
//...

By registering the HttpClient's mbean you can get information about the HttpClient

HttpClientJConsole.gif

2.1 Retry-Handling

The HTTP methods GET, PUT and DELETE are idempotent. An idempotent method means that the result of a successful performed request is independent of the number of times you execute it. For this reason the HttpClient will re-executed such methods automatically in the case of a network error (this is also true for the HttpClientConnection).

Per default the call method will be returned, if the response header is received. The HttpClient will never retry a method, if (part of) the response message is visible to the user. This means if an network error occurs by receiving the response body, the HttpClient will not re-execute the method. By setting CallReturnOnMessage to true, the call method will return, if the complete response is received. In this case the HttpClient will re-execute the method an any time.

HttpClient httpClient = new HttpClient();
httpClient.setCallReturnOnMessage(true);  
httpClient.setMaxRetries(2);

IHttpResponse response = httpClient.call(new GetRequest("http://www.gmx.com/index.html"));
//...

The number of max retries can be overriden by setting maxRetries (default: 4)

2.2 Cookies handling

Cookies will be auto-handled, if autoHandleCookies is true (default: true)

HttpClient httpClient = new HttpClient();
httpClient.setAutoHandleCookies(true);

 ...

2.3 Redirect support

Auto-redirect will be supported, if followsRedirect is true (default is false). The number of max redirects can be overriden by setting maxRedirects (default: 5)

HttpClient httpClient = new HttpClient(); 
httpClient.setFollowsRedirect(true);
httpClient.setMaxRedirects(2);

 ...

2.4 Client-side caching support

The HttpClient supports client-side HttpCaching. By default the the client-side HttpCache is deactivated (maxSizeKB = 0). If the property maxSizeKB is set larger than 0, cacheable responses will be stored in a internal, in-memory cache. The cache implements a LRU-strategy.

HttpClient httpClient = new HttpClient(); 
httpClient.setCacheMaxSizeKB(2000); 

 ...

2.5 Connect through a Proxy

The HttpClient supports calling servers through a proxy . The proxy's address will be set by performing the setProxyHost(...) and setProxyPort(...) methods. If the proxy requires proxy authentication the username and password will be set by performing the setProxyUser(...) and setProxyPassword(...) methods.

HttpClient httpClient = new HttpClient();

httpClient.setProxyHost(myProxyHost);
httpClient.setProxyPort(myProxyPort);
httpClient.setProxyUser(username);
httpClient.setProxyPassword(password);

IHttpResponse response = httpClient.call(new GetRequest("http://www.gmx.com/index.html"));
//...

2.6 Interceptor support

The HttpClient also supports intercepting the request by custom interceptors. This will be done by calling the setInterceptor method.

// create the HttpClient and adds a interceptor
HttpClient httpClient = new HttpClient();

httpClient.addInterceptor(new HeaderLogFilter());

// perform the request
IHttpResponse response = httpClient.call(new GetRequest("http://www.gmx.com/index.html"));

// handle the response
// ...
 

The addInterceptor method accepts a IHttpRequestHandler instance, which will be described later in the server-side section. The onRequest method will be called by sending the HttpRequest. The example below shows how the request and response headers can be logged.

class HeaderLogFilter implements IHttpRequestHandler {

   public void onRequest(final IHttpExchange exchange) throws IOException {

      // print out request header 
      System.out.println(exchange.getRequest().getRequestHeader());
 

      IHttpResponseHandler respHdl = new IHttpResponseHandler() {

         public void onResponse(IHttpResponse response) throws IOException {
            // print out response header
            System.out.println(response.getResponseHeader());
            exchange.send(response);
        }

         public void onException(IOException ioe) {
            System.out.println("error occured by receiving response " + ioe.toString());
            exchange.sendError(500);
         }
      };

      exchange.forward(exchange.getRequest(), respHdl);
   }
}  

xLightweight supports also accessing the message body by fowarding it.

class MessageLogFilter implements IHttpRequestHandler {

   @InvokeOn(InvokeOn.MESSAGE_RECEIVED)
   public void onRequest(final IHttpExchange exchange) throws IOException {

      // print out request header
      System.out.println(exchange.getRequest().getRequestHeader());
      if (exchange.getRequest().hasBody()) {
         System.out.println(exchange.getRequest().getNonBlockingBody());
      } 


      IHttpResponseHandler respHdl = new IHttpResponseHandler() {

         @InvokeOn(InvokeOn.MESSAGE_RECEIVED)
         public void onResponse(IHttpResponse response) throws IOException {
            System.out.println(response.getResponseHeader());
            if (response.hasBody()) {
               System.out.println(exchange.getRequest().getNonBlockingBody());
            }

            exchange.send(response);
         } 

         public void onException(IOException ioe) {
            System.out.println("error occured by receiving response " + ioe.toString());
            exchange.sendError(500);
         }
      };

      exchange.forward(exchange.getRequest(), respHdl);
   }  
} 

2.7 Client-side Server-Sent Events

A new Server-Sent Event stream will be opened by calling the openEventDataSource(..) method of the XHttpClient.

XHttpClient httpClient = new XHttpClient();

IEventDataSource eventSource = client.openEventDataSource(url);
// ...

2.7.1 Reading an event

A message is read by calling the readMessage() method. This method is a blocking method, which means it will block as long as at minimum one event is available. To avoid blocking read calls, first the availableMessages() method can be performed to get the number of available messages.

//...
int available = eventSource.availableMessages();

// ...
Event event = ds.readMessage();
String data = event.getData(); 
//...

2.7.2 Reading an event in a asynchronous way

If an WebSocketConnection is created with an IEventHandler instance, the event handler’s method onMessage(…) will called each time a new event is received/available. The handler also defines a onConnect(…) and a onDisconnect() method which will be called once, a the event-stream is established or closed logically. This means re-establishing a physically connection in case of a network error does not trigger the onConnect() method.

Similar to the IHttpRequestHandler/IHttpResponse handler interface the handler can be annotated with the Execution mode.

class MyEventHandler implements IEventHandler {

   public void onConnect(IEventDataSource webEventDataSource) throws IOException {
   }
        
   public void onMessage(IEventDataSource webEventDataSource) throws IOException {
      Event event = webEventDataSource.readMessage();
      // ...
   }
        
   public void onDisconnect(IEventDataSource webEventDataSource) throws IOException {
   }            
}

IEventDataSource eventSource = client.openEventDataSource(url, new MyEventHandler());
// ...            

2.8 Client-side WebSockets

A new Web socket connection will be established by creating a IWebSocketConnection instance

IWebSocketConnection webSocketConnection = new WebSocketConnection("ws://localhost:8877/services", "net.example.myprotocol");

.. or by calling the openWebSocketConnection(..) method of the XHttpClient.

XHttpClient httpClient = new XHttpClient();

// ...
IWebSocketConnection webSocketConnection = httpClient.openWebSocketConnection("ws://localhost:8877/services")

The XHttpClient is an extension of the HttpClient which supports web socket related methods.

2.8.1 Sending a WebSocket message

The current web sockets protocol defines one type of messages (data frames): text messages. xLightweb includes the correlating class TextMessage. A message will be send by performing the write message method a WebSocketConnection instance.

TextMessage msg = new TextMessage("Hello");
webSocketConnection.writeMessage(msg);
        
// ...

2.8.2 Sending a WebSocket message in a asynchronous way

Messages can also be sent in a asynchronous way. Here an instance of IWebSocketWriteCompleteHandler has to pass over. If the message is written, the onWritten(...) will be called. In an write error is occurred, the onException(…) will be called.

class MyWriteCompleteHandler implements IWriteCompleteHandler {
            
   public void onWritten(int written) {
       // ..
   }
            
   public void onException(IOException ioe) {
       //..
   }
}
        
        
MyWriteCompleteHandler ch = new MyWriteCompleteHandler();
webSocketConnection.writeMessage(msg2, ch);

// ..

2.8.3 Reading a message

A message is read by calling the readTextMessage() method. This method is a blocking method, which means it will block as long as at minimum one message is available. To avoid blocking read calls, first the availableMessages() method can be performed to get the number of available messages.

//...
int available = webSocketConnection.availableMessages();
// ...

TextMessage msg = webSocketConnection.readTextMessage();
//...

2.8.4 Reading a message in a asynchronous way

If an WebSocketConnection is created with an IWebSocketHandler instance, the web socket handler’s method onMessage(…) will called each time a new message is received/available. The handler also defines a onConnect(…) and a onDisconnect method which will be called once, a connection is established or destroyed. Similar to the IHttpRequestHandler/IHttpResponse handler interface the handler can be annotated with the Execution mode.

class MyWebSocketHandler implements IWebSocketHandler {
            
   public void onConnect(IWebSocketConnection con) throws IOException, UnsupportedProtocolException {
   }
            
   public void onMessage(IWebSocketConnection con) throws IOException {
      TextMessage msg = con.readTextMessage();
      // ...
   }
            
   public void onDisconnect(IWebSocketConnection con) throws IOException {                
   }
}

        
IWebSocketConnection webSocketConnection = httpClient.openWebSocketConnection(wsURI, new MyWebSocketHandler());
// ...         

3. HttpServer - Server-side http connection

3.1 Server handler

To handle incoming http request on the server side a server handler has to be implemented. The IHttpRequestHandler interface defines an onRequest method, which will be called, each time a new request is received by the server.

By calling the method the IHttpExchange will be passed over. This object encapsulates a HTTP request received and a response to be generated in one exchange. It provides methods to retrieve and forward the the request, and to send the response. The response will be send by calling the send(Response) method. An error response can also be send by using the sendError(...) method.

class MethodInfoHandler implements IHttpRequestHandler {

   public void onRequest(IHttpExchange exchange) throws IOException, BadMessageException {

      IHttpRequest req = exchange.getRequest();
      // ...
   
      // create a response object
      IHttpResponse resp = new HttpResponse("text/plain", "called method=" + req.getMethod());
      
      // ... and send it back to the client
      exchange.send(resp);
   }
}


Equals the response object discussed above, the request object supports a getNonBlockingBody() method as well as a getBody() method. By using the getBody() method it has to be considered, that the body data retrieve methods blocks which causes that the current thread can be suspended. Retrieving the body by the getBody() method should never be used within the NONTHREADED mode.

Based on the server handler a server can be instantiated.

// activates detailed message output for debugging purposes
System.setProperty("org.xlightweb.showDetailedError", "true");

// creates the server by passing over the port number & the server handler
HttpServer srv = new HttpServer(80, new MethodInfoHandler());
 
// run it
srv.run();

// ... or start it by using a dedicated thread
srv.start(); // returns after the server has been started

By setting the system propertiy org.xlightweb.showDetailedError to true, a detailed error page will be returned from the server, if an error occurs. This is very useful for debugging purposes. For instance by using convenience methods such as getRequiredIntParameter(...) the stack trace and the mandatory parameter name will be part of the response error page. A missing mandatory parameter leads to a BadMessageException. If the onRequest() method implementation does not catch this exception, xlightweb will return a status 400 error page.

class MyRequestHandler implements IHttpRequestHandler {

   public void onRequest(IHttpExchange exchange) throws IOException, BadMessageException {

      IHttpRequest req = exchange.getRequest();

      // will throw a BadMessageException, if the parameter 'id' is not present
      Integer customerId = request.getRequiredIntParameter("id");
      // ...
  
   }
}

3.2 Message Forwarding (static proxy example)

The client and server-side support is high integrated. For this reason writing a proxy becomes an easy part. On both sides, the same programming style and classes will be used.

proxy.gif

For this reason it is very easy to write http proxies or routers which typically receives, modifies and forwards http messages. xLightweb implements a lot of optimizations to support such UseCases . For instance if a message is received and forwarded, xLightweb will stream the body internally in a non-blocking way.

class ForwardHandler implements IHttpRequestHandler, ILifeCycle {

   private HttpClient httpClient = null;  
   private String host = null;
   private int port = -1;

   ForwardHandler(String targetHost, int targetPort) {
      host = targetHost;
      port = targetPort;
   }
   
   public void onInit() {
      httpClient = new HttpClient();
      httpClient.setAutoHandleCookies(false);  // cookie auto handling has to be deactivated!
      httpClient.setFollowsRedirect(false);
      httpClient.setAutoUncompress(false);
   }
   
   public void onDestroy() throws IOException {
      httpClient.close();
   }

   @Execution(Execution.MULTITHREADED)
   public void onRequest(IHttpExchange exchange) throws IOException, BadMessageException {

      IHttpRequest req = exchange.getRequest();

      // reset address (Host header will be update automatically)
      URL url = req.getRequestUrl();
      req.setRequestUrl(new URL(url.getProtocol(), host, port, url.getFile()));

      // perform further proxy issues (via header, cache, remove hop-by-hop headers, ...)
      // ...


      // .. and forward the request
      try {
         httpClient.send(req, new ReverseHandler(exchange));
      } catch (ConnectException ce) {
         exchange.sendError(502, ce.getMessage());
      }
   }
}

The ReverseHandler implements the IHttpResponseHandler interface as well as the IHttpSocketTimeoutHandler interface. The onException(SocketTimeoutException) method will be called, if the response timeout is reached. The response timeout can be set on the HttpClient and on the HttpClientConnection class.

class ReverseHandler implements IHttpResponseHandler, IHttpSocketTimeoutHandler {

   private IHttpExchange exchange = null;

   public ReverseHandler(IHttpExchange exchange) {
      this.exchange = exchange;
   }

   @Execution(Execution.NONTHREADED)
   @InvokeOn(InvokeOn.HEADER_RECEIVED)
   public void onResponse(IHttpResponse resp) throws IOException {

      // handle proxy issues (hop-by-hop headers, ...)
      // ...

      // return the response 
      exchange.send(resp);
   }

   @Execution(Execution.NONTHREADED)
   public void onException(IOException ioe) {
      exchange.sendError(500, ioe.toString());
   }

   @Execution(Execution.NONTHREADED)
   public void onException(SocketTimeoutException stoe) {
      exchange.sendError(504, stoe.toString());
   }
}

Another HttpProxy example can be found here

3.3 Handler chaining

By using a RequestHandlerChain handlers can be chained. The RequestHandlerChain implements the IHttpRequestHandler interface and will be handled as a regular IHttpRequestHandler implementation.

// define a chain 
RequestHandlerChain chain = new RequestHandlerChain();
chain.addLast(new LogFilter());
chain.addLast(new ForwardHandler());

HttpServer proxy = new HttpServer(listenport, chain);
proxy.setAutoCompressThresholdBytes(Integer.MAX_VALUE);
proxy.setAutoUncompress(false);

proxy.run();

The LogFilter defined here creates a copy of the request and response message and prints it out. The message will be forwarded locally by calling the forward method. In this case the next RequestHandler of the chain will be called. Here the ForwardHandler of the example above will be called.

requesthandlerchain.gif

By using the forwarded method based on the request header, the body can be stream without buffering the total message.

The LogFilter here also uses the xLightweb's BodyForwarder convenience class to forward be message body.

class LogFilter implements IHttpRequestHandler {

   public void onRequest(final IHttpExchange exchange) throws IOException, BadMessageException {

      IHttpRequest req = exchange.getRequest(); 

      // is body less request?
      if (!req.hasBody()) {
         System.out.println(req.getRequestHeader().toString());
         exchange.forward(req, new ResponseHandler(exchange));

      // .. no, the request do have a body
      } else {
         // get the request header and body 
         final IHttpRequestHeader header = req.getRequestHeader(); 
         NonBlockingBodyDataSource bodyDataSource = req.getNonBlockingBody();

         // create a log buffer 
         final List<ByteBuffer> logBuffer = new ArrayList<ByteBuffer>(); 

         // forward the request (header) 
         final BodyDataSink bodyDataSink = exchange.forward(req.getRequestHeader(), new ResponseHandler(exchange));

         // define a forward handler 
         BodyForwarder bodyForwardHandler = new BodyForwarder(bodyDataSource, bodyDataSink) {

            @Override
            public void onData(NonBlockingBodyDataSource source, BodyDataSink sink) throws IOException {
               ByteBuffer[] bufs = source.readByteBufferByLength(source.available());

               for (ByteBuffer byteBuffer : bufs) {
                  logBuffer.add(byteBuffer.duplicate());
               }

               sink.write(bufs);
            }

            @Override
            public void onComplete() {
               System.out.println(header.toString());
               try {
                  System.out.println(header.toString() + 
                                     DataConverter.toString(logBuffer, header.getCharacterEncoding()));
               } catch (Exception e) {
                  System.out.println("<body not printable>");
               }
            }
         };

         // an set it 
         bodyDataSource.setDataHandler(bodyForwardHandler);
      }  
   }
} 

Similar to the request handler the response handler also uses the BodyForwarder class.

class ResponseHandler implements IHttpResponseHandler {

   private IHttpExchange exchange = null;

   ResponseHandler(IHttpExchange exchange) {
      this.exchange = exchange;
   }

   public void onResponse(IHttpResponse response) throws IOException {

      // is body less response?
      if (!response.hasBody()) {
         try {
            System.out.println(response.getResponseHeader());
         } catch (Exception ignore) { }
         exchange.send(response);

      // ... no, it has a body
      } else {
         // get the response header and body 
         final IHttpResponseHeader header = response.getResponseHeader(); 
         NonBlockingBodyDataSource bodyDataSource = response.getNonBlockingBody();

         // create a log buffer 
         final List<ByteBuffer> logBuffer = new ArrayList<ByteBuffer>(); 

         // send the response (header)        
         final BodyDataSink bodyDataSink = exchange.send(response.getResponseHeader());

         // define a forward handler 
         BodyForwarder bodyForwardHandler = new BodyForwarder(bodyDataSource, bodyDataSink) {

            @Override
            public void onData(NonBlockingBodyDataSource source, BodyDataSink sink) throws IOException {
               ByteBuffer[] bufs = source.readByteBufferByLength(source.available());

               for (ByteBuffer byteBuffer : bufs) {
                  logBuffer.add(byteBuffer.duplicate());
               }

               sink.write(bufs);
            }

            @Override
            public void onComplete() {
               System.out.println(header.toString());
               try {
                  System.out.println(header.toString() + 
                                     DataConverter.toString(logBuffer, header.getCharacterEncoding()));
               } catch (Exception e) {
                  System.out.println("<body not printable>");
               }
            }  
         };

         // an set it 
         bodyDataSource.setDataHandler(bodyForwardHandler);
      }
   }

   public void onException(IOException ioe) {
      exchange.sendError(500);
   }
} 

In the example below a AuthHandler filter is used which implements the http basic authorization. Each request first enters the AuthHandler, which validates the authorization. If the authorization fails, the AuthHandler will send a error message. If the authorization passes, the next handler of the chain, the FileServiceRequestHandler will be called by forwarding the request.

RequestHandlerChain chain = new RequestHandlerChain();
chain.addLast(new AuthFilter());
chain.addLast(new FileServiceRequestHandler("C:\\temp", true));

IServer server = new HttpServer(port, chain);
server.run();


class AuthFilter implements IHttpRequestHandler {

   private Authenticator authenticator = new Authenticator();

   public void onRequest(final IHttpExchange exchange) throws IOException, BadMessageException {

      IHttpRequest request = exchange.getRequest();
      String authorization = request.getHeader("Authorization");
      if (authorization != null) {
         String[] s = authorization.split(" ");
         if (!s[0].equalsIgnoreCase("BASIC")) {
         exchange.sendError(401);
         return;
      }

      String decoded = new String(Base64.decodeBase64(s[1].getBytes()));
      String[] userPasswordPair = decoded.split(":");

      String authtoken = authenticator.login(userPasswordPair[0], userPasswordPair[1]);

      IHttpResponseHandler respHdl = new IHttpResponseHandler() {

         public void onResponse(IHttpResponse response) throws IOException {
            exchange.send(response);
         }

         public void onException(IOException ioe) {
            exchange.sendError(500); 
         }
      };

      exchange.forward(exchange.getRequest(), respHdl);
      return;
   }


   String authentication = request.getHeader("Authentication");
   if (authentication == null) {
      HttpResponse resp = new HttpResponse(401);
      resp.setHeader("WWW-Authenticate", "basic");
      exchange.send(resp);
   }
} 

The xLightweb built-in FileServiceRequestHandler returns the requested file based on the configured base path and the requested URI resource.

3.4 Server-side 100-Continue handling

By default an 'Expect: 100-continue' will be handled automatically, if such a request header is received. xLightweb will send an interim "100 Continue" response (once), if

  • any method of the NonBlockingBody/BlockingBody is called or
  • the content-type of the request body is application/x-www-form-urlencoded

In mot case the auto handling is sufficient. However, if the continue mangement is handled by the IHttpRequestHandler, the handler will be annotated with the Supports100Continue annotation. In this case the auto handling of a 'Expect: 100-continue' header is deactivated. The 100-continue response will be send by performing the sendContinueIfRequested() method.

class HttpRequestHandler implements IHttpRequestHandler {

   @Supports100Continue
   public void onRequest(IHttpExchange exchange) throws IOException {
 
      IHttpRequest request = exchange.getRequest();    
            
      if (isVaildRequest(request)) {
         exchange.sendContinueIfRequested();  // signals the client that the server is willing to reveive the request (body)
                    
         NonBlockingBodyDataSource dataSource = request.getNonBlockingBody();
         //...
                    
      } else {
         exchange.sendError(417); 
      }
   }

   boolean void isValidRequest(IHttpRequest request) {
      //...
   }
   
} 

3.5 (Un)Compressing-Support

Client-requests will be uncompressed by default. If a (gzip) compressed request is received, the body will be uncompressed, automatically. The uncompress support can be deactivated by setting autoUncompress to false (default: true).

IServer server = new HttpServer(port, chain);
server.setAutoUncompress(false);
server.setAutoCompressThresholdBytes(2000);
server.run();

 ...        

In case the client request contains a Accept-Encoding: gzip header the response will be compressed, if the autoCompressThreshold is exceeded. The threshold can be overriden by setting autoCompressThreshold.

3.6 Routing (URL pattern support)

Analogous to the Servlet approach specific request handlers can be assigned via a url-pattern to a set of URLs. The url-pattern syntax is equals to the Servlet approach. A request handler will be assigned to a url by using a Context.

Typically, this approach is required, if static resources have to be supported as well as dynamic resources.

 class CometForeverFrameHandler implements IHttpRequestHandler {
        
   private final Timer timer = new Timer("timer", true);
             
   public void onRequest(final IHttpExchange exchange) throws IOException, BadMessageException {

      IHttpRequest req = exchange.getRequest();

      // handle the time request
      if (req.getRequestURI().endsWith("/time")) {
                
         // write the message header by retrieving the body handle
         IHttpResponseHeader respHdr = new HttpResponseHeader(200, "text/html");
         final BodyDataSink outChannel = exchange.send(respHdr);

         // timer task definition                  
         TimerTask timerTask = new TimerTask() {
                
            public void run() {
               try {
                  String script = "<script>\r\n" +
                                  "  parent.printTime(\"" + new Date() + "\");\r\n" +
                                  "</script>";
                  outChannel.write(script);
               } catch (IOException ioe) {
                  cancel();
                  try {
                     outChannel.close();
                  } catch (IOException ignore) { }
               }
            }      
        };
         
         // start the timer task 
         timer.schedule(timerTask, 0, 1000);
      }
   }
}


Context rootCtx = new Context("");
rootCtx.addHandler("/service/*", new CometForeverFrameHandler());
rootCtx.addHandler("/*", new FileServiceHandler("C:\\apps\timer\files"));

IServer server = new HttpServer(port, rootCtx);
server.run(); 


The example above defines the FileServiceHandler as default handler (namespace /*). The TimeHandler is assigned to the namespace /service/*, which means if the requested URI matches with this namespace, the TimeHandler will be used to serve the request.

The namespace mapping can also be defined by using the Mapping annotation.

@Mapping("/Person/*")
public class PersonRequestHandler implements IHttpRequestHandler {
    
   public void onRequest(IHttpExchange exchange) throws IOException, BadMessageException {
      // ...
   }    
}


@Mapping("/Account/*")
public class AccountRequestHandler implements IHttpRequestHandler {
        
   public void onRequest(IHttpExchange exchange) throws IOException, BadMessageException {
      //...
   }       
}


Context rootCtx = new Context("");
rootCtx.addHandler(new PersonRequestHandler());
rootCtx.addHandler(new AccountRequestHandler());

IServer server = new HttpServer(port, rootCtx);
server.start(); 

3.7 Server-side caching

Server-side caching can activated by adding aCacheHandler into the request handling chain. Please consider, that the HttpClient will add a cache handler automatically, if the HttpClient maxSizeKB ist set with larger than zero.

RequestHandlerChain chain = new RequestHandlerChain();
chain.addLast(new CacheHandler(500));  // cache handler with 500 KB max cache size
chain.addLast(new FileServiceRequestHandler("C:\\apps\stat\files"));
        
HttpServer server = new HttpServer(chain);
//.. 

The FileServiceRequestHandler supports a build-in expired-based cache support. In this case an additional cache hander is not required. By passing over the expire time within the constructor of the FileServiceRequestHandler, all responses will include a expired-based cache-header.

3.8 Handling timeouts

By sending requests 3 types of timeouts can be set:

  • ConnectionTimeout: the max life time of a connection, independent, if messages or data will be received or sent.
  • RequestTimeout: the max time between a new request is reveived after finishing the former request is completed.
  • BodyDataReceiveTimeout: the max time between receiving data packages of the response body.

RequestTimeout.gif

If a timeout occurs, the connection will be closed. This is independent of the timeout type. By implementing the IHttpRequestTimeout interface this behaviour can be overridden for RequestTimeouts.

class ServerHandler implements IHttpRequestHandler, IHttpRequestTimeoutHandler {
 
   public void onRequest(final IHttpExchange exchange) throws IOException {
      //...
   }

   public boolean onRequestTimeout(IHttpConnection con) throws IOException, BadMessageException {
      // ...
      
      return true; // true indicates, that event has been handled by app (returning false causes close) 
   }
}

A BodyDataReceivedTimeout will lead to a ClosedChannelException, if a body data source read method is called.

The timeouts can be set for all incoming connections

HttpServer server = new HttpServer(new MyHandler());

server.setConnectionTimeoutMillis(24L * 60L * 60L * 1000L);
server.setRequestTimeoutMillis(2L * 60L * 1000L);
server.setBodyDataReceiveTimeoutMillis(30L * 1000L);

server.start(); 
//...

... or for a concrete connection instance only

class MyHandler implements IHttpConnectHandler, IHttpRequestHandler { 

   public boolean onConnect(IHttpConnection httpConnection) throws IOException {
      //...
      httpConnection.setConnectionTimeoutMillis(24L * 60L * 60L * 1000L);
      httpConnection.setRequestTimeoutMillis(2L * 60L * 1000L);
      httpConnection.setBodyDataReceiveTimeoutMillis(30L * 1000L);
   }

   public void onRequest(final IHttpExchange exchange) throws IOException {
      //...
   }
}

The body data receive timeout can also be set for a dedicated message (only useful for InvokeOn.HEADER_RECEIVED case)

//...
message.getNonBlockingBody().setBodyDataReceiveTimeoutMillis(30L * 1000L);
        

3.9 HttpSession support

Classic Web-applications often require storing application session data on the server-side. This will be supported by the IHttpSession. The session can be retieved by calling the getSession(…) method. When true is passed as argument, a new session will be created, if there isn't one already. In this case a cookie named ‘JSESSIONID’ will be send to the client. The value of the ‘JSESSIONID’ cookie is the unique session id. This id will be used to identify the session object, which is stored on the server side. By receiving further client-request, the server is able to fetch the associated session object based on the cookie header of the client-request.

class MyRequestHandler implements IHttpRequestHandler {

   public void onRequest(IHttpExchange exchange) throws IOException {

      // accessing the http session (create  a new one, if there isn't one already)
      IHttpSession session = exchange.getSession(true);

      Integer counter = (Integer) session.getAttribute("counter");
      // ...
      session.setAttribute("counter", counter);

      exchange.send(response);
   }
}        

To avoid side-effects by accessing the IHttpSession concurrently, the onRequest(...) method can be annotated with the SynchronizedOn annotation. By setting the annotation to SESSION the onRequest(…) method will be synchronized around the IHttpSession. If there is no session, the synchronization scope will be the default synchronisation scope CONNECTION.

class MyRequestHandler implements IHttpRequestHandler {

   @SynchronizedOn(SynchronizedOn.SESSION)
   public void onRequest(IHttpExchange exchange) throws IOException {

      IHttpSession session = exchange.getSession(true);

      Integer counter = (Integer) session.getAttribute("counter");
      // ...
      session.setAttribute("counter", counter);

      exchange.send(response);
   }
}        

For a more detailed explanation on side-effects by using a HttpSession refer to the article Java theory and practice: Are all stateful Web applications broken?

3.10 Server-side Server-Sent Events

On the server-side an event stream will be handled by an ordinary IHttpRequestHandler implementation. To write a new event the Event class can be created and serialized. The Event class is implemented according to the text/event-stream specification.

class MyHttpRequestHandler implements IHttpRequestHandler {
            
   public void onRequest(IHttpExchange exchange) throws IOException {
      IHttpRequest request = exchange.getRequest();
                
      if (request.getAccept().contains(new ContentType("text/event-stream"))) {
         BodyDataSink ds = exchange.send(new HttpResponseHeader(200, "text/event-stream"));
                    
         Event event = new Event();
         event.setComment("test stream");
         ds.write(event.toString());

         // ...
                    
      } else {
         // ...

      }
  }
}
        
HttpServer server = new HttpServer(new MyHttpRequestHandler());
server.start();

3.11 Server-side Web Sockets

A web socket server will be created by passing over a IWebSocketHandler instance. The XHttpServer is an extension of the HttpServer which supports the IWebSocketHandler.

class MyWebSocketHandler implements IWebSocketHandler {
          
   public void onConnect(IWebSocketConnection con) throws IOException {
      String protocol = con.getProtocol();
      String webSocketLocation = con.getWebSocketLocation();
            
      //...
   }

   public void onMessage(IWebSocketConnection con) throws IOException {
      TextMessage msg = con.readTextMessage();

      // ...
      con.writeMessage(msg);
   }
        
   public void onDisconnect(IWebSocketConnection con) throws IOException {

  }                
}
   

XHttpServer server = new XHttpServer(port, new MyWebSocketHandler());
server.start();

If the web handler implements the IHttpRequestHander as well as the IWebSocketHandler, the handler will be called in case of a HTTP request and in case of web socket message. Please note the web socket upgrade HTTP request will not be visible for the onRequest() method.

class MyMixedRequestHandler implements IHttpRequestHandler, IWebSocketHandler {
        

   public void onRequest(IHttpExchange exchange) throws IOException, BadMessageException {
      IHttpRequest request = exchange.getRequest();
      // ...      
      
      exchange.send(new HttpResponse(200, "text/plain", msg));
   }


   @Execution(Execution.NONTHREADED)        
   public void onConnect(IWebSocketConnection con) throws IOException {
      IHttpRequestHeader header = con.getUpgradeRequestHeader();
      // ...
   }
        
       
   public void onMessage(IWebSocketConnection con) throws IOException {
      TextMessage msg = con.readTextMessage();
      // ...
   }


   public void onDisconnect(IWebSocketConnection con) throws IOException {
      // ...
   }
}        
     

XHttpServer server = new XHttpServer(port, new MyMixedRequestHandler());
server.start();

4 Using xLightweb together with Spring

For basic information about xLightweb and Spring see xSocket’s core tutorial. The example here shows how to use xLightweb's RequestHandlerChain, Context and HttpClient together with Spring.

By using a RequestHandlerChain the request handler can be set by using Spring's constructor Injection.

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans" 
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" 
       xsi:schemaLocation="http://www.springframework.org/schema/beans
                           http://www.springframework.org/schema/beans/spring-beans-2.0.xsd">

   <bean id="server" class="org.xlightweb.server.HttpServer" scope="singleton"
         init-method="start" destroy-method="close">
      <constructor-arg value="8080"/>
      <constructor-arg ref="chain"/>
   </bean>

   <bean id="chain" class="org.xlightweb.RequestHandlerChain" 
         scope="prototype">
      <constructor-arg>
         <list>
            <ref bean="authFilter"/>
            <ref bean="handler"/>
         </list>
      </constructor-arg>
   </bean>

   <bean id="authFilter" class="mynamespace.MyAuthFilter" scope="prototype"/>

   <bean id="handler" class="mynamespace.MyHandler" scope="prototype"/>

</beans> 

The constructor injection can also used to create a Context.

<?xml version="1.0" encoding="UTF-8"?>

<beans xmlns="http://www.springframework.org/schema/beans" 
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" 
       xsi:schemaLocation="http://www.springframework.org/schema/beans  
                           http://www.springframework.org/schema/beans/spring-beans-2.0.xsd">

   <bean id="server" class="org.xlightweb.server.HttpServer" scope="singleton"
         init-method="start" destroy-method="close">
      <constructor-arg value="8080"/>
      <constructor-arg ref="context"/>
   </bean>

   <bean id="context" class="org.xlightweb.Context" scope="prototype">
      <constructor-arg value=""/>
      <constructor-arg>
         <map>
            <entry key="/info/*">
               <ref bean="infoHandler"/>
            </entry>
            <entry key="/files/*">
               <ref bean="fileServiceHandler"/>
            </entry>
         </map>
      </constructor-arg>
   </bean>

   <bean id="infoHandler" class="mynamespace.MyHandler" scope="prototype"/>
   
   <bean id="fileServiceHandler" class="org.xlightweb.FileServiceRequestHandler"
         scope="prototype">
      <constructor-arg index="0" value="C:\temp"/>
      <constructor-arg index="1" value="true"/>
   </bean>

</beans> 

To create and configure a HttpClient see the example below.

<?xml version="1.0" encoding="UTF-8"?>

<beans xmlns="http://www.springframework.org/schema/beans" 
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" 
       xsi:schemaLocation="http://www.springframework.org/schema/beans 
                           http://www.springframework.org/schema/beans/spring-beans-2.0.xsd">
   
   <bean id="httpClient" class="org.xlightweb.client.HttpClient" scope="prototype">
      <property name="maxIdle">
         <value>30</value>
      </property> 
      <property name="responseTimeoutMillis">
         <value>120</value>
      </property>
   </bean>

</beans>