5.9 Limax Http

Most server applications need to provide some HTTP services more or less, and the Limax service framework is no exception. Sun has provided a simple server development package com.sun.net.httpserver since JDK6. The actual use will find that the function of this package is too weak, so the development package limax.http is provided as an alternative.

The limax.http supports HTTP/1.1 (compatible with HTTP/1.0), HTTP/2, WebSocket (both upgraded from HTTP/1.1 and HTTP/2 tunnel), and Server-Sent Events. Provide a simpler and more efficient service architecture than Servlet (Servlet has gone through 4 versions and is getting more and more complex).

The service implementation does not need to care whether it is currently running in the HTTP/1.1 environment or the HTTP/2 environment. If the conditions are met, HTTP/2 is automatically selected.


  • 5.9.1 HTTP service architecture


    public interface HttpHandler extends Handler {
        DataSupplier handle(HttpExchange exchange) throws Exception;
        default void censor(HttpExchange exchange) throws Exception {
            throw new UnsupportedOperationException();
        }
    }
    


    • 1. The handle method of HttpHandler is scheduled following the end of request, service coding in it.

    • 2. The censor method of HttpHandler is used for censoring the progress of uploading. Fail in the censoring, any exception can be thrown, terminiate the connection immediately. POST request is forbidden by the default implementation

    • 3. The POST request schedule censor one or more times, and the first scheduling can obtain a complete request header, and the uploading data leads to subsequent scheduling. See the advanced topic for the detail of censoring of POSTING.

    • 4. The getData() method of FormData can get the query data after parsing, even if the query of the GET request is parsed. The return type is Map<String, List<Object>>, where Object may be a String, or a List<ByteBuffer>. The String corresponds to the string query parameter, and the List<ByteBuffer> corresponds to the uploaded file data. The reason why List<ByteBuffer> is used here is that the size of ByteBuffer has Integer.MAX_INTEGER limit.

    • 5. The postLimit(long postLimit) method of FormData is used for size limit of POSTING. Beyond the limit, Connection will be terminated directly. The useTempFile(int threshold) method of FormData provides a size threshold for uploading files. Below the threshold the contents of the file are stored in the heap ByteBuffer. Otherwise, the contents of the file are stored in a temporary file. The ByteBuffer is obtained through the memory mapping of the file. A suitable threshold should be set if the uploading file is supported. Using useTempFile method with POST request that doesn’t support file uploading, Exception will be thrown internally to terminate the connection. It is reasonable because larger postLimit is needed while file uploading, Howerver with POST request that doesn’t support file uploading whole POST data is stored in memory, larger postLimit will give malicious client a chance to attack the server. For specific usage, please refer to the example.

    • 6. The getRaw() method of FormData returns an Octets containing the raw data sent by POST (excluding uploading files, in which case the Octets is empty) to support processing of non-string-formatted queries, such as OCSP queries.


    • public interface limax.http.DataSupplier {
          interface Done {
              void done(HttpExchange exchange) throws Exception;
          }
          java.nio.ByteBuffer get();  
          default void done(HttpExchange exchange) throws Exception;
          static DataSupplier from(byte[] data) ; 
          static DataSupplier from(java.nio.ByteBuffer data);  
          static DataSupplier from(java.nio.ByteBuffer[] datas);  
          static DataSupplier from(java.io.File file) throws IOException; 
          static DataSupplier from(java.nio.channels.FileChannel fc,long begin,long end) throws IOException;
          static DataSupplier from(java.io.InputStream in,int buffersize);  
          static DataSupplier from(java.nio.file.Path path) throws IOException;   
          static DataSupplier from(java.nio.channels.ReadableByteChannel ch,int buffersize);  
          static DataSupplier from(java.lang.String text,java.nio.charset.Charset charset);
          static DataSupplier from(DataSupplier supplier, DataSupplier.Done done);
          static DataSupplier from(HttpExchange exchange, java.util.function.BiConsumer<String, ServerSentEvents> consumer, Runnable onSendReady, Runnable onClose);
          static DataSupplier async();
      }
      
    • 7. Seen from the DataSupplier interface definition, the static methods provided by DataSupplier are sufficient to cover most application scenarios. If the resource is needs to be released, the method with the DataSupplier.Done parameter can be used to decorate the DataSupplier and perform the release operation in the done operation. The last method is used to support ServerSentEvents, as described below.

    • 8. In rare cases, it needs to implement the own DataSupplier. If it is implemented, each call to the get method should return a ByteBuffer. The last time get returns null, indicating completion. Finally, the done method is called, and the resource can be released in the done method. In particular, the done method passes in an exchange again, providing an opportunity to set the trailer header. (The HTTP protocol defines the Trailer header. It is now unknown which browsers support it.)

    • 9. DataSupplier.async() returned by the handle means nothing can be responded, used for further asynchronous process. In this circumstance, after asynchronous process, exchange.async(HttpHandler handler) must be called for response. Time-consuming data prepare work should be accomplished in this way for service efficiency. (see example)

    • 10. The handle allows any exception to be thrown. Except to limax.http.HttpException, responds InternalServerError with the exception stack. HTTP error codes can be thrown by limax.http.HttpException. Constructing an HttpException also allows an HttpHandler to be used, which is then processed once with this special HttpHandler. The forceClose parameter, which determines that the connection needs to be closed after response. In particular, for HTTP/2, forceClose can be disabled by setting the server parameter.Further more, HttpException without any parameter can be thrown, abort the connection immediately and unconditionally.

    • 11. The application can also set the error code through exchange.getResponseHeaders().set(":status", code);. It should be noted that the ":status" here is a description of HTTP/2.

    • 12. The WebSocketExchange provides a promise (URI uri) method to support HTTP/2 server push, and the promise should be called during the processing of the service. Usually, the pushed resource should be referenced by the following page. The client finds that the resource referenced by the page has already been pushed before, and then it does not need to request the resource again, saving the request process. So this is an optimization measure. If it is no push, the final result of the push should be the same. The promise call is no operation under HTTP/1.1. In other words, if it needs to implement push in HttpHandler, it is no need to care whether the current running environment is HTTP/1.1 or HTTP/2. It should be noticed that server push is not necessarily a good choice in some cases. For example, the client has already cached the pushed resources, and then performing push instead wastes bandwidth.


  • 5.9.2 WebSocket service architecture


    public interface WebSocketHandler extends Handler {
        void handle(WebSocketEvent event) throws Exception;
    }
    

    • 1. The WebSocketHandler only needs to implement a handle method. When the client data is received or the websocket is closed, the handle is scheduled, and the handle scheduling is serial.

    • 2. event.type() return the type of event, event.getWebSocketExchange() return WebSocketExchange object which is used to send data, and access the currently associated SessionObject.

    • 3. When event.type()==OPEN the application's own SessionObject can be set to associate the current connection.

    • 4. The ping() method of WebSocketExchange can be used to measure RTT. The ping() method returns an id. After receiving the pong event, the corresponding id and RTT value can be obtained.

    • 5. The send(byte[] binary) and send(String text) method of WebSocketExchange are used to send binary data and string data of WebSocket. event.type()==SENDREADY means previous data is sent, new data can be sent, it can be used for flow control.

    • 6. The sendFinal() method of WebSocketExchange closes the current websocket connection immediately after sending CloseFrame. The sendFinal(long timeout) method half-closes the connection after sending a CloseFrame, and waits for the handshake CloseFrame of the peer within the timeout limit to ensure that the RFC6455 closes the semantics. In fact, timeout has no meaning for the WebSocket on the https connection, because the WebSocket close handshake happens before SSL close handshake, which ensures that the WebSocket close handshake must be completed. Similarly, timeout has no meaning for the HTTP/2 tunneling WebSocket close defined by RFC8441, because WebSocket close only corresponds to Stream close.

    • 7. The resetAlarm of WebSocketExchange can set the timeout. If resetAlarm is not called in the timeout limit, the WebSocke connection is automatically closed.

    • 8. The close event is the last event. After receiving it, the corresponding resources can be released immediately. Usually the close-code and close-reason are complex, and don't care too much.

    • 9. There are two operation modes for WebSocket, originally upgraded from HTTP/1.1. The RFC8441 proposed by Mozillia allows WebSocket traffic to be tunneled through HTTP/2 stream. It is currently known that only some release of FireFox supports this mode. This mode has great advantages when using WebSockets frequently to accomplish some small tasks. If the WebSocket is used to perform complex persistence tasks, this mode may not be appropriate. If it needs to be disabled, setting the parameter httpServer.set(HttpServer.Parameter.HTTP2_SETTINGS_ENABLE_CONNECT_PROTOCOL, 0) on the HttpServer object can be used.


  • 5.9.3 Server-Sent Events

    • 1. When HttpHandle.handle returns, call DataSupplier.from(HttpExchange exchange, BiConsumer<String, ServerSentEvents> consumer, Runnable onSendReady, Runnable onClose) to get a ServerSentEvents object. Among them, the String of BiConsumer is Last-Event-Id, and the onSendReady is used for flow control.The onClose is used for advice of terminiation of the connection or operation of done is accomplished,further operation of emit is insignificance,the ServerSentEvents object should be released


      public interface ServerSentEvents {
          void emit(String event,String id,String data);
          void emit(String data);
          void emit(long milliseconds);
          void done();
      }
      
    • 2. The emit method with three parameters is used to push data containing event, id, and data to the browser, where event and id are allowed to be null. The emit method with a single String parameter is equivalent to event and id are null. The emit method with the long parameter is used to send the retry command to the browser in milliseconds. If the push is complete, the done method should be called. If the trailer is set on the exchange before the done method is called, the trailer can be sent out. (Http semantics allow and which browser support it is unknown).

    • 3. The EventSource corresponding to the browser receives the onerror message. It is best to turn off the EventSource immediately. Otherwise, the browser will continue to reconnect to the server. This is almost an attack. This situation can be observed when removing the s.close in sse.html in the sample code.

    • 4. Server-Sent Events is essentially a simple design that uses the chunked transport encoding of HTTP/1.1. The ability is too weak, and it is better to use WebSocket instead.


Prev Next