tutorialThis 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 connection2. HttpClient3. HttpServer - Server-side http connection4. Using xLightweb together with Springhttp protocol supportxLightweb 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 architecturesxLightweb 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. . ![]() 1. Client-side http connectionA 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 callTo 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 modeA 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 modeIn 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 modeIt 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.
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 styleAsynchronous 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 styleBeside 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 bodyTo 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 timeoutsBy sending requests 3 types of timeouts can be set:
![]() 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 -SupportMost 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 handlingThe 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 supportBeside 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. HttpClientOften 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.![]() 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 ![]() 2.1 Retry-HandlingThe 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 handlingCookies will be auto-handled, if autoHandleCookies is true (default: true) HttpClient httpClient = new HttpClient(); httpClient.setAutoHandleCookies(true); ... 2.3 Redirect supportAuto-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 supportThe 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 ProxyThe 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 supportThe 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 EventsA 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 eventA 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 wayIf 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 WebSocketsA 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 messageThe 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 wayMessages 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 messageA 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 wayIf 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 connection3.1 Server handlerTo 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. ![]() 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 chainingBy 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. ![]() 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 handlingBy 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
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-SupportClient-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 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 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 cachingServer-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 timeoutsBy sending requests 3 types of timeouts can be set:
![]() 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 supportClassic 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 EventsOn 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 SocketsA 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 SpringFor 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> |