398467 Servlet 3.1 Non Blocking IO

Improved sendContent API and javadoc. Added FastFileServer example
This commit is contained in:
Greg Wilkins 2013-05-16 09:26:37 +10:00
parent c03cb95e8a
commit 76d4859eda
4 changed files with 315 additions and 13 deletions

View File

@ -0,0 +1,181 @@
//
// ========================================================================
// Copyright (c) 1995-2013 Mort Bay Consulting Pty. Ltd.
// ------------------------------------------------------------------------
// All rights reserved. This program and the accompanying materials
// are made available under the terms of the Eclipse Public License v1.0
// and Apache License v2.0 which accompanies this distribution.
//
// The Eclipse Public License is available at
// http://www.eclipse.org/legal/epl-v10.html
//
// The Apache License v2.0 is available at
// http://www.opensource.org/licenses/apache2.0.php
//
// You may elect to redistribute this code under either of these licenses.
// ========================================================================
//
package org.eclipse.jetty.embedded;
import java.io.File;
import java.io.IOException;
import java.io.RandomAccessFile;
import java.nio.ByteBuffer;
import java.nio.channels.FileChannel;
import java.nio.channels.FileChannel.MapMode;
import java.nio.file.StandardOpenOption;
import javax.servlet.AsyncContext;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.eclipse.jetty.http.MimeTypes;
import org.eclipse.jetty.server.Handler;
import org.eclipse.jetty.server.HttpOutput;
import org.eclipse.jetty.server.Request;
import org.eclipse.jetty.server.Server;
import org.eclipse.jetty.server.handler.AbstractHandler;
import org.eclipse.jetty.server.handler.DefaultHandler;
import org.eclipse.jetty.server.handler.HandlerList;
import org.eclipse.jetty.server.handler.ResourceHandler;
import org.eclipse.jetty.servlet.DefaultServlet;
import org.eclipse.jetty.util.Callback;
import org.eclipse.jetty.util.URIUtil;
import org.eclipse.jetty.util.resource.Resource;
/* ------------------------------------------------------------ */
/** Fast FileServer.
*
* <p>This example shows how to use the Jetty APIs for sending static
* as fast as possible using various strategies for small, medium and
* large content.</p>
* <p>The Jetty {@link DefaultServlet} does all this and more, and to
* a lesser extent so does the {@link ResourceHandler}, so unless you
* have exceptional circumstances it is best to use those classes for
* static content</p>
*/
public class FastFileServer
{
public static void main(String[] args) throws Exception
{
Server server = new Server(8080);
HandlerList handlers = new HandlerList();
handlers.setHandlers(new Handler[] { new FastFileHandler(new File(".")), new DefaultHandler() });
server.setHandler(handlers);
server.start();
server.join();
}
static class FastFileHandler extends AbstractHandler
{
private final MimeTypes _mimeTypes = new MimeTypes();
private final File _dir;
FastFileHandler(File dir)
{
_dir=dir;
}
@Override
public void handle(String target, Request baseRequest, HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException
{
// define small medium and large.
// This should be turned for your content, JVM and OS, but we will huge HTTP response buffer size as a measure
final int SMALL=response.getBufferSize();
final int MEDIUM=8*SMALL;
// What file to serve?
final File file = new File(_dir,request.getPathInfo());
// Only handle existing files
if (!file.exists())
return;
// we will handle this request
baseRequest.setHandled(true);
// Handle directories
if (file.isDirectory())
{
if (!request.getPathInfo().endsWith(URIUtil.SLASH))
{
response.sendRedirect(response.encodeRedirectURL(URIUtil.addPaths(request.getRequestURI(),URIUtil.SLASH)));
return;
}
String listing = Resource.newResource(file).getListHTML(request.getRequestURI(),request.getPathInfo().lastIndexOf("/") > 0);
response.setContentType("text/html; charset=UTF-8");
response.getWriter().println(listing);
return;
}
// Set some content headers
// Jetty DefaultServlet will cache formatted date strings, but we will reformat for each request here
response.setDateHeader("Last-Modified",file.lastModified());
response.setDateHeader("Content-Length",file.length());
response.setContentType(_mimeTypes.getMimeByExtension(file.getName()));
// send "small" files blocking directly from an input stream
if (file.length()<SMALL)
{
// need to caste to Jetty output stream for best API
((HttpOutput)response.getOutputStream()).sendContent(FileChannel.open(file.toPath(),StandardOpenOption.READ));
return;
}
// send not "small" files asynchronously so we don't hold threads if the client is slow
final AsyncContext async = request.startAsync();
Callback completionCB = new Callback()
{
@Override
public void succeeded()
{
// Async content write succeeded, so complete async response
async.complete();
}
@Override
public void failed(Throwable x)
{
// log error and complete async response;
x.printStackTrace();
async.complete();
}
};
// send "medium" files from an input stream
if (file.length()<MEDIUM)
{
((HttpOutput)response.getOutputStream()).sendContent(FileChannel.open(file.toPath(),StandardOpenOption.READ),completionCB);
return;
}
// for "large" files get the file mapped buffer to send
// Typically the resulting buffer should be cached as allocating kernel memory
// can be hard to GC on some JVMs. But for this example we will create a new buffer per file
ByteBuffer buffer;
try (RandomAccessFile raf = new RandomAccessFile(file,"r");)
{
buffer=raf.getChannel().map(MapMode.READ_ONLY,0,raf.length());
}
// Assuming the file buffer might be shared cached version, so lets take our own view of it
buffer=buffer.asReadOnlyBuffer();
// send the content as a buffer with a callback to complete the async request
// need to caste to Jetty output stream for best API
((HttpOutput)response.getOutputStream()).sendContent(buffer,completionCB);
}
}
}

View File

@ -231,6 +231,12 @@ public class HttpOutput extends ServletOutputStream
write(s.getBytes(_channel.getResponse().getCharacterEncoding()));
}
/* ------------------------------------------------------------ */
/** Set headers and send content.
* @deprecated Use {@link Response#setHeaders(HttpContent)} and {@link #sendContent(HttpContent)} instead.
* @param content
* @throws IOException
*/
@Deprecated
public void sendContent(Object content) throws IOException
{
@ -277,7 +283,69 @@ public class HttpOutput extends ServletOutputStream
}
}
/* ------------------------------------------------------------ */
/** Blocking send of content.
* @param content The content to send
* @throws IOException
*/
public void sendContent(ByteBuffer content) throws IOException
{
try
{
final BlockingCallback callback =_channel.getWriteBlockingCallback();
_channel.write(content,true,callback);
callback.block();
}
catch (InterruptedException | TimeoutException e)
{
throw new IOException(e);
}
}
/* ------------------------------------------------------------ */
/** Blocking send of content.
* @param content The content to send
* @throws IOException
*/
public void sendContent(InputStream in) throws IOException
{
try
{
final BlockingCallback callback =_channel.getWriteBlockingCallback();
new InputStreamWritingCB(in,callback).iterate();
callback.block();
}
catch (InterruptedException | TimeoutException e)
{
throw new IOException(e);
}
}
/* ------------------------------------------------------------ */
/** Blocking send of content.
* @param content The content to send
* @throws IOException
*/
public void sendContent(ReadableByteChannel in) throws IOException
{
try
{
final BlockingCallback callback =_channel.getWriteBlockingCallback();
new ReadableByteChannelWritingCB(in,callback).iterate();
callback.block();
}
catch (InterruptedException | TimeoutException e)
{
throw new IOException(e);
}
}
/* ------------------------------------------------------------ */
/** Blocking send of content.
* @param content The content to send
* @throws IOException
*/
public void sendContent(HttpContent content) throws IOException
{
try
@ -292,21 +360,42 @@ public class HttpOutput extends ServletOutputStream
}
}
/* ------------------------------------------------------------ */
/** Asynchronous send of content.
* @param content The content to send
* @param callback The callback to use to notify success or failure
*/
public void sendContent(ByteBuffer content, Callback callback)
{
_channel.write(content,true,callback);
}
/* ------------------------------------------------------------ */
/** Asynchronous send of content.
* @param content The content to send
* @param callback The callback to use to notify success or failure
*/
public void sendContent(InputStream in, Callback callback)
{
new InputStreamWritingCB(in,callback).iterate();
}
/* ------------------------------------------------------------ */
/** Asynchronous send of content.
* @param content The content to send
* @param callback The callback to use to notify success or failure
*/
public void sendContent(ReadableByteChannel in, Callback callback)
{
new ReadableByteChannelWritingCB(in,callback).iterate();
}
/* ------------------------------------------------------------ */
/** Asynchronous send of content.
* @param content The content to send
* @param callback The callback to use to notify success or failure
*/
public void sendContent(HttpContent httpContent, Callback callback) throws IOException
{
if (isClosed())
@ -361,6 +450,15 @@ public class HttpOutput extends ServletOutputStream
BufferUtil.clear(_aggregate);
}
/* ------------------------------------------------------------ */
/** An iterating callback that will take content from an
* InputStream and write it to the associated {@link HttpChannel}.
* A non direct buffer of size {@link HttpOutput#getBufferSize()} is used.
* This callback is passed to the {@link HttpChannel#write(ByteBuffer, boolean, Callback)} to
* be notified as each buffer is written and only once all the input is consumed will the
* wrapped {@link Callback#succeeded()} method be called.
*/
private class InputStreamWritingCB extends IteratingCallback
{
final InputStream _in;
@ -409,6 +507,15 @@ public class HttpOutput extends ServletOutputStream
}
/* ------------------------------------------------------------ */
/** An iterating callback that will take content from a
* ReadableByteChannel and write it to the {@link HttpChannel}.
* A {@link ByteBuffer} of size {@link HttpOutput#getBufferSize()} is used that will be direct if
* {@link HttpChannel#useDirectBuffers()} is true.
* This callback is passed to the {@link HttpChannel#write(ByteBuffer, boolean, Callback)} to
* be notified as each buffer is written and only once all the input is consumed will the
* wrapped {@link Callback#succeeded()} method be called.
*/
private class ReadableByteChannelWritingCB extends IteratingCallback
{
final ReadableByteChannel _in;
@ -456,6 +563,5 @@ public class HttpOutput extends ServletOutputStream
super.failed(x);
_channel.getByteBufferPool().release(_buffer);
}
}
}

View File

@ -22,6 +22,7 @@ import java.io.IOException;
import java.io.OutputStream;
import java.net.MalformedURLException;
import javax.servlet.AsyncContext;
import javax.servlet.RequestDispatcher;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
@ -37,6 +38,7 @@ import org.eclipse.jetty.server.HttpOutput;
import org.eclipse.jetty.server.Request;
import org.eclipse.jetty.server.Response;
import org.eclipse.jetty.server.handler.ContextHandler.Context;
import org.eclipse.jetty.util.Callback;
import org.eclipse.jetty.util.URIUtil;
import org.eclipse.jetty.util.log.Log;
import org.eclipse.jetty.util.log.Logger;
@ -438,22 +440,34 @@ public class ResourceHandler extends HandlerWrapper
if(skipContentBody)
return;
// Send the content
OutputStream out =null;
try {out = response.getOutputStream();}
catch(IllegalStateException e) {out = new WriterOutputStream(response.getWriter());}
if (out instanceof HttpOutput)
{
((HttpOutput)out).sendContent(resource);
}
else
{
if (last_modified>0)
response.setDateHeader(HttpHeader.LAST_MODIFIED.asString(),last_modified);
if (out instanceof HttpOutput && request.isAsyncSupported())
{
final AsyncContext async = request.startAsync();
((HttpOutput)out).sendContent(resource.getReadableByteChannel(),new Callback()
{
@Override
public void succeeded()
{
async.complete();
}
@Override
public void failed(Throwable x)
{
async.complete();
}
});
}
else
{
// Write content normally
resource.writeTo(out,0,resource.length());
}

View File

@ -731,9 +731,10 @@ public class BufferUtil
public static ByteBuffer toBuffer(File file) throws IOException
{
RandomAccessFile raf = new RandomAccessFile(file,"r");
MappedByteBuffer buffer=raf.getChannel().map(MapMode.READ_ONLY,0,raf.length());
return buffer;
try(RandomAccessFile raf = new RandomAccessFile(file,"r");)
{
return raf.getChannel().map(MapMode.READ_ONLY,0,raf.length());
}
}
public static String toSummaryString(ByteBuffer buffer)