Samples

The following example demonstrates the flexibility of the http module by using the module for both the client and the server. To begin, we'll start with the the client:

  1 /**
  2  * Simple asynchronous HTTP client implementation, which downloads HTTP resource
  3  * and saves its content in a local file.
  4  * 
  5  * @author Alexey Stashok
  6  */
  7 public class Client {
  8     private static final Logger logger = Grizzly.logger(Client.class);
  9     
 10     public static void main(String[] args) throws IOException, URISyntaxException {
 11         // Check command line parameters
 12         if (args.length < 1) {
 13             System.out.println("To download the resource, please run: Client <url>");
 14             System.exit(0);
 15         }
 16 
 17         final String url = args[0];
 18 
 19         // Parse passed URL
 20         final URI uri = new URI(url);
 21         final String host = uri.getHost();
 22         final int port = uri.getPort() > 0 ? uri.getPort() : 80;
 23         
 24         final FutureImpl<String> completeFuture = SafeFutureImpl.create();
 25 
 26         // Build HTTP client filter chain
 27         FilterChainBuilder clientFilterChainBuilder = FilterChainBuilder.stateless();
 28         // Add transport filter
 29         clientFilterChainBuilder.add(new TransportFilter());
 30         // Add IdleTimeoutFilter, which will close connections, which stay
 31         // idle longer than 10 seconds.
 32         clientFilterChainBuilder.add(new IdleTimeoutFilter(10, TimeUnit.SECONDS));
 33         // Add HttpClientFilter, which transforms Buffer <-> HttpContent
 34         clientFilterChainBuilder.add(new HttpClientFilter());
 35         // Add HTTP client download filter, which is responsible for downloading
 36         // HTTP resource asynchronously
 37         clientFilterChainBuilder.add(new ClientDownloadFilter(uri, completeFuture));
 38 
 39         // Initialize Transport
 40         final TCPNIOTransport transport =
 41                 TCPNIOTransportBuilder.newInstance().build();
 42         // Set filterchain as a Transport Processor
 43         transport.setProcessor(clientFilterChainBuilder.build());
 44 
 45         try {
 46             // start the transport
 47             transport.start();
 48 
 49             Connection connection = null;
 50             
 51             // Connecting to a remote Web server
 52             Future<Connection> connectFuture = transport.connect(host, port);
 53             try {
 54                 // Wait until the client connect operation will be completed
 55                 // Once connection will be established - downloading will
 56                 // start @ ClientDownloadFilter.onConnect(...)
 57                 connection = connectFuture.get(10, TimeUnit.SECONDS);
 58                 // Wait until download will be completed
 59                 String filename = completeFuture.get();
 60                 logger.log(Level.INFO, "File " + filename + " was successfully downloaded");
 61             } catch (Exception e) {
 62                 if (connection == null) {
 63                     logger.log(Level.WARNING, "Can not connect to the target resource");
 64                 } else {
 65                     logger.log(Level.WARNING, "Error downloading the resource");
 66                 }
 67             } finally {
 68                 // Close the client connection
 69                 if (connection != null) {
 70                     connection.close();
 71                 }
 72             }
 73         } finally {
 74             logger.info("Stopping transport...");
 75             // stop the transport
 76             transport.stop();
 77 
 78             logger.info("Stopped transport...");
 79         }
 80     }
 81 }

The documentation within the example above should be sufficient to get an understanding of what is going on within the client. However, the ClientDownloadFilter, added at line 37 warrants a look:

  1 /**
  2  * HTTP client download filter.
  3  * This Filter is responsible for asynchronous downloading of a HTTP resource and
  4  * saving its content in a local file.
  5  *
  6  * @author Alexey Stashok
  7  */
  8 public class ClientDownloadFilter extends BaseFilter {
  9     private final static Logger logger = Grizzly.logger(ClientDownloadFilter.class);
 10     
 11     // URI of a remote resource
 12     private final URI uri;
 13     // local filename, where content will be saved
 14     private final String fileName;
 15     
 16     // Download completion future
 17     private FutureImpl<String> completeFuture;
 18 
 19     // local file channel, where we save resource content
 20     private volatile FileChannel output;
 21     // number of bytes downloaded
 22     private volatile int bytesDownloaded;
 23 
 24     private final String resourcePath;
 25 
 26     /**
 27      * <tt>ClientDownloadFilter</tt> constructor
 28      *
 29      * @param uri {@link URI} of a remote resource to download
 30      * @param completeFuture download completion handler ({@link FutureImpl})
 31      */
 32     public ClientDownloadFilter(URI uri, FutureImpl<String> completeFuture) {
 33         this.uri = uri;
 34         
 35         // Extracting resource path
 36         resourcePath =
 37                 uri.getPath().trim().length() > 0 ? uri.getPath().trim() : "/";
 38 
 39         int lastSlashIdx = resourcePath.lastIndexOf('/');
 40         if (lastSlashIdx != -1 && lastSlashIdx < resourcePath.length() - 1) {
 41             // if the path contains a filename - take it as local filename
 42             fileName = resourcePath.substring(lastSlashIdx + 1);
 43         } else {
 44             // if the path doesn't contain filename - we will use default filename
 45             fileName = "download#" + System.currentTimeMillis() + ".txt";
 46         }
 47         
 48         this.completeFuture = completeFuture;
 49     }
 50 
 51     /**
 52      * The method is called, when a client connection gets connected to a web
 53      * server.
 54      * When this method gets called by a framework - it means that client connection
 55      * has been established and we can send HTTP request to the web server.
 56      *
 57      * @param ctx Client connect processing context
 58      *
 59      * @return {@link NextAction}
 60      * @throws IOException
 61      */
 62     @Override
 63     public NextAction handleConnect(FilterChainContext ctx) throws IOException {
 64         // Build the HttpRequestPacket, which will be sent to a server
 65         // We construct HTTP request version 1.1 and specifying the URL of the
 66         // resource we want to download
 67         final HttpRequestPacket httpRequest = HttpRequestPacket.builder().method("GET")
 68                 .uri(resourcePath).protocol(Protocol.HTTP_1_1)
 69                 .header("Host", uri.getHost()).build();
 70         logger.log(Level.INFO, "Connected... Sending the request: {0}", httpRequest);
 71 
 72         // Write the request asynchronously
 73         ctx.write(httpRequest);
 74 
 75         // Return the stop action, which means we don't expect next filter to process
 76         // connect event
 77         return ctx.getStopAction();
 78     }
 79 
 80     /**
 81      * The method is called, when we receive a {@link HttpContent} from a server.
 82      * Once we receive one - we save the content chunk to a local file.
 83      * 
 84      * @param ctx Request processing context
 85      *
 86      * @return {@link NextAction}
 87      * @throws IOException
 88      */
 89     @Override
 90     public NextAction handleRead(FilterChainContext ctx) throws IOException {
 91         try {
 92             // Cast message to a HttpContent
 93             final HttpContent httpContent = (HttpContent) ctx.getMessage();
 94 
 95             logger.log(Level.FINE, "Got HTTP response chunk");
 96             if (output == null) {
 97                 // If local file wasn't created - create it
 98                 logger.log(Level.INFO, "HTTP response: {0}", httpContent.getHttpHeader());
 99                 logger.log(Level.FINE, "Create a file: {0}", fileName);
100                 FileOutputStream fos = new FileOutputStream(fileName);
101                 output = fos.getChannel();
102             }
103 
104             // Get HttpContent's Buffer
105             final Buffer buffer = httpContent.getContent();
106 
107             logger.log(Level.FINE, "HTTP content size: {0}", buffer.remaining());
108             if (buffer.remaining() > 0) {
109                 bytesDownloaded += buffer.remaining();
110                 
111                 // save Buffer to a local file, represented by FileChannel
112                 ByteBuffer byteBuffer = buffer.toByteBuffer();
113                 do {
114                     output.write(byteBuffer);
115                 } while (byteBuffer.hasRemaining());
116                 
117                 // Dispose a content buffer
118                 buffer.dispose();
119             }
120 
121             if (httpContent.isLast()) {
122                 // it's last HttpContent - we close the local file and
123                 // notify about download completion
124                 logger.log(Level.FINE, "Downloaded done: {0} bytes", bytesDownloaded);
125                 completeFuture.result(fileName);
126                 close();
127             }
128         } catch (IOException e) {
129             close();
130         }
131 
132         // Return stop action, which means we don't expect next filter to process
133         // read event
134         return ctx.getStopAction();
135     }
136 
137     /**
138      * The method is called, when the client connection will get closed.
139      * Intercepting this method let's use release resources, like local FileChannel,
140      * if it wasn't released before.
141      *
142      * @param ctx Request processing context
143      *
144      * @return {@link NextAction}
145      * @throws IOException
146      */
147     @Override
148     public NextAction handleClose(FilterChainContext ctx) throws IOException {
149         close();
150         return ctx.getStopAction();
151     }
152 
153     /**
154      * Method closes the local file channel, and if download wasn't completed -
155      * notify {@link FutureImpl} about download failure.
156      * 
157      * @throws IOException If failed to close <em>localOutput</em>.
158      */
159     private void close() throws IOException {
160         final FileChannel localOutput = this.output;
161         // close the local file channel
162         if (localOutput != null) {
163             localOutput.close();
164         }
165 
166         if (!completeFuture.isDone()) {
167             //noinspection ThrowableInstanceNeverThrown
168             completeFuture.failure(new IOException("Connection was closed"));
169         }
170     }
171 }

Now for the server side. Like the client, there are two parts. The server itself and the custom Filter. Let's start with the server:

  1 /**
  2  * Simple HTTP (Web) server, which listens on a specific TCP port and shares
  3  * static resources (files), located in a passed folder.
  4  * 
  5  * @author Alexey Stashok
  6  */
  7 public class Server {
  8     private static final Logger logger = Grizzly.logger(Server.class);
  9 
 10     // TCP Host
 11     public static final String HOST = "localhost";
 12     // TCP port
 13     public static final int PORT = 7777;
 14 
 15     public static void main(String[] args) throws IOException {
 16         // Construct filter chain
 17         FilterChainBuilder serverFilterChainBuilder = FilterChainBuilder.stateless();
 18         // Add transport filter
 19         serverFilterChainBuilder.add(new TransportFilter());
 20         // Add IdleTimeoutFilter, which will close connetions, which stay
 21         // idle longer than 10 seconds.
 22         serverFilterChainBuilder.add(new IdleTimeoutFilter(10, TimeUnit.SECONDS));
 23         // Add HttpServerFilter, which transforms Buffer <-> HttpContent
 24         serverFilterChainBuilder.add(new HttpServerFilter());
 25         // Simple server implementation, which locates a resource in a local file system
 26         // and transfers it via HTTP
 27         serverFilterChainBuilder.add(new WebServerFilter("."));
 28 
 29         // Initialize Transport
 30         final TCPNIOTransport transport =
 31                 TCPNIOTransportBuilder.newInstance().build();
 32         // Set filterchain as a Transport Processor
 33         transport.setProcessor(serverFilterChainBuilder.build());
 34 
 35         try {
 36             // binding transport to start listen on certain host and port
 37             transport.bind(HOST, PORT);
 38 
 39             // start the transport
 40             transport.start();
 41 
 42             logger.info("Press any key to stop the server...");
 43             System.in.read();
 44         } finally {
 45             logger.info("Stopping transport...");
 46             // stop the transport
 47             transport.stop();
 48 
 49             logger.info("Stopped transport...");
 50         }
 51     }
 52 }

Line 27, a custom Filter, WebServerFilter, is added to serve resources from the directory in which the server was started. Let's take a look at that Filter:

  1 /**
  2  * Simple Web server implementation, which locates requested resources in a
  3  * local filesystem and transfers it asynchronously to a client.
  4  * 
  5  * @author Alexey Stashok
  6  */
  7 public class WebServerFilter extends BaseFilter {
  8     private static final Logger logger = Grizzly.logger(WebServerFilter.class);
  9     private final File rootFolderFile;
 10 
 11     /**
 12      * Construct a WebServer
 13      * @param rootFolder Root folder in a local filesystem, where server will look
 14      *                   for resources
 15      */
 16     public WebServerFilter(String rootFolder) {
 17         if (rootFolder != null) {
 18             this.rootFolderFile = new File(rootFolder);
 19         } else {
 20             // if root folder wasn't specified - use the current folder
 21             this.rootFolderFile = new File(".");
 22         }
 23 
 24         // check whether the root folder
 25         if (!rootFolderFile.isDirectory()) {
 26             throw new IllegalStateException("Directory " + rootFolder + " doesn't exist");
 27         }
 28     }
 29 
 30     /**
 31      * The method is called once we have received some {@link HttpContent}.
 32      *
 33      * Filter gets {@link HttpContent}, which represents a part or complete HTTP
 34      * request. If it's just a chunk of a complete HTTP request - filter checks
 35      * whether it's the last chunk, if not - swallows content and returns.
 36      * If incoming {@link HttpContent} represents complete HTTP request or it is
 37      * the last HTTP request - it initiates file download and sends the file
 38      * asynchronously to the client.
 39      *
 40      * @param ctx Request processing context
 41      *
 42      * @return {@link NextAction}
 43      * @throws IOException
 44      */
 45     @Override
 46     public NextAction handleRead(FilterChainContext ctx)
 47             throws IOException {
 48 
 49         // Get the incoming message
 50         final Object message = ctx.getMessage();
 51         
 52         // Check if this is DownloadCompletionHandler, which means download has
 53         // been completed and HTTP request processing was resumed.
 54         if (message instanceof DownloadCompletionHandler) {
 55             // Download completed
 56             return ctx.getStopAction();
 57         }
 58 
 59         // Otherwise cast message to a HttpContent
 60         final HttpContent httpContent = (HttpContent) ctx.getMessage();
 61         // Get HTTP request message header
 62         final HttpRequestPacket request = (HttpRequestPacket) httpContent.getHttpHeader();
 63 
 64         // Check if it's the last HTTP request chunk
 65         if (!httpContent.isLast()) {
 66             // if not
 67             // swallow content
 68             return ctx.getStopAction();
 69         }
 70 
 71         // if entire request was parsed
 72         // extract requested resource URL path
 73         final String localURL = extractLocalURL(request);
 74 
 75         // Locate corresponding file
 76         final File file = new File(rootFolderFile, localURL);
 77 
 78         logger.log(Level.INFO, "Request file: {0}", file.getAbsolutePath());
 79 
 80         if (!file.isFile()) {
 81             // If file doesn't exist - response 404
 82             final HttpPacket response = create404(request);
 83             ctx.write(response);
 84 
 85             // return stop action
 86             return ctx.getStopAction();
 87         } else {
 88             // if file exists
 89             // suspend HttpRequestPacket processing to send the HTTP response
 90             // asynchronously
 91             ctx.suspend();
 92             final NextAction suspendAction = ctx.getSuspendAction();
 93 
 94             // Start asynchronous file download
 95             downloadFile(ctx, request, file);
 96             // return suspend action
 97             return suspendAction;
 98         }
 99     }
100 
101     /**
102      * Start asynchronous file download
103      *
104      * @param ctx HttpRequestPacket processing context
105      * @param request HttpRequestPacket
106      * @param file local file
107      * 
108      * @throws IOException
109      */
110     private void downloadFile(FilterChainContext ctx,
111             HttpRequestPacket request, File file) throws IOException {
112         // Create DownloadCompletionHandler, responsible for asynchronous
113         // file transferring
114         final DownloadCompletionHandler downloadHandler =
115                 new DownloadCompletionHandler(ctx, request, file);
116         // Start the download
117         downloadHandler.start();
118     }
119 
120     /**
121      * Create a 404 HttpResponsePacket packet
122      * @param request original HttpRequestPacket
123      *
124      * @return 404 HttpContent
125      */
126     private static HttpPacket create404(HttpRequestPacket request)
127             throws CharConversionException {
128         // Build 404 HttpResponsePacket message headers
129         final HttpResponsePacket responseHeader = HttpResponsePacket.builder(request).
130                 protocol(request.getProtocol()).status(404).
131                 reasonPhrase("Not Found").build();
132         
133         // Build 404 HttpContent on base of HttpResponsePacket message header
134         return responseHeader.httpContentBuilder().
135                     content(Buffers.wrap(null,
136                                          "Can not find file, corresponding to URI: "
137                                                  + request.getRequestURIRef().getDecodedURI())).
138                           build();
139     }
140 
141     /**
142      * Extract URL path from the HttpRequestPacket
143      *
144      * @param request HttpRequestPacket message header
145      * @return requested URL path
146      */
147     private static String extractLocalURL(HttpRequestPacket request)
148             throws CharConversionException {
149         // Get requested URL
150         String url = request.getRequestURIRef().getDecodedURI();
151 
152         // Extract path
153         final int idx;
154         if ((idx = url.indexOf("://")) != -1) {
155             final int localPartStart = url.indexOf('/', idx + 3);
156             if (localPartStart != -1) {
157                 url = url.substring(localPartStart + 1);
158             } else {
159                 url = "/";
160             }
161         }
162 
163         return url;
164     }
165 
166     /**
167      * {@link org.glassfish.grizzly.CompletionHandler}, responsible for asynchronous file transferring
168      * via HTTP protocol.
169      */
170     private static class DownloadCompletionHandler
171             extends EmptyCompletionHandler<WriteResult>{
172 
173         // MemoryManager, used to allocate Buffers
174         private final MemoryManager memoryManager;
175         // Downloading FileInputStream
176         private final InputStream in;
177         // Suspended HttpRequestPacket processing context
178         private final FilterChainContext ctx;
179         // HttpResponsePacket message header
180         private final HttpResponsePacket response;
181 
182         // Completion flag
183         private volatile boolean isDone;
184 
185         /**
186          * Construct a DownloadCompletionHandler
187          * 
188          * @param ctx Suspended HttpRequestPacket processing context
189          * @param request HttpRequestPacket message header
190          * @param file local file to be sent
191          * @throws FileNotFoundException
192          */
193         public DownloadCompletionHandler(FilterChainContext ctx,
194                 HttpRequestPacket request, File file) throws FileNotFoundException {
195             
196             // Open file input stream
197             in = new FileInputStream(file);
198             this.ctx = ctx;
199             // Build HttpResponsePacket message header (send file using chunked HTTP messages).
200             response = HttpResponsePacket.builder(request).
201                 protocol(request.getProtocol()).status(200).
202                 reasonPhrase("OK").chunked(true).build();
203             memoryManager = ctx.getConnection().getTransport().getMemoryManager();
204         }
205 
206         /**
207          * Start the file tranferring
208          * 
209          * @throws IOException
210          */
211         public void start() throws IOException {
212             sendFileChunk();
213         }
214 
215         /**
216          * Send the next file chunk
217          * @throws IOException
218          */
219         public void sendFileChunk() throws IOException {
220             // Allocate a new buffer
221             final Buffer buffer = memoryManager.allocate(1024);
222             
223             // prepare byte[] for InputStream.read(...)
224             final byte[] bufferByteArray = buffer.toByteBuffer().array();
225             final int offset = buffer.toByteBuffer().arrayOffset();
226             final int length = buffer.remaining();
227 
228             // Read file chunk from the file input stream
229             int bytesRead = in.read(bufferByteArray, offset, length);
230             final HttpContent content;
231             
232             if (bytesRead == -1) {
233                 // if the file was completely sent
234                 // build the last HTTP chunk
235                 content = response.httpTrailerBuilder().build();
236                 isDone = true;
237             } else {
238                 // Prepare the Buffer
239                 buffer.limit(bytesRead);
240                 // Create HttpContent, based on HttpResponsePacket message header
241                 content = response.httpContentBuilder().content(buffer).build();
242             }
243 
244             // Send a file chunk asynchronously.
245             // Once the chunk will be sent, the DownloadCompletionHandler.completed(...) method
246             // will be called, or DownloadCompletionHandler.failed(...) is error will happen.
247             ctx.write(content, this);
248         }
249 
250         /**
251          * Method gets called, when file chunk was successfully sent.
252          * @param result the result
253          */
254         @Override
255         public void completed(WriteResult result) {
256             try {
257                 if (!isDone) {
258                     // if transfer is not completed - send next file chunk
259                     sendFileChunk();
260                 } else {
261                     // if transfer is completed - close the local file input stream.
262                     close();
263                     // resume(finishing) HttpRequestPacket processing
264                     resume();
265                 }
266             } catch (IOException e) {
267                 failed(e);
268             }
269         }
270 
271         /**
272          * The method will be called, when file transferring was canceled
273          */
274         @Override
275         public void cancelled() {
276             // Close local file input stream
277             close();
278             // resume the HttpRequestPacket processing
279             resume();
280         }
281 
282         /**
283          * The method will be called, if file transferring was failed.
284          * @param throwable the cause
285          */
286         @Override
287         public void failed(Throwable throwable) {
288             // Close local file input stream
289             close();
290             // resume the HttpRequestPacket processing
291             resume();
292         }
293 
294         /**
295          * Returns <tt>true</tt>, if file transfer was completed, or
296          * <tt>false</tt> otherwise.
297          *
298          * @return <tt>true</tt>, if file transfer was completed, or
299          * <tt>false</tt> otherwise.
300          */
301         public boolean isDone() {
302             return isDone;
303         }
304 
305         /**
306          * Close the local file input stream.
307          */
308         private void close() {
309             try {
310                 in.close();
311             } catch (IOException e) {
312                 logger.fine("Error closing a downloading file");
313             }
314         }
315 
316         /**
317          * Resume the HttpRequestPacket processing
318          */
319         private void resume() {
320             // Set this DownloadCompletionHandler as message
321             ctx.setMessage(this);
322             // Resume the request processing
323             // After resume will be called - filter chain will execute
324             // WebServerFilter.handleRead(...) again with the ctx as FilterChainContext.
325             ctx.resume();
326         }
327     }
328 }

Again, we'll let the inline documentation in the examples provide the details on what's going on here. Let's see the output of the examples in action. Here's the server start:

[586][target]$ java -cp grizzly-http-samples-2.2.10.jar org.glassfish.grizzly.samples.http.download.Server /tmp
Jan 28, 2011 1:11:17 PM org.glassfish.grizzly.samples.http.download.Server main
INFO: Press any key to stop the server...

And on the client side:

[574][ryanlubke.lunasa: target]$ java -cp grizzly-http-samples-2.2.10.jar org.glassfish.grizzly.samples.http.download.Client http://localhost:7777/test.html
Jan 28, 2011 1:54:49 PM org.glassfish.grizzly.samples.http.download.ClientDownloadFilter handleConnect
INFO: Connected... Sending the request: HttpRequestPacket (
   method=GET
   url=/test.html
   query=null
   protocol=HTTP_1_1
   content-length=-1
   headers=[
      Host=localhost]
)
Jan 28, 2011 1:54:49 PM org.glassfish.grizzly.samples.http.download.ClientDownloadFilter handleRead
INFO: HTTP response: HttpResponsePacket (status=200 reason=OK protocol=HTTP_1_1 content-length=-1 headers==== MimeHeaders ===
date = Fri, 28 Jan 2011 21:54:49 GMT
transfer-encoding = chunked
 committed=false)
Jan 28, 2011 1:54:49 PM org.glassfish.grizzly.samples.http.download.Client main
INFO: File test.html was successfully downloaded
Jan 28, 2011 1:54:49 PM org.glassfish.grizzly.samples.http.download.Client main
INFO: Stopping transport...
Jan 28, 2011 1:54:49 PM org.glassfish.grizzly.samples.http.download.Client main
INFO: Stopped transport...

This example within the java.net maven repository: https://maven.java.net/content/repositories/releases/org/glassfish/grizzly/samples/grizzly-http-samples/2.2.10.