398467 Servlet 3.1 Non Blocking IO
Improved sendContent API and javadoc. Added FastFileServer example
This commit is contained in:
parent
c03cb95e8a
commit
76d4859eda
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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
|
||||
{
|
||||
|
@ -276,8 +282,70 @@ public class HttpOutput extends ServletOutputStream
|
|||
throw new IOException(e);
|
||||
}
|
||||
}
|
||||
|
||||
/* ------------------------------------------------------------ */
|
||||
/** 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
|
||||
|
@ -291,22 +359,43 @@ public class HttpOutput extends ServletOutputStream
|
|||
throw new IOException(e);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/* ------------------------------------------------------------ */
|
||||
/** 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;
|
||||
|
@ -408,7 +506,16 @@ 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);
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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 (last_modified>0)
|
||||
response.setDateHeader(HttpHeader.LAST_MODIFIED.asString(),last_modified);
|
||||
|
||||
if (out instanceof HttpOutput)
|
||||
if (out instanceof HttpOutput && request.isAsyncSupported())
|
||||
{
|
||||
((HttpOutput)out).sendContent(resource);
|
||||
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
|
||||
{
|
||||
if (last_modified>0)
|
||||
response.setDateHeader(HttpHeader.LAST_MODIFIED.asString(),last_modified);
|
||||
|
||||
// Write content normally
|
||||
resource.writeTo(out,0,resource.length());
|
||||
}
|
||||
|
|
|
@ -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)
|
||||
|
|
Loading…
Reference in New Issue