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()));
|
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
|
@Deprecated
|
||||||
public void sendContent(Object content) throws IOException
|
public void sendContent(Object content) throws IOException
|
||||||
{
|
{
|
||||||
|
@ -276,8 +282,70 @@ public class HttpOutput extends ServletOutputStream
|
||||||
throw new IOException(e);
|
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
|
public void sendContent(HttpContent content) throws IOException
|
||||||
{
|
{
|
||||||
try
|
try
|
||||||
|
@ -291,22 +359,43 @@ public class HttpOutput extends ServletOutputStream
|
||||||
throw new IOException(e);
|
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)
|
public void sendContent(ByteBuffer content, Callback callback)
|
||||||
{
|
{
|
||||||
_channel.write(content,true,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)
|
public void sendContent(InputStream in, Callback callback)
|
||||||
{
|
{
|
||||||
new InputStreamWritingCB(in,callback).iterate();
|
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)
|
public void sendContent(ReadableByteChannel in, Callback callback)
|
||||||
{
|
{
|
||||||
new ReadableByteChannelWritingCB(in,callback).iterate();
|
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
|
public void sendContent(HttpContent httpContent, Callback callback) throws IOException
|
||||||
{
|
{
|
||||||
if (isClosed())
|
if (isClosed())
|
||||||
|
@ -361,6 +450,15 @@ public class HttpOutput extends ServletOutputStream
|
||||||
BufferUtil.clear(_aggregate);
|
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
|
private class InputStreamWritingCB extends IteratingCallback
|
||||||
{
|
{
|
||||||
final InputStream _in;
|
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
|
private class ReadableByteChannelWritingCB extends IteratingCallback
|
||||||
{
|
{
|
||||||
final ReadableByteChannel _in;
|
final ReadableByteChannel _in;
|
||||||
|
@ -456,6 +563,5 @@ public class HttpOutput extends ServletOutputStream
|
||||||
super.failed(x);
|
super.failed(x);
|
||||||
_channel.getByteBufferPool().release(_buffer);
|
_channel.getByteBufferPool().release(_buffer);
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -22,6 +22,7 @@ import java.io.IOException;
|
||||||
import java.io.OutputStream;
|
import java.io.OutputStream;
|
||||||
import java.net.MalformedURLException;
|
import java.net.MalformedURLException;
|
||||||
|
|
||||||
|
import javax.servlet.AsyncContext;
|
||||||
import javax.servlet.RequestDispatcher;
|
import javax.servlet.RequestDispatcher;
|
||||||
import javax.servlet.ServletException;
|
import javax.servlet.ServletException;
|
||||||
import javax.servlet.http.HttpServletRequest;
|
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.Request;
|
||||||
import org.eclipse.jetty.server.Response;
|
import org.eclipse.jetty.server.Response;
|
||||||
import org.eclipse.jetty.server.handler.ContextHandler.Context;
|
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.URIUtil;
|
||||||
import org.eclipse.jetty.util.log.Log;
|
import org.eclipse.jetty.util.log.Log;
|
||||||
import org.eclipse.jetty.util.log.Logger;
|
import org.eclipse.jetty.util.log.Logger;
|
||||||
|
@ -438,22 +440,34 @@ public class ResourceHandler extends HandlerWrapper
|
||||||
if(skipContentBody)
|
if(skipContentBody)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
|
|
||||||
// Send the content
|
// Send the content
|
||||||
OutputStream out =null;
|
OutputStream out =null;
|
||||||
try {out = response.getOutputStream();}
|
try {out = response.getOutputStream();}
|
||||||
catch(IllegalStateException e) {out = new WriterOutputStream(response.getWriter());}
|
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
|
else
|
||||||
{
|
{
|
||||||
if (last_modified>0)
|
|
||||||
response.setDateHeader(HttpHeader.LAST_MODIFIED.asString(),last_modified);
|
|
||||||
|
|
||||||
// Write content normally
|
// Write content normally
|
||||||
resource.writeTo(out,0,resource.length());
|
resource.writeTo(out,0,resource.length());
|
||||||
}
|
}
|
||||||
|
|
|
@ -731,9 +731,10 @@ public class BufferUtil
|
||||||
|
|
||||||
public static ByteBuffer toBuffer(File file) throws IOException
|
public static ByteBuffer toBuffer(File file) throws IOException
|
||||||
{
|
{
|
||||||
RandomAccessFile raf = new RandomAccessFile(file,"r");
|
try(RandomAccessFile raf = new RandomAccessFile(file,"r");)
|
||||||
MappedByteBuffer buffer=raf.getChannel().map(MapMode.READ_ONLY,0,raf.length());
|
{
|
||||||
return buffer;
|
return raf.getChannel().map(MapMode.READ_ONLY,0,raf.length());
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public static String toSummaryString(ByteBuffer buffer)
|
public static String toSummaryString(ByteBuffer buffer)
|
||||||
|
|
Loading…
Reference in New Issue