Merge remote-tracking branch 'origin/master' into servlet-3.1-api
Conflicts: jetty-server/src/main/java/org/eclipse/jetty/server/HttpOutput.java
This commit is contained in:
commit
5397f16559
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -54,13 +54,13 @@ public class Activator implements BundleActivator
|
|||
@Override
|
||||
public void contextInitialized(ServletContextEvent sce)
|
||||
{
|
||||
System.err.println("Context is initialized");
|
||||
// System.err.println("Context is initialized");
|
||||
}
|
||||
|
||||
@Override
|
||||
public void contextDestroyed(ServletContextEvent sce)
|
||||
{
|
||||
System.err.println("CONTEXT IS DESTROYED!");
|
||||
// System.err.println("CONTEXT IS DESTROYED!");
|
||||
}
|
||||
|
||||
});
|
||||
|
|
|
@ -120,6 +120,11 @@ public class HttpChannel<T> implements HttpParser.RequestHandler<T>, Runnable
|
|||
return _version;
|
||||
}
|
||||
|
||||
BlockingCallback getWriteBlockingCallback()
|
||||
{
|
||||
return _writeblock;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return the number of requests handled by this connection
|
||||
*/
|
||||
|
@ -587,9 +592,7 @@ public class HttpChannel<T> implements HttpParser.RequestHandler<T>, Runnable
|
|||
try
|
||||
{
|
||||
if (_state.handling()==Next.CONTINUE)
|
||||
{
|
||||
sendResponse(new ResponseInfo(HttpVersion.HTTP_1_1,new HttpFields(),0,status,reason,false),null,true);
|
||||
}
|
||||
}
|
||||
catch (IOException e)
|
||||
{
|
||||
|
@ -609,62 +612,19 @@ public class HttpChannel<T> implements HttpParser.RequestHandler<T>, Runnable
|
|||
{
|
||||
// We need an info to commit
|
||||
if (info==null)
|
||||
{
|
||||
info = _response.newResponseInfo();
|
||||
}
|
||||
|
||||
// wrap callback to process 100 or 500 responses
|
||||
final int status=info.getStatus();
|
||||
final Callback committed = new Callback()
|
||||
{
|
||||
@Override
|
||||
public void succeeded()
|
||||
{
|
||||
// If we are committing a 1xx response, we need to reset the commit
|
||||
// status so that the "real" response can be committed again.
|
||||
if (status<200 && status>=100)
|
||||
_committed.set(false);
|
||||
callback.succeeded();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void failed(final Throwable x)
|
||||
{
|
||||
if (x instanceof EofException)
|
||||
{
|
||||
LOG.debug(x);
|
||||
_response.getHttpOutput().closed();
|
||||
callback.failed(x);
|
||||
}
|
||||
else
|
||||
{
|
||||
LOG.warn(x);
|
||||
_transport.send(HttpGenerator.RESPONSE_500_INFO,null,true,new Callback()
|
||||
{
|
||||
@Override
|
||||
public void succeeded()
|
||||
{
|
||||
_response.getHttpOutput().closed();
|
||||
callback.failed(x);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void failed(Throwable th)
|
||||
{
|
||||
LOG.ignore(th);
|
||||
_response.getHttpOutput().closed();
|
||||
callback.failed(x);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
};
|
||||
final Callback committed = (status<200&&status>=100)?new Commit100Callback(callback):new CommitCallback(callback);
|
||||
|
||||
// committing write
|
||||
_transport.send(info, content, complete, committed);
|
||||
}
|
||||
else if (info==null)
|
||||
{
|
||||
// This is a normal write
|
||||
_transport.send(null, content, complete, callback);
|
||||
_transport.send(content, complete, callback);
|
||||
}
|
||||
else
|
||||
{
|
||||
|
@ -676,16 +636,7 @@ public class HttpChannel<T> implements HttpParser.RequestHandler<T>, Runnable
|
|||
protected boolean sendResponse(ResponseInfo info, ByteBuffer content, boolean complete) throws IOException
|
||||
{
|
||||
boolean committing=sendResponse(info,content,complete,_writeblock);
|
||||
|
||||
try
|
||||
{
|
||||
_writeblock.block();
|
||||
}
|
||||
catch (InterruptedException | TimeoutException e)
|
||||
{
|
||||
throw new IOException(e);
|
||||
}
|
||||
|
||||
_writeblock.block();
|
||||
return committing;
|
||||
}
|
||||
|
||||
|
@ -695,8 +646,7 @@ public class HttpChannel<T> implements HttpParser.RequestHandler<T>, Runnable
|
|||
}
|
||||
|
||||
/**
|
||||
* <p>Requests to write (in a blocking way) the given response content buffer,
|
||||
* committing the response if needed.</p>
|
||||
* <p>Blocking write, committing the response if needed.</p>
|
||||
*
|
||||
* @param content the content buffer to write
|
||||
* @param complete whether the content is complete for the response
|
||||
|
@ -704,7 +654,21 @@ public class HttpChannel<T> implements HttpParser.RequestHandler<T>, Runnable
|
|||
*/
|
||||
protected void write(ByteBuffer content, boolean complete) throws IOException
|
||||
{
|
||||
sendResponse(null, content, complete);
|
||||
sendResponse(null,content,complete,_writeblock);
|
||||
_writeblock.block();
|
||||
}
|
||||
|
||||
/**
|
||||
* <p>Non-Blocking write, committing the response if needed.</p>
|
||||
*
|
||||
* @param content the content buffer to write
|
||||
* @param complete whether the content is complete for the response
|
||||
* @param callback Callback when complete or failed
|
||||
* @throws IOException if the write fails
|
||||
*/
|
||||
protected void write(ByteBuffer content, boolean complete, Callback callback)
|
||||
{
|
||||
sendResponse(null,content,complete,callback);
|
||||
}
|
||||
|
||||
protected void execute(Runnable task)
|
||||
|
@ -725,4 +689,68 @@ public class HttpChannel<T> implements HttpParser.RequestHandler<T>, Runnable
|
|||
{
|
||||
return getEndPoint() instanceof ChannelEndPoint;
|
||||
}
|
||||
|
||||
private class CommitCallback implements Callback
|
||||
{
|
||||
private final Callback _callback;
|
||||
|
||||
private CommitCallback(Callback callback)
|
||||
{
|
||||
_callback = callback;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void succeeded()
|
||||
{
|
||||
_callback.succeeded();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void failed(final Throwable x)
|
||||
{
|
||||
if (x instanceof EofException)
|
||||
{
|
||||
LOG.debug(x);
|
||||
_response.getHttpOutput().closed();
|
||||
_callback.failed(x);
|
||||
}
|
||||
else
|
||||
{
|
||||
LOG.warn(x);
|
||||
_transport.send(HttpGenerator.RESPONSE_500_INFO,null,true,new Callback()
|
||||
{
|
||||
@Override
|
||||
public void succeeded()
|
||||
{
|
||||
_response.getHttpOutput().closed();
|
||||
_callback.failed(x);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void failed(Throwable th)
|
||||
{
|
||||
LOG.ignore(th);
|
||||
_response.getHttpOutput().closed();
|
||||
_callback.failed(x);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private class Commit100Callback extends CommitCallback
|
||||
{
|
||||
private Commit100Callback(Callback callback)
|
||||
{
|
||||
super(callback);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void succeeded()
|
||||
{
|
||||
_committed.set(false);
|
||||
super.succeeded();
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
|
|
@ -569,6 +569,24 @@ public class HttpChannelState
|
|||
}
|
||||
}
|
||||
|
||||
public boolean isAsyncStarted()
|
||||
{
|
||||
synchronized (this)
|
||||
{
|
||||
switch(_state)
|
||||
{
|
||||
case ASYNCSTARTED:
|
||||
case REDISPATCHING:
|
||||
case COMPLETECALLED:
|
||||
case ASYNCWAIT:
|
||||
return true;
|
||||
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public boolean isAsync()
|
||||
{
|
||||
synchronized (this)
|
||||
|
|
|
@ -41,6 +41,7 @@ import org.eclipse.jetty.io.EofException;
|
|||
import org.eclipse.jetty.util.BlockingCallback;
|
||||
import org.eclipse.jetty.util.BufferUtil;
|
||||
import org.eclipse.jetty.util.Callback;
|
||||
import org.eclipse.jetty.util.IteratingCallback;
|
||||
import org.eclipse.jetty.util.log.Log;
|
||||
import org.eclipse.jetty.util.log.Logger;
|
||||
|
||||
|
@ -304,50 +305,54 @@ public class HttpConnection extends AbstractConnection implements Runnable, Http
|
|||
onFillable();
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public void send(HttpGenerator.ResponseInfo info, ByteBuffer content, boolean lastContent) throws IOException
|
||||
{
|
||||
try
|
||||
{
|
||||
// If we are still expecting a 100 continues
|
||||
if (info !=null && _channel.isExpecting100Continue())
|
||||
// then we can't be persistent
|
||||
_generator.setPersistent(false);
|
||||
|
||||
Sender sender = new Sender(content,lastContent,_writeBlocker);
|
||||
sender.process(info);
|
||||
|
||||
if (info==null)
|
||||
new ContentCallback(content,lastContent,_writeBlocker).iterate();
|
||||
else
|
||||
{
|
||||
// If we are still expecting a 100 continues
|
||||
if (_channel.isExpecting100Continue())
|
||||
// then we can't be persistent
|
||||
_generator.setPersistent(false);
|
||||
new CommitCallback(info,content,lastContent,_writeBlocker).iterate();
|
||||
}
|
||||
_writeBlocker.block();
|
||||
}
|
||||
catch (InterruptedException x)
|
||||
{
|
||||
x.printStackTrace();
|
||||
throw (IOException)new InterruptedIOException().initCause(x);
|
||||
}
|
||||
catch (TimeoutException e)
|
||||
{
|
||||
e.printStackTrace();
|
||||
throw new IOException(e);
|
||||
}
|
||||
catch (ClosedChannelException e)
|
||||
{
|
||||
e.printStackTrace();
|
||||
throw new EofException(e);
|
||||
}
|
||||
catch (IOException e)
|
||||
{
|
||||
throw e;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void send(ResponseInfo info, ByteBuffer content, boolean lastContent, Callback callback)
|
||||
{
|
||||
// If we are still expecting a 100 continues
|
||||
if (info !=null && _channel.isExpecting100Continue())
|
||||
// then we can't be persistent
|
||||
_generator.setPersistent(false);
|
||||
|
||||
new Sender(content,lastContent,callback).process(info);
|
||||
if (info==null)
|
||||
new ContentCallback(content,lastContent,callback).iterate();
|
||||
else
|
||||
{
|
||||
// If we are still expecting a 100 continues
|
||||
if (_channel.isExpecting100Continue())
|
||||
// then we can't be persistent
|
||||
_generator.setPersistent(false);
|
||||
new CommitCallback(info,content,lastContent,callback).iterate();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void send(ByteBuffer content, boolean lastContent, Callback callback)
|
||||
{
|
||||
new ContentCallback(content,lastContent,callback).iterate();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void completed()
|
||||
{
|
||||
|
@ -422,68 +427,57 @@ public class HttpConnection extends AbstractConnection implements Runnable, Http
|
|||
default implementation of a blocking queue with an implementation
|
||||
that uses the calling thread to block on a readable callback and
|
||||
then to do the parsing before before attempting the read.
|
||||
*/
|
||||
try
|
||||
*/
|
||||
while (true)
|
||||
{
|
||||
while (true)
|
||||
// Can the parser progress (even with an empty buffer)
|
||||
boolean event=_parser.parseNext(_requestBuffer==null?BufferUtil.EMPTY_BUFFER:_requestBuffer);
|
||||
|
||||
// If there is more content to parse, loop so we can queue all content from this buffer now without the
|
||||
// need to call blockForContent again
|
||||
while (event && BufferUtil.hasContent(_requestBuffer) && _parser.inContentState())
|
||||
_parser.parseNext(_requestBuffer);
|
||||
|
||||
// If we have an event, return
|
||||
if (event)
|
||||
return;
|
||||
|
||||
// Do we have content ready to parse?
|
||||
if (BufferUtil.isEmpty(_requestBuffer))
|
||||
{
|
||||
// Can the parser progress (even with an empty buffer)
|
||||
boolean event=_parser.parseNext(_requestBuffer==null?BufferUtil.EMPTY_BUFFER:_requestBuffer);
|
||||
|
||||
// If there is more content to parse, loop so we can queue all content from this buffer now without the
|
||||
// need to call blockForContent again
|
||||
while (event && BufferUtil.hasContent(_requestBuffer) && _parser.inContentState())
|
||||
_parser.parseNext(_requestBuffer);
|
||||
|
||||
// If we have an event, return
|
||||
if (event)
|
||||
return;
|
||||
|
||||
// Do we have content ready to parse?
|
||||
if (BufferUtil.isEmpty(_requestBuffer))
|
||||
// If no more input
|
||||
if (getEndPoint().isInputShutdown())
|
||||
{
|
||||
// If no more input
|
||||
if (getEndPoint().isInputShutdown())
|
||||
{
|
||||
_parser.shutdownInput();
|
||||
shutdown();
|
||||
return;
|
||||
}
|
||||
_parser.shutdownInput();
|
||||
shutdown();
|
||||
return;
|
||||
}
|
||||
|
||||
// Wait until we can read
|
||||
block(_readBlocker);
|
||||
LOG.debug("{} block readable on {}",this,_readBlocker);
|
||||
_readBlocker.block();
|
||||
// Wait until we can read
|
||||
block(_readBlocker);
|
||||
LOG.debug("{} block readable on {}",this,_readBlocker);
|
||||
_readBlocker.block();
|
||||
|
||||
// We will need a buffer to read into
|
||||
if (_requestBuffer==null)
|
||||
{
|
||||
long content_length=_channel.getRequest().getContentLength();
|
||||
int size=getInputBufferSize();
|
||||
if (size<content_length)
|
||||
size=size*4; // TODO tune this
|
||||
_requestBuffer=_bufferPool.acquire(size,REQUEST_BUFFER_DIRECT);
|
||||
}
|
||||
// We will need a buffer to read into
|
||||
if (_requestBuffer==null)
|
||||
{
|
||||
long content_length=_channel.getRequest().getContentLength();
|
||||
int size=getInputBufferSize();
|
||||
if (size<content_length)
|
||||
size=size*4; // TODO tune this
|
||||
_requestBuffer=_bufferPool.acquire(size,REQUEST_BUFFER_DIRECT);
|
||||
}
|
||||
|
||||
// read some data
|
||||
int filled=getEndPoint().fill(_requestBuffer);
|
||||
LOG.debug("{} block filled {}",this,filled);
|
||||
if (filled<0)
|
||||
{
|
||||
_parser.shutdownInput();
|
||||
return;
|
||||
}
|
||||
// read some data
|
||||
int filled=getEndPoint().fill(_requestBuffer);
|
||||
LOG.debug("{} block filled {}",this,filled);
|
||||
if (filled<0)
|
||||
{
|
||||
_parser.shutdownInput();
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (TimeoutException e)
|
||||
{
|
||||
throw new EofException(e);
|
||||
}
|
||||
catch (final InterruptedException x)
|
||||
{
|
||||
throw new InterruptedIOException(getEndPoint().toString()){{initCause(x);}};
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -586,147 +580,208 @@ public class HttpConnection extends AbstractConnection implements Runnable, Http
|
|||
_generator.setPersistent(false);
|
||||
super.handleException(x);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
private class Sender implements Callback
|
||||
private class CommitCallback extends IteratingCallback
|
||||
{
|
||||
final ByteBuffer _content;
|
||||
final boolean _lastContent;
|
||||
final Callback _callback;
|
||||
final ResponseInfo _info;
|
||||
ByteBuffer _header;
|
||||
|
||||
Sender(ByteBuffer content, boolean last, Callback callback)
|
||||
CommitCallback(ResponseInfo info, ByteBuffer content, boolean last, Callback callback)
|
||||
{
|
||||
_callback=callback;
|
||||
super(callback);
|
||||
_info=info;
|
||||
_content=content;
|
||||
_lastContent=last;
|
||||
}
|
||||
|
||||
public void process(ResponseInfo info)
|
||||
@Override
|
||||
public boolean process() throws Exception
|
||||
{
|
||||
try
|
||||
ByteBuffer chunk = _chunk;
|
||||
while (true)
|
||||
{
|
||||
ByteBuffer header = null;
|
||||
ByteBuffer chunk = null;
|
||||
while (true)
|
||||
{
|
||||
HttpGenerator.Result result = _generator.generateResponse(info, header, chunk, _content, _lastContent);
|
||||
if (LOG.isDebugEnabled())
|
||||
LOG.debug("{} generate: {} ({},{},{})@{}",
|
||||
this,
|
||||
result,
|
||||
BufferUtil.toSummaryString(header),
|
||||
BufferUtil.toSummaryString(_content),
|
||||
_lastContent,
|
||||
_generator.getState());
|
||||
HttpGenerator.Result result = _generator.generateResponse(_info, _header, chunk, _content, _lastContent);
|
||||
if (LOG.isDebugEnabled())
|
||||
LOG.debug("{} generate: {} ({},{},{})@{}",
|
||||
this,
|
||||
result,
|
||||
BufferUtil.toSummaryString(_header),
|
||||
BufferUtil.toSummaryString(_content),
|
||||
_lastContent,
|
||||
_generator.getState());
|
||||
|
||||
switch (result)
|
||||
switch (result)
|
||||
{
|
||||
case NEED_HEADER:
|
||||
{
|
||||
case NEED_HEADER:
|
||||
if (_lastContent && _content!=null && BufferUtil.space(_content)>_config.getResponseHeaderSize() && _content.hasArray() )
|
||||
{
|
||||
if (_lastContent && _content!=null && BufferUtil.space(_content)>_config.getResponseHeaderSize() && _content.hasArray() )
|
||||
// use spare space in content buffer for header buffer
|
||||
int p=_content.position();
|
||||
int l=_content.limit();
|
||||
_content.position(l);
|
||||
_content.limit(l+_config.getResponseHeaderSize());
|
||||
_header=_content.slice();
|
||||
_header.limit(0);
|
||||
_content.position(p);
|
||||
_content.limit(l);
|
||||
}
|
||||
else
|
||||
_header = _bufferPool.acquire(_config.getResponseHeaderSize(), HEADER_BUFFER_DIRECT);
|
||||
continue;
|
||||
}
|
||||
case NEED_CHUNK:
|
||||
{
|
||||
chunk = _chunk = _bufferPool.acquire(HttpGenerator.CHUNK_SIZE, CHUNK_BUFFER_DIRECT);
|
||||
continue;
|
||||
}
|
||||
case FLUSH:
|
||||
{
|
||||
// Don't write the chunk or the content if this is a HEAD response
|
||||
if (_channel.getRequest().isHead())
|
||||
{
|
||||
BufferUtil.clear(chunk);
|
||||
BufferUtil.clear(_content);
|
||||
}
|
||||
|
||||
// If we have a header
|
||||
if (BufferUtil.hasContent(_header))
|
||||
{
|
||||
if (BufferUtil.hasContent(_content))
|
||||
{
|
||||
// use spare space in content buffer for header buffer
|
||||
int p=_content.position();
|
||||
int l=_content.limit();
|
||||
_content.position(l);
|
||||
_content.limit(l+_config.getResponseHeaderSize());
|
||||
header=_content.slice();
|
||||
header.limit(0);
|
||||
_content.position(p);
|
||||
_content.limit(l);
|
||||
if (BufferUtil.hasContent(chunk))
|
||||
getEndPoint().write(this, _header, chunk, _content);
|
||||
else
|
||||
getEndPoint().write(this, _header, _content);
|
||||
}
|
||||
else
|
||||
header = _bufferPool.acquire(_config.getResponseHeaderSize(), HEADER_BUFFER_DIRECT);
|
||||
continue;
|
||||
getEndPoint().write(this, _header);
|
||||
}
|
||||
case NEED_CHUNK:
|
||||
else if (BufferUtil.hasContent(chunk))
|
||||
{
|
||||
chunk = _chunk;
|
||||
if (chunk==null)
|
||||
chunk = _chunk = _bufferPool.acquire(HttpGenerator.CHUNK_SIZE, CHUNK_BUFFER_DIRECT);
|
||||
continue;
|
||||
}
|
||||
case FLUSH:
|
||||
{
|
||||
// Don't write the chunk or the content if this is a HEAD response
|
||||
if (_channel.getRequest().isHead())
|
||||
{
|
||||
BufferUtil.clear(chunk);
|
||||
BufferUtil.clear(_content);
|
||||
}
|
||||
|
||||
// If we have a header
|
||||
if (BufferUtil.hasContent(header))
|
||||
{
|
||||
// we know there will not be a chunk, so write either header+content or just the header
|
||||
if (BufferUtil.hasContent(_content))
|
||||
getEndPoint().write(this, header, _content);
|
||||
else
|
||||
getEndPoint().write(this, header);
|
||||
}
|
||||
else if (BufferUtil.hasContent(chunk))
|
||||
{
|
||||
if (BufferUtil.hasContent(_content))
|
||||
getEndPoint().write(this, chunk, _content);
|
||||
else
|
||||
getEndPoint().write(this, chunk);
|
||||
}
|
||||
else if (BufferUtil.hasContent(_content))
|
||||
{
|
||||
getEndPoint().write(this, _content);
|
||||
}
|
||||
if (BufferUtil.hasContent(_content))
|
||||
getEndPoint().write(this, chunk, _content);
|
||||
else
|
||||
continue;
|
||||
return;
|
||||
getEndPoint().write(this, chunk);
|
||||
}
|
||||
case SHUTDOWN_OUT:
|
||||
else if (BufferUtil.hasContent(_content))
|
||||
{
|
||||
getEndPoint().shutdownOutput();
|
||||
getEndPoint().write(this, _content);
|
||||
}
|
||||
else
|
||||
continue;
|
||||
}
|
||||
case DONE:
|
||||
return false;
|
||||
}
|
||||
case SHUTDOWN_OUT:
|
||||
{
|
||||
getEndPoint().shutdownOutput();
|
||||
continue;
|
||||
}
|
||||
case DONE:
|
||||
{
|
||||
if (_header!=null)
|
||||
{
|
||||
if (header!=null)
|
||||
{
|
||||
// don't release header in spare content buffer
|
||||
if (!_lastContent || _content==null || !_content.hasArray() || !header.hasArray() || _content.array()!=header.array())
|
||||
_bufferPool.release(header);
|
||||
}
|
||||
if (chunk!=null)
|
||||
_bufferPool.release(chunk);
|
||||
_callback.succeeded();
|
||||
return;
|
||||
}
|
||||
case CONTINUE:
|
||||
{
|
||||
break;
|
||||
}
|
||||
default:
|
||||
{
|
||||
throw new IllegalStateException("generateResponse="+result);
|
||||
// don't release header in spare content buffer
|
||||
if (!_lastContent || _content==null || !_content.hasArray() || !_header.hasArray() || _content.array()!=_header.array())
|
||||
_bufferPool.release(_header);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
case CONTINUE:
|
||||
{
|
||||
break;
|
||||
}
|
||||
default:
|
||||
{
|
||||
throw new IllegalStateException("generateResponse="+result);
|
||||
}
|
||||
}
|
||||
}
|
||||
catch(Exception e)
|
||||
{
|
||||
_callback.failed(e);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void succeeded()
|
||||
{
|
||||
process(null);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void failed(Throwable x)
|
||||
{
|
||||
_callback.failed(x);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
private class ContentCallback extends IteratingCallback
|
||||
{
|
||||
final ByteBuffer _content;
|
||||
final boolean _lastContent;
|
||||
|
||||
ContentCallback(ByteBuffer content, boolean last, Callback callback)
|
||||
{
|
||||
super(callback);
|
||||
_content=content;
|
||||
_lastContent=last;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean process() throws Exception
|
||||
{
|
||||
ByteBuffer chunk = _chunk;
|
||||
while (true)
|
||||
{
|
||||
HttpGenerator.Result result = _generator.generateResponse(null, null, chunk, _content, _lastContent);
|
||||
if (LOG.isDebugEnabled())
|
||||
LOG.debug("{} generate: {} ({},{})@{}",
|
||||
this,
|
||||
result,
|
||||
BufferUtil.toSummaryString(_content),
|
||||
_lastContent,
|
||||
_generator.getState());
|
||||
|
||||
switch (result)
|
||||
{
|
||||
case NEED_HEADER:
|
||||
throw new IllegalStateException();
|
||||
case NEED_CHUNK:
|
||||
{
|
||||
chunk = _chunk = _bufferPool.acquire(HttpGenerator.CHUNK_SIZE, CHUNK_BUFFER_DIRECT);
|
||||
continue;
|
||||
}
|
||||
case FLUSH:
|
||||
{
|
||||
// Don't write the chunk or the content if this is a HEAD response
|
||||
if (_channel.getRequest().isHead())
|
||||
{
|
||||
BufferUtil.clear(chunk);
|
||||
BufferUtil.clear(_content);
|
||||
continue;
|
||||
}
|
||||
else if (BufferUtil.hasContent(chunk))
|
||||
{
|
||||
if (BufferUtil.hasContent(_content))
|
||||
getEndPoint().write(this, chunk, _content);
|
||||
else
|
||||
getEndPoint().write(this, chunk);
|
||||
}
|
||||
else if (BufferUtil.hasContent(_content))
|
||||
{
|
||||
getEndPoint().write(this, _content);
|
||||
}
|
||||
else
|
||||
continue;
|
||||
return false;
|
||||
}
|
||||
case SHUTDOWN_OUT:
|
||||
{
|
||||
getEndPoint().shutdownOutput();
|
||||
continue;
|
||||
}
|
||||
case DONE:
|
||||
{
|
||||
return true;
|
||||
}
|
||||
case CONTINUE:
|
||||
{
|
||||
break;
|
||||
}
|
||||
default:
|
||||
{
|
||||
throw new IllegalStateException("generateResponse="+result);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -23,6 +23,8 @@ import java.io.IOException;
|
|||
import java.io.InputStream;
|
||||
import java.nio.ByteBuffer;
|
||||
import java.nio.channels.ReadableByteChannel;
|
||||
import java.util.concurrent.TimeoutException;
|
||||
|
||||
import javax.servlet.RequestDispatcher;
|
||||
import javax.servlet.ServletOutputStream;
|
||||
import javax.servlet.ServletRequest;
|
||||
|
@ -31,7 +33,10 @@ import javax.servlet.WriteListener;
|
|||
|
||||
import org.eclipse.jetty.http.HttpContent;
|
||||
import org.eclipse.jetty.http.HttpHeader;
|
||||
import org.eclipse.jetty.util.BlockingCallback;
|
||||
import org.eclipse.jetty.util.BufferUtil;
|
||||
import org.eclipse.jetty.util.Callback;
|
||||
import org.eclipse.jetty.util.IteratingCallback;
|
||||
import org.eclipse.jetty.util.log.Log;
|
||||
import org.eclipse.jetty.util.log.Logger;
|
||||
import org.eclipse.jetty.util.resource.Resource;
|
||||
|
@ -142,7 +147,13 @@ public class HttpOutput extends ServletOutputStream
|
|||
|
||||
public boolean closeIfAllContentWritten() throws IOException
|
||||
{
|
||||
return _channel.getResponse().closeIfAllContentWritten(_written);
|
||||
Response response=_channel.getResponse();
|
||||
if (response.isAllContentWritten(_written))
|
||||
{
|
||||
response.closeOutput();
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -151,53 +162,55 @@ public class HttpOutput extends ServletOutputStream
|
|||
if (isClosed())
|
||||
throw new EOFException("Closed");
|
||||
|
||||
// Do we have an aggregate buffer already ?
|
||||
// Do we have an aggregate buffer ?
|
||||
if (_aggregate == null)
|
||||
{
|
||||
// What size should the aggregate be ?
|
||||
// NO - should we have an aggregate buffer? yes if this write will easily fit in it
|
||||
int size = getBufferSize();
|
||||
|
||||
// If this write would fill more than half the aggregate, just write it directly
|
||||
if (len > size / 2)
|
||||
if (len<=size/2)
|
||||
{
|
||||
_channel.write(ByteBuffer.wrap(b, off, len), false);
|
||||
_aggregate = _channel.getByteBufferPool().acquire(size, false);
|
||||
BufferUtil.append(_aggregate, b, off, len);
|
||||
_written += len;
|
||||
closeIfAllContentWritten();
|
||||
return;
|
||||
}
|
||||
|
||||
// Allocate an aggregate buffer.
|
||||
// Never direct as it is slow to do little writes to a direct buffer.
|
||||
_aggregate = _channel.getByteBufferPool().acquire(size, false);
|
||||
}
|
||||
|
||||
// Do we have space to aggregate ?
|
||||
int space = BufferUtil.space(_aggregate);
|
||||
if (len > space)
|
||||
else
|
||||
{
|
||||
// No space so write the aggregate out if it is not empty
|
||||
if (BufferUtil.hasContent(_aggregate))
|
||||
// YES - fill the aggregate with content from the buffer
|
||||
int filled = BufferUtil.fill(_aggregate, b, off, len);
|
||||
_written += filled;
|
||||
|
||||
// if closed or there is no content left over and we are not full, then we are done
|
||||
if (closeIfAllContentWritten() || filled==len && !BufferUtil.isFull(_aggregate))
|
||||
return;
|
||||
|
||||
off+=filled;
|
||||
len-=filled;
|
||||
}
|
||||
|
||||
// flush the aggregate
|
||||
if (BufferUtil.hasContent(_aggregate))
|
||||
{
|
||||
_channel.write(_aggregate, false);
|
||||
|
||||
// should we fill aggregate again from the buffer?
|
||||
if (len<_aggregate.capacity()/2)
|
||||
{
|
||||
_channel.write(_aggregate, false);
|
||||
space = BufferUtil.space(_aggregate);
|
||||
BufferUtil.append(_aggregate, b, off, len);
|
||||
_written += len;
|
||||
closeIfAllContentWritten();
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// Do we have space to aggregate now ?
|
||||
if (len > space)
|
||||
{
|
||||
// No space so write the content directly
|
||||
_channel.write(ByteBuffer.wrap(b, off, len), false);
|
||||
_written += len;
|
||||
return;
|
||||
}
|
||||
|
||||
// Aggregate the content
|
||||
BufferUtil.append(_aggregate, b, off, len);
|
||||
|
||||
// write any remaining content in the buffer directly
|
||||
_written += len;
|
||||
|
||||
// Check if all written or full
|
||||
if (!closeIfAllContentWritten() && BufferUtil.isFull(_aggregate))
|
||||
_channel.write(_aggregate, false);
|
||||
boolean complete=_channel.getResponse().isAllContentWritten(_written);
|
||||
_channel.write(ByteBuffer.wrap(b, off, len), complete);
|
||||
if (complete)
|
||||
_channel.getResponse().closeOutput();
|
||||
}
|
||||
|
||||
|
||||
|
@ -217,7 +230,11 @@ public class HttpOutput extends ServletOutputStream
|
|||
|
||||
// Check if all written or full
|
||||
if (!closeIfAllContentWritten() && BufferUtil.isFull(_aggregate))
|
||||
_channel.write(_aggregate, false);
|
||||
{
|
||||
BlockingCallback callback = _channel.getWriteBlockingCallback();
|
||||
_channel.write(_aggregate, false, callback);
|
||||
callback.block();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -229,105 +246,172 @@ 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
|
||||
{
|
||||
if (isClosed())
|
||||
throw new IOException("Closed");
|
||||
final BlockingCallback callback =_channel.getWriteBlockingCallback();
|
||||
|
||||
if (content instanceof HttpContent)
|
||||
{
|
||||
HttpContent httpContent = (HttpContent)content;
|
||||
Response response = _channel.getResponse();
|
||||
String contentType = httpContent.getContentType();
|
||||
if (contentType != null && !response.getHttpFields().containsKey(HttpHeader.CONTENT_TYPE.asString()))
|
||||
response.getHttpFields().put(HttpHeader.CONTENT_TYPE, contentType);
|
||||
|
||||
if (httpContent.getContentLength() > 0)
|
||||
response.getHttpFields().putLongField(HttpHeader.CONTENT_LENGTH, httpContent.getContentLength());
|
||||
|
||||
String lm = httpContent.getLastModified();
|
||||
if (lm != null)
|
||||
response.getHttpFields().put(HttpHeader.LAST_MODIFIED, lm);
|
||||
else if (httpContent.getResource() != null)
|
||||
{
|
||||
long lml = httpContent.getResource().lastModified();
|
||||
if (lml != -1)
|
||||
response.getHttpFields().putDateField(HttpHeader.LAST_MODIFIED, lml);
|
||||
}
|
||||
|
||||
String etag=httpContent.getETag();
|
||||
if (etag!=null)
|
||||
response.getHttpFields().put(HttpHeader.ETAG,etag);
|
||||
|
||||
content = _channel.useDirectBuffers()?httpContent.getDirectBuffer():null;
|
||||
if (content == null)
|
||||
content = httpContent.getIndirectBuffer();
|
||||
if (content == null)
|
||||
content = httpContent.getReadableByteChannel();
|
||||
if (content == null)
|
||||
content = httpContent.getInputStream();
|
||||
_channel.getResponse().setHeaders((HttpContent)content);
|
||||
sendContent((HttpContent)content,callback);
|
||||
}
|
||||
else if (content instanceof Resource)
|
||||
{
|
||||
Resource resource = (Resource)content;
|
||||
_channel.getResponse().getHttpFields().putDateField(HttpHeader.LAST_MODIFIED, resource.lastModified());
|
||||
content=resource.getInputStream(); // Closed below
|
||||
|
||||
ReadableByteChannel in=((Resource)content).getReadableByteChannel();
|
||||
if (in!=null)
|
||||
sendContent(in,callback);
|
||||
else
|
||||
sendContent(resource.getInputStream(),callback);
|
||||
}
|
||||
|
||||
// Process content.
|
||||
if (content instanceof ByteBuffer)
|
||||
else if (content instanceof ByteBuffer)
|
||||
{
|
||||
_channel.write((ByteBuffer)content, true);
|
||||
_closed=true;
|
||||
sendContent((ByteBuffer)content,callback);
|
||||
}
|
||||
else if (content instanceof ReadableByteChannel)
|
||||
{
|
||||
ByteBuffer buffer = _channel.getByteBufferPool().acquire(getBufferSize(), _channel.useDirectBuffers());
|
||||
try (ReadableByteChannel channel = (ReadableByteChannel)content;)
|
||||
{
|
||||
while(channel.isOpen())
|
||||
{
|
||||
int pos = BufferUtil.flipToFill(buffer);
|
||||
int len=channel.read(buffer);
|
||||
if (len<0)
|
||||
break;
|
||||
BufferUtil.flipToFlush(buffer,pos);
|
||||
_channel.write(buffer,false);
|
||||
}
|
||||
}
|
||||
finally
|
||||
{
|
||||
close();
|
||||
_channel.getByteBufferPool().release(buffer);
|
||||
}
|
||||
sendContent((ReadableByteChannel)content,callback);
|
||||
}
|
||||
else if (content instanceof InputStream)
|
||||
{
|
||||
// allocate non direct buffer so array may be directly accessed.
|
||||
ByteBuffer buffer = _channel.getByteBufferPool().acquire(getBufferSize(), false);
|
||||
byte[] array = buffer.array();
|
||||
int offset=buffer.arrayOffset();
|
||||
int size=array.length-offset;
|
||||
try (InputStream in = (InputStream)content;)
|
||||
{
|
||||
while(true)
|
||||
{
|
||||
int len=in.read(array,offset,size);
|
||||
if (len<0)
|
||||
break;
|
||||
buffer.position(0);
|
||||
buffer.limit(len);
|
||||
_channel.write(buffer,false);
|
||||
}
|
||||
}
|
||||
finally
|
||||
{
|
||||
close();
|
||||
_channel.getByteBufferPool().release(buffer);
|
||||
}
|
||||
sendContent((InputStream)content,callback);
|
||||
}
|
||||
else
|
||||
throw new IllegalArgumentException("unknown content type "+content.getClass());
|
||||
callback.failed(new IllegalArgumentException("unknown content type "+content.getClass()));
|
||||
|
||||
callback.block();
|
||||
}
|
||||
|
||||
/* ------------------------------------------------------------ */
|
||||
/** Blocking send of content.
|
||||
* @param content The content to send
|
||||
* @throws IOException
|
||||
*/
|
||||
public void sendContent(ByteBuffer content) throws IOException
|
||||
{
|
||||
final BlockingCallback callback =_channel.getWriteBlockingCallback();
|
||||
_channel.write(content,true,callback);
|
||||
callback.block();
|
||||
}
|
||||
|
||||
/* ------------------------------------------------------------ */
|
||||
/** Blocking send of content.
|
||||
* @param content The content to send
|
||||
* @throws IOException
|
||||
*/
|
||||
public void sendContent(InputStream in) throws IOException
|
||||
{
|
||||
final BlockingCallback callback =_channel.getWriteBlockingCallback();
|
||||
new InputStreamWritingCB(in,callback).iterate();
|
||||
callback.block();
|
||||
}
|
||||
|
||||
/* ------------------------------------------------------------ */
|
||||
/** Blocking send of content.
|
||||
* @param content The content to send
|
||||
* @throws IOException
|
||||
*/
|
||||
public void sendContent(ReadableByteChannel in) throws IOException
|
||||
{
|
||||
final BlockingCallback callback =_channel.getWriteBlockingCallback();
|
||||
new ReadableByteChannelWritingCB(in,callback).iterate();
|
||||
callback.block();
|
||||
}
|
||||
|
||||
|
||||
/* ------------------------------------------------------------ */
|
||||
/** Blocking send of content.
|
||||
* @param content The content to send
|
||||
* @throws IOException
|
||||
*/
|
||||
public void sendContent(HttpContent content) throws IOException
|
||||
{
|
||||
final BlockingCallback callback =_channel.getWriteBlockingCallback();
|
||||
sendContent(content,callback);
|
||||
callback.block();
|
||||
}
|
||||
|
||||
|
||||
/* ------------------------------------------------------------ */
|
||||
/** 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())
|
||||
throw new IOException("Closed");
|
||||
if (BufferUtil.hasContent(_aggregate))
|
||||
throw new IOException("written");
|
||||
if (_channel.isCommitted())
|
||||
throw new IOException("committed");
|
||||
|
||||
_closed=true;
|
||||
|
||||
ByteBuffer buffer= _channel.useDirectBuffers()?httpContent.getDirectBuffer():null;
|
||||
if (buffer == null)
|
||||
buffer = httpContent.getIndirectBuffer();
|
||||
|
||||
if (buffer!=null)
|
||||
{
|
||||
sendContent(buffer,callback);
|
||||
return;
|
||||
}
|
||||
|
||||
ReadableByteChannel rbc=httpContent.getReadableByteChannel();
|
||||
if (rbc!=null)
|
||||
{
|
||||
sendContent(rbc,callback);
|
||||
return;
|
||||
}
|
||||
|
||||
InputStream in = httpContent.getInputStream();
|
||||
if ( in!=null )
|
||||
{
|
||||
sendContent(in,callback);
|
||||
return;
|
||||
}
|
||||
|
||||
callback.failed(new IllegalArgumentException("unknown content for "+httpContent));
|
||||
}
|
||||
|
||||
public int getBufferSize()
|
||||
|
@ -374,4 +458,118 @@ public class HttpOutput extends ServletOutputStream
|
|||
// TODO 3.1 Auto-generated method stub
|
||||
return false;
|
||||
}
|
||||
|
||||
/* ------------------------------------------------------------ */
|
||||
/** 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;
|
||||
final ByteBuffer _buffer;
|
||||
|
||||
public InputStreamWritingCB(InputStream in, Callback callback)
|
||||
{
|
||||
super(callback);
|
||||
_in=in;
|
||||
_buffer = _channel.getByteBufferPool().acquire(getBufferSize(), false);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected boolean process() throws Exception
|
||||
{
|
||||
int len=_in.read(_buffer.array(),0,_buffer.capacity());
|
||||
if (len==-1)
|
||||
{
|
||||
_channel.getByteBufferPool().release(_buffer);
|
||||
return true;
|
||||
}
|
||||
boolean eof=false;
|
||||
|
||||
// if we read less than a buffer, are we at EOF?
|
||||
if (len<_buffer.capacity())
|
||||
{
|
||||
int len2=_in.read(_buffer.array(),len,_buffer.capacity()-len);
|
||||
if (len2<0)
|
||||
eof=true;
|
||||
else
|
||||
len+=len2;
|
||||
}
|
||||
|
||||
_buffer.position(0);
|
||||
_buffer.limit(len);
|
||||
_channel.write(_buffer,eof,this);
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void failed(Throwable x)
|
||||
{
|
||||
super.failed(x);
|
||||
_channel.getByteBufferPool().release(_buffer);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/* ------------------------------------------------------------ */
|
||||
/** 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;
|
||||
final ByteBuffer _buffer;
|
||||
|
||||
public ReadableByteChannelWritingCB(ReadableByteChannel in, Callback callback)
|
||||
{
|
||||
super(callback);
|
||||
_in=in;
|
||||
_buffer = _channel.getByteBufferPool().acquire(getBufferSize(), _channel.useDirectBuffers());
|
||||
}
|
||||
|
||||
@Override
|
||||
protected boolean process() throws Exception
|
||||
{
|
||||
_buffer.clear();
|
||||
int len=_in.read(_buffer);
|
||||
if (len==-1)
|
||||
{
|
||||
_channel.getByteBufferPool().release(_buffer);
|
||||
return true;
|
||||
}
|
||||
|
||||
boolean eof=false;
|
||||
|
||||
// if we read less than a buffer, are we at EOF?
|
||||
if (len<_buffer.capacity())
|
||||
{
|
||||
int len2=_in.read(_buffer);
|
||||
if (len2<0)
|
||||
eof=true;
|
||||
else
|
||||
len+=len2;
|
||||
}
|
||||
|
||||
_buffer.flip();
|
||||
_channel.write(_buffer,eof,this);
|
||||
return false;
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void failed(Throwable x)
|
||||
{
|
||||
super.failed(x);
|
||||
_channel.getByteBufferPool().release(_buffer);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -26,9 +26,12 @@ import org.eclipse.jetty.util.Callback;
|
|||
|
||||
public interface HttpTransport
|
||||
{
|
||||
@Deprecated
|
||||
void send(HttpGenerator.ResponseInfo info, ByteBuffer content, boolean lastContent) throws IOException;
|
||||
|
||||
void send(HttpGenerator.ResponseInfo info, ByteBuffer content, boolean lastContent, Callback callback);
|
||||
|
||||
void send(ByteBuffer content, boolean lastContent, Callback callback);
|
||||
|
||||
void completed();
|
||||
}
|
||||
|
|
|
@ -38,6 +38,13 @@ public class Iso88591HttpWriter extends HttpWriter
|
|||
if (length==0)
|
||||
out.closeIfAllContentWritten();
|
||||
|
||||
if (length==1)
|
||||
{
|
||||
int c=s[offset];
|
||||
out.write(c<256?c:'?');
|
||||
return;
|
||||
}
|
||||
|
||||
while (length > 0)
|
||||
{
|
||||
_bytes.reset();
|
||||
|
|
|
@ -1401,7 +1401,7 @@ public class Request implements HttpServletRequest
|
|||
@Override
|
||||
public boolean isAsyncStarted()
|
||||
{
|
||||
return getHttpChannelState().isAsync();
|
||||
return getHttpChannelState().isAsyncStarted();
|
||||
}
|
||||
|
||||
|
||||
|
|
|
@ -34,6 +34,7 @@ import javax.servlet.http.Cookie;
|
|||
import javax.servlet.http.HttpServletResponse;
|
||||
import javax.servlet.http.HttpSession;
|
||||
|
||||
import org.eclipse.jetty.http.HttpContent;
|
||||
import org.eclipse.jetty.http.HttpCookie;
|
||||
import org.eclipse.jetty.http.HttpField;
|
||||
import org.eclipse.jetty.http.HttpFields;
|
||||
|
@ -129,6 +130,31 @@ public class Response implements HttpServletResponse
|
|||
_fields.clear();
|
||||
}
|
||||
|
||||
public void setHeaders(HttpContent httpContent)
|
||||
{
|
||||
Response response = _channel.getResponse();
|
||||
String contentType = httpContent.getContentType();
|
||||
if (contentType != null && !response.getHttpFields().containsKey(HttpHeader.CONTENT_TYPE.asString()))
|
||||
response.getHttpFields().put(HttpHeader.CONTENT_TYPE, contentType);
|
||||
|
||||
if (httpContent.getContentLength() > 0)
|
||||
response.getHttpFields().putLongField(HttpHeader.CONTENT_LENGTH, httpContent.getContentLength());
|
||||
|
||||
String lm = httpContent.getLastModified();
|
||||
if (lm != null)
|
||||
response.getHttpFields().put(HttpHeader.LAST_MODIFIED, lm);
|
||||
else if (httpContent.getResource() != null)
|
||||
{
|
||||
long lml = httpContent.getResource().lastModified();
|
||||
if (lml != -1)
|
||||
response.getHttpFields().putDateField(HttpHeader.LAST_MODIFIED, lml);
|
||||
}
|
||||
|
||||
String etag=httpContent.getETag();
|
||||
if (etag!=null)
|
||||
response.getHttpFields().put(HttpHeader.ETAG,etag);
|
||||
}
|
||||
|
||||
public HttpOutput getHttpOutput()
|
||||
{
|
||||
return _out;
|
||||
|
@ -753,34 +779,37 @@ public class Response implements HttpServletResponse
|
|||
|
||||
if (_contentLength > 0)
|
||||
{
|
||||
try
|
||||
if (isAllContentWritten(written))
|
||||
{
|
||||
closeIfAllContentWritten(written);
|
||||
}
|
||||
catch(IOException e)
|
||||
{
|
||||
throw new RuntimeIOException(e);
|
||||
try
|
||||
{
|
||||
closeOutput();
|
||||
}
|
||||
catch(IOException e)
|
||||
{
|
||||
throw new RuntimeIOException(e);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public boolean closeIfAllContentWritten(long written) throws IOException
|
||||
public boolean isAllContentWritten(long written)
|
||||
{
|
||||
if (_contentLength >= 0 && written >= _contentLength)
|
||||
return (_contentLength >= 0 && written >= _contentLength);
|
||||
}
|
||||
|
||||
public void closeOutput() throws IOException
|
||||
{
|
||||
switch (_outputType)
|
||||
{
|
||||
switch (_outputType)
|
||||
{
|
||||
case WRITER:
|
||||
_writer.close();
|
||||
break;
|
||||
case STREAM:
|
||||
getOutputStream().close();
|
||||
break;
|
||||
default:
|
||||
}
|
||||
return true;
|
||||
case WRITER:
|
||||
_writer.close();
|
||||
break;
|
||||
case STREAM:
|
||||
getOutputStream().close();
|
||||
break;
|
||||
default:
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
public long getLongContentLength()
|
||||
|
|
|
@ -21,7 +21,10 @@ package org.eclipse.jetty.server.handler;
|
|||
import java.io.IOException;
|
||||
import java.io.OutputStream;
|
||||
import java.net.MalformedURLException;
|
||||
import java.nio.ByteBuffer;
|
||||
import java.nio.channels.ReadableByteChannel;
|
||||
|
||||
import javax.servlet.AsyncContext;
|
||||
import javax.servlet.RequestDispatcher;
|
||||
import javax.servlet.ServletException;
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
|
@ -33,12 +36,16 @@ import org.eclipse.jetty.http.HttpMethod;
|
|||
import org.eclipse.jetty.http.HttpStatus;
|
||||
import org.eclipse.jetty.http.MimeTypes;
|
||||
import org.eclipse.jetty.io.WriterOutputStream;
|
||||
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.BufferUtil;
|
||||
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;
|
||||
import org.eclipse.jetty.util.resource.FileResource;
|
||||
import org.eclipse.jetty.util.resource.Resource;
|
||||
|
||||
|
||||
|
@ -65,6 +72,8 @@ public class ResourceHandler extends HandlerWrapper
|
|||
String _cacheControl;
|
||||
boolean _directory;
|
||||
boolean _etags;
|
||||
int _minMemoryMappedContentLength=-1;
|
||||
int _minAsyncContentLength=0;
|
||||
|
||||
/* ------------------------------------------------------------ */
|
||||
public ResourceHandler()
|
||||
|
@ -102,6 +111,50 @@ public class ResourceHandler extends HandlerWrapper
|
|||
_directory = directory;
|
||||
}
|
||||
|
||||
/* ------------------------------------------------------------ */
|
||||
/** Get minimum memory mapped file content length.
|
||||
* @return the minimum size in bytes of a file resource that will
|
||||
* be served using a memory mapped buffer, or -1 (default) for no memory mapped
|
||||
* buffers.
|
||||
*/
|
||||
public int getMinMemoryMappedContentLength()
|
||||
{
|
||||
return _minMemoryMappedContentLength;
|
||||
}
|
||||
|
||||
/* ------------------------------------------------------------ */
|
||||
/** Set minimum memory mapped file content length.
|
||||
* @param minMemoryMappedFileSize the minimum size in bytes of a file resource that will
|
||||
* be served using a memory mapped buffer, or -1 for no memory mapped
|
||||
* buffers.
|
||||
*/
|
||||
public void setMinMemoryMappedContentLength(int minMemoryMappedFileSize)
|
||||
{
|
||||
_minMemoryMappedContentLength = minMemoryMappedFileSize;
|
||||
}
|
||||
|
||||
/* ------------------------------------------------------------ */
|
||||
/** Get the minimum content length for async handling.
|
||||
* @return The minimum size in bytes of the content before asynchronous
|
||||
* handling is used, or -1 for no async handling or 0 (default) for using
|
||||
* {@link HttpServletResponse#getBufferSize()} as the minimum length.
|
||||
*/
|
||||
public int getMinAsyncContentLength()
|
||||
{
|
||||
return _minAsyncContentLength;
|
||||
}
|
||||
|
||||
/* ------------------------------------------------------------ */
|
||||
/** Set the minimum content length for async handling.
|
||||
* @param minAsyncContentLength The minimum size in bytes of the content before asynchronous
|
||||
* handling is used, or -1 for no async handling or 0 for using
|
||||
* {@link HttpServletResponse#getBufferSize()} as the minimum length.
|
||||
*/
|
||||
public void setMinAsyncContentLength(int minAsyncContentLength)
|
||||
{
|
||||
_minAsyncContentLength = minAsyncContentLength;
|
||||
}
|
||||
|
||||
/* ------------------------------------------------------------ */
|
||||
/**
|
||||
* @return True if ETag processing is done
|
||||
|
@ -358,9 +411,10 @@ public class ResourceHandler extends HandlerWrapper
|
|||
}
|
||||
|
||||
Resource resource = getResource(request);
|
||||
|
||||
// If resource is not found
|
||||
if (resource==null || !resource.exists())
|
||||
{
|
||||
// inject the jetty-dir.css file if it matches
|
||||
if (target.endsWith("/jetty-dir.css"))
|
||||
{
|
||||
resource = getStylesheet();
|
||||
|
@ -379,6 +433,7 @@ public class ResourceHandler extends HandlerWrapper
|
|||
// We are going to serve something
|
||||
baseRequest.setHandled(true);
|
||||
|
||||
// handle directories
|
||||
if (resource.isDirectory())
|
||||
{
|
||||
if (!request.getPathInfo().endsWith(URIUtil.SLASH))
|
||||
|
@ -398,7 +453,7 @@ public class ResourceHandler extends HandlerWrapper
|
|||
}
|
||||
}
|
||||
|
||||
// set some headers
|
||||
// Handle ETAGS
|
||||
long last_modified=resource.lastModified();
|
||||
String etag=null;
|
||||
if (_etags)
|
||||
|
@ -414,7 +469,7 @@ public class ResourceHandler extends HandlerWrapper
|
|||
}
|
||||
}
|
||||
|
||||
|
||||
// Handle if modified since
|
||||
if (last_modified>0)
|
||||
{
|
||||
long if_modified=request.getDateHeader(HttpHeader.IF_MODIFIED_SINCE.asString());
|
||||
|
@ -425,34 +480,88 @@ public class ResourceHandler extends HandlerWrapper
|
|||
}
|
||||
}
|
||||
|
||||
// set the headers
|
||||
String mime=_mimeTypes.getMimeByExtension(resource.toString());
|
||||
if (mime==null)
|
||||
mime=_mimeTypes.getMimeByExtension(request.getPathInfo());
|
||||
|
||||
// set the headers
|
||||
doResponseHeaders(response,resource,mime!=null?mime.toString():null);
|
||||
response.setDateHeader(HttpHeader.LAST_MODIFIED.asString(),last_modified);
|
||||
if (_etags)
|
||||
baseRequest.getResponse().getHttpFields().put(HttpHeader.ETAG,etag);
|
||||
|
||||
if(skipContentBody)
|
||||
return;
|
||||
|
||||
|
||||
// Send the content
|
||||
OutputStream out =null;
|
||||
try {out = response.getOutputStream();}
|
||||
catch(IllegalStateException e) {out = new WriterOutputStream(response.getWriter());}
|
||||
|
||||
// See if a short direct method can be used?
|
||||
/* TODO file mapped buffers
|
||||
if (out instanceof HttpOutput)
|
||||
{
|
||||
// TODO file mapped buffers
|
||||
((HttpOutput)out).send(resource.getInputStream());
|
||||
}
|
||||
else*/
|
||||
{
|
||||
// Write content normally
|
||||
// Has the output been wrapped
|
||||
if (!(out instanceof HttpOutput))
|
||||
// Write content via wrapped output
|
||||
resource.writeTo(out,0,resource.length());
|
||||
else
|
||||
{
|
||||
// select async by size
|
||||
int min_async_size=_minAsyncContentLength==0?response.getBufferSize():_minAsyncContentLength;
|
||||
|
||||
if (request.isAsyncSupported() &&
|
||||
min_async_size>0 &&
|
||||
resource.length()>=min_async_size)
|
||||
{
|
||||
final AsyncContext async = request.startAsync();
|
||||
Callback callback = new Callback()
|
||||
{
|
||||
@Override
|
||||
public void succeeded()
|
||||
{
|
||||
async.complete();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void failed(Throwable x)
|
||||
{
|
||||
async.complete();
|
||||
}
|
||||
};
|
||||
|
||||
// Can we use a memory mapped file?
|
||||
if (_minMemoryMappedContentLength>0 &&
|
||||
resource.length()>_minMemoryMappedContentLength &&
|
||||
resource instanceof FileResource)
|
||||
{
|
||||
ByteBuffer buffer = BufferUtil.toBuffer(resource.getFile());
|
||||
((HttpOutput)out).sendContent(buffer,callback);
|
||||
}
|
||||
else // Do a blocking write of a channel (if available) or input stream
|
||||
{
|
||||
ReadableByteChannel channel= resource.getReadableByteChannel();
|
||||
if (channel!=null)
|
||||
((HttpOutput)out).sendContent(channel,callback);
|
||||
else
|
||||
((HttpOutput)out).sendContent(resource.getInputStream(),callback);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
// Can we use a memory mapped file?
|
||||
if (_minMemoryMappedContentLength>0 &&
|
||||
resource.length()>_minMemoryMappedContentLength &&
|
||||
resource instanceof FileResource)
|
||||
{
|
||||
ByteBuffer buffer = BufferUtil.toBuffer(resource.getFile());
|
||||
((HttpOutput)out).sendContent(buffer);
|
||||
}
|
||||
else // Do a blocking write of a channel (if available) or input stream
|
||||
{
|
||||
ReadableByteChannel channel= resource.getReadableByteChannel();
|
||||
if (channel!=null)
|
||||
((HttpOutput)out).sendContent(channel);
|
||||
else
|
||||
((HttpOutput)out).sendContent(resource.getInputStream());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -37,6 +37,7 @@ import javax.servlet.http.HttpServletRequest;
|
|||
|
||||
import org.eclipse.jetty.server.handler.ContextHandler;
|
||||
import org.eclipse.jetty.util.ClassLoadingObjectInputStream;
|
||||
import org.eclipse.jetty.util.IO;
|
||||
import org.eclipse.jetty.util.log.Logger;
|
||||
|
||||
|
||||
|
@ -553,8 +554,7 @@ public class HashSessionManager extends AbstractSessionManager
|
|||
}
|
||||
finally
|
||||
{
|
||||
if (in != null)
|
||||
try {in.close();} catch (Exception x) {__log.ignore(x);}
|
||||
if (in != null) IO.close(in);
|
||||
|
||||
if (error != null)
|
||||
{
|
||||
|
|
|
@ -150,18 +150,14 @@ public class HashedSession extends AbstractSession
|
|||
file.createNewFile();
|
||||
fos = new FileOutputStream(file);
|
||||
save(fos);
|
||||
IO.close(fos);
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
saveFailed();
|
||||
if (fos != null)
|
||||
{
|
||||
// Must not leave the file open if the saving failed
|
||||
IO.close(fos);
|
||||
// No point keeping the file if we didn't save the whole session
|
||||
file.delete();
|
||||
throw e;
|
||||
}
|
||||
saveFailed(); // We won't try again for this session
|
||||
if (fos != null) IO.close(fos);
|
||||
if (file != null) file.delete(); // No point keeping the file if we didn't save the whole session
|
||||
throw e;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -219,17 +215,18 @@ public class HashedSession extends AbstractSession
|
|||
fis = new FileInputStream(file);
|
||||
_idled = false;
|
||||
_hashSessionManager.restoreSession(fis, this);
|
||||
|
||||
IO.close(fis);
|
||||
|
||||
didActivate();
|
||||
|
||||
// If we are doing period saves, then there is no point deleting at this point
|
||||
// If we are doing period saves, then there is no point deleting at this point
|
||||
if (_hashSessionManager._savePeriodMs == 0)
|
||||
file.delete();
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
LOG.warn("Problem de-idling session " + super.getId(), e);
|
||||
IO.close(fis);
|
||||
if (fis != null) IO.close(fis);//Must ensure closed before invalidate
|
||||
invalidate();
|
||||
}
|
||||
}
|
||||
|
|
|
@ -580,11 +580,11 @@ public class JDBCSessionIdManager extends AbstractSessionIdManager
|
|||
@Override
|
||||
public void doStart()
|
||||
throws Exception
|
||||
{
|
||||
{
|
||||
initializeDatabase();
|
||||
prepareTables();
|
||||
prepareTables();
|
||||
super.doStart();
|
||||
if (LOG.isDebugEnabled())
|
||||
if (LOG.isDebugEnabled())
|
||||
LOG.debug("Scavenging interval = "+getScavengeInterval()+" sec");
|
||||
_timer=new Timer("JDBCSessionScavenger", true);
|
||||
setScavengeInterval(getScavengeInterval());
|
||||
|
|
|
@ -305,7 +305,7 @@ public class HttpManyWaysToCommitTest extends AbstractHttpTest
|
|||
}
|
||||
|
||||
@Test
|
||||
public void testHandledBufferOverflow() throws Exception
|
||||
public void testHandledOverflow() throws Exception
|
||||
{
|
||||
server.setHandler(new OverflowHandler(false));
|
||||
server.start();
|
||||
|
@ -317,6 +317,34 @@ public class HttpManyWaysToCommitTest extends AbstractHttpTest
|
|||
if (!"HTTP/1.0".equals(httpVersion))
|
||||
assertHeader(response, "transfer-encoding", "chunked");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testHandledOverflow2() throws Exception
|
||||
{
|
||||
server.setHandler(new Overflow2Handler(false));
|
||||
server.start();
|
||||
|
||||
SimpleHttpResponse response = executeRequest();
|
||||
|
||||
assertThat("response code is 200", response.getCode(), is("200"));
|
||||
assertResponseBody(response, "foobar");
|
||||
if (!"HTTP/1.0".equals(httpVersion))
|
||||
assertHeader(response, "transfer-encoding", "chunked");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testHandledOverflow3() throws Exception
|
||||
{
|
||||
server.setHandler(new Overflow3Handler(false));
|
||||
server.start();
|
||||
|
||||
SimpleHttpResponse response = executeRequest();
|
||||
|
||||
assertThat("response code is 200", response.getCode(), is("200"));
|
||||
assertResponseBody(response, "foobar");
|
||||
if (!"HTTP/1.0".equals(httpVersion))
|
||||
assertHeader(response, "transfer-encoding", "chunked");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testHandledBufferOverflowAndThrow() throws Exception
|
||||
|
@ -350,6 +378,43 @@ public class HttpManyWaysToCommitTest extends AbstractHttpTest
|
|||
}
|
||||
}
|
||||
|
||||
private class Overflow2Handler extends ThrowExceptionOnDemandHandler
|
||||
{
|
||||
private Overflow2Handler(boolean throwException)
|
||||
{
|
||||
super(throwException);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void handle(String target, Request baseRequest, HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException
|
||||
{
|
||||
baseRequest.setHandled(true);
|
||||
response.setBufferSize(3);
|
||||
response.getWriter().write("f");
|
||||
response.getWriter().write("oobar");
|
||||
super.handle(target, baseRequest, request, response);
|
||||
}
|
||||
}
|
||||
|
||||
private class Overflow3Handler extends ThrowExceptionOnDemandHandler
|
||||
{
|
||||
private Overflow3Handler(boolean throwException)
|
||||
{
|
||||
super(throwException);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void handle(String target, Request baseRequest, HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException
|
||||
{
|
||||
baseRequest.setHandled(true);
|
||||
response.setBufferSize(5);
|
||||
response.getWriter().write("fo");
|
||||
response.getWriter().write("ob");
|
||||
response.getWriter().write("ar");
|
||||
super.handle(target, baseRequest, request, response);
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testSetContentLengthAndWriteExactlyThatAmountOfBytes() throws Exception
|
||||
{
|
||||
|
|
|
@ -29,6 +29,8 @@ import java.util.Collections;
|
|||
import java.util.Enumeration;
|
||||
import java.util.Iterator;
|
||||
import java.util.Locale;
|
||||
import java.util.concurrent.TimeoutException;
|
||||
|
||||
import javax.servlet.ServletException;
|
||||
import javax.servlet.ServletOutputStream;
|
||||
import javax.servlet.http.Cookie;
|
||||
|
@ -46,6 +48,7 @@ import org.eclipse.jetty.server.handler.ContextHandler;
|
|||
import org.eclipse.jetty.server.session.HashSessionIdManager;
|
||||
import org.eclipse.jetty.server.session.HashSessionManager;
|
||||
import org.eclipse.jetty.server.session.HashedSession;
|
||||
import org.eclipse.jetty.util.BlockingCallback;
|
||||
import org.eclipse.jetty.util.Callback;
|
||||
import org.eclipse.jetty.util.thread.Scheduler;
|
||||
import org.eclipse.jetty.util.thread.TimerScheduler;
|
||||
|
@ -84,6 +87,9 @@ public class ResponseTest
|
|||
@Override
|
||||
public void send(HttpGenerator.ResponseInfo info, ByteBuffer content, boolean lastContent) throws IOException
|
||||
{
|
||||
BlockingCallback cb = new BlockingCallback();
|
||||
send(info,content,lastContent,cb);
|
||||
cb.block();
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -91,6 +97,12 @@ public class ResponseTest
|
|||
{
|
||||
callback.succeeded();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void send(ByteBuffer responseBodyContent, boolean lastContent, Callback callback)
|
||||
{
|
||||
send(null,responseBodyContent, lastContent, callback);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void completed()
|
||||
|
|
|
@ -18,15 +18,24 @@
|
|||
|
||||
package org.eclipse.jetty.server.handler;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.FileInputStream;
|
||||
import java.io.FileOutputStream;
|
||||
import java.net.Socket;
|
||||
import java.net.URI;
|
||||
|
||||
import org.eclipse.jetty.server.Connector;
|
||||
import org.eclipse.jetty.server.HttpConfiguration;
|
||||
import org.eclipse.jetty.server.HttpConnectionFactory;
|
||||
import org.eclipse.jetty.server.Server;
|
||||
import org.eclipse.jetty.server.ServerConnector;
|
||||
import org.eclipse.jetty.toolchain.test.MavenTestingUtils;
|
||||
import org.eclipse.jetty.toolchain.test.SimpleRequest;
|
||||
import org.eclipse.jetty.util.IO;
|
||||
import org.hamcrest.Matchers;
|
||||
import org.junit.AfterClass;
|
||||
import org.junit.Assert;
|
||||
import org.junit.Before;
|
||||
import org.junit.BeforeClass;
|
||||
import org.junit.Test;
|
||||
|
||||
|
@ -38,6 +47,7 @@ import org.junit.Test;
|
|||
public class ResourceHandlerTest
|
||||
{
|
||||
private static Server _server;
|
||||
private static HttpConfiguration _config;
|
||||
private static ServerConnector _connector;
|
||||
private static ContextHandler _contextHandler;
|
||||
private static ResourceHandler _resourceHandler;
|
||||
|
@ -45,11 +55,29 @@ public class ResourceHandlerTest
|
|||
@BeforeClass
|
||||
public static void setUp() throws Exception
|
||||
{
|
||||
File dir = MavenTestingUtils.getTargetFile("test-classes/simple");
|
||||
File huge = new File(dir,"huge.txt");
|
||||
File big=new File(dir,"big.txt");
|
||||
FileOutputStream out = new FileOutputStream(huge);
|
||||
for (int i=0;i<100;i++)
|
||||
{
|
||||
FileInputStream in=new FileInputStream(big);
|
||||
IO.copy(in,out);
|
||||
}
|
||||
huge.deleteOnExit();
|
||||
|
||||
_server = new Server();
|
||||
_connector = new ServerConnector(_server);
|
||||
_config=new HttpConfiguration();
|
||||
_config.setOutputBufferSize(2048);
|
||||
_connector = new ServerConnector(_server,new HttpConnectionFactory(_config));
|
||||
|
||||
_server.setConnectors(new Connector[] { _connector });
|
||||
|
||||
_resourceHandler = new ResourceHandler();
|
||||
_resourceHandler.setMinAsyncContentLength(4096);
|
||||
_resourceHandler.setMinMemoryMappedContentLength(8192);
|
||||
|
||||
_resourceHandler.setResourceBase(MavenTestingUtils.getTargetFile("test-classes/simple").getAbsolutePath());
|
||||
|
||||
_contextHandler = new ContextHandler("/resource");
|
||||
_contextHandler.setHandler(_resourceHandler);
|
||||
|
@ -62,16 +90,69 @@ public class ResourceHandlerTest
|
|||
{
|
||||
_server.stop();
|
||||
}
|
||||
|
||||
@Before
|
||||
public void before()
|
||||
{
|
||||
_config.setOutputBufferSize(4096);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testSimpleResourceHandler() throws Exception
|
||||
public void testMissing() throws Exception
|
||||
{
|
||||
_resourceHandler.setResourceBase(MavenTestingUtils.getTestResourceDir("simple").getAbsolutePath());
|
||||
|
||||
SimpleRequest sr = new SimpleRequest(new URI("http://localhost:" + _connector.getLocalPort()));
|
||||
|
||||
Assert.assertEquals("simple text", sr.getString("/resource/simple.txt"));
|
||||
|
||||
Assert.assertNotNull("missing jetty.css" , sr.getString("/resource/jetty-dir.css"));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testSimple() throws Exception
|
||||
{
|
||||
SimpleRequest sr = new SimpleRequest(new URI("http://localhost:" + _connector.getLocalPort()));
|
||||
Assert.assertEquals("simple text", sr.getString("/resource/simple.txt"));
|
||||
}
|
||||
|
||||
|
||||
@Test
|
||||
public void testBigFile() throws Exception
|
||||
{
|
||||
_config.setOutputBufferSize(2048);
|
||||
SimpleRequest sr = new SimpleRequest(new URI("http://localhost:" + _connector.getLocalPort()));
|
||||
String response=sr.getString("/resource/big.txt");
|
||||
Assert.assertThat(response,Matchers.startsWith(" 1\tThis is a big file"));
|
||||
Assert.assertThat(response,Matchers.endsWith(" 400\tThis is a big file\n"));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testBigFileBigBuffer() throws Exception
|
||||
{
|
||||
_config.setOutputBufferSize(16*1024);
|
||||
SimpleRequest sr = new SimpleRequest(new URI("http://localhost:" + _connector.getLocalPort()));
|
||||
String response=sr.getString("/resource/big.txt");
|
||||
Assert.assertThat(response,Matchers.startsWith(" 1\tThis is a big file"));
|
||||
Assert.assertThat(response,Matchers.endsWith(" 400\tThis is a big file\n"));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testBigFileLittleBuffer() throws Exception
|
||||
{
|
||||
_config.setOutputBufferSize(8);
|
||||
SimpleRequest sr = new SimpleRequest(new URI("http://localhost:" + _connector.getLocalPort()));
|
||||
String response=sr.getString("/resource/big.txt");
|
||||
Assert.assertThat(response,Matchers.startsWith(" 1\tThis is a big file"));
|
||||
Assert.assertThat(response,Matchers.endsWith(" 400\tThis is a big file\n"));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testHuge() throws Exception
|
||||
{
|
||||
try (Socket socket = new Socket("localhost",_connector.getLocalPort());)
|
||||
{
|
||||
socket.getOutputStream().write("GET /resource/huge.txt HTTP/1.0\n\n".getBytes());
|
||||
Thread.sleep(1000);
|
||||
String response = IO.toString(socket.getInputStream());
|
||||
Assert.assertThat(response,Matchers.startsWith("HTTP/1.1 200 OK"));
|
||||
Assert.assertThat(response,Matchers.containsString(" 400\tThis is a big file\n 1\tThis is a big file"));
|
||||
Assert.assertThat(response,Matchers.endsWith(" 400\tThis is a big file\n"));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,400 @@
|
|||
1 This is a big file
|
||||
2 This is a big file
|
||||
3 This is a big file
|
||||
4 This is a big file
|
||||
5 This is a big file
|
||||
6 This is a big file
|
||||
7 This is a big file
|
||||
8 This is a big file
|
||||
9 This is a big file
|
||||
10 This is a big file
|
||||
11 This is a big file
|
||||
12 This is a big file
|
||||
13 This is a big file
|
||||
14 This is a big file
|
||||
15 This is a big file
|
||||
16 This is a big file
|
||||
17 This is a big file
|
||||
18 This is a big file
|
||||
19 This is a big file
|
||||
20 This is a big file
|
||||
21 This is a big file
|
||||
22 This is a big file
|
||||
23 This is a big file
|
||||
24 This is a big file
|
||||
25 This is a big file
|
||||
26 This is a big file
|
||||
27 This is a big file
|
||||
28 This is a big file
|
||||
29 This is a big file
|
||||
30 This is a big file
|
||||
31 This is a big file
|
||||
32 This is a big file
|
||||
33 This is a big file
|
||||
34 This is a big file
|
||||
35 This is a big file
|
||||
36 This is a big file
|
||||
37 This is a big file
|
||||
38 This is a big file
|
||||
39 This is a big file
|
||||
40 This is a big file
|
||||
41 This is a big file
|
||||
42 This is a big file
|
||||
43 This is a big file
|
||||
44 This is a big file
|
||||
45 This is a big file
|
||||
46 This is a big file
|
||||
47 This is a big file
|
||||
48 This is a big file
|
||||
49 This is a big file
|
||||
50 This is a big file
|
||||
51 This is a big file
|
||||
52 This is a big file
|
||||
53 This is a big file
|
||||
54 This is a big file
|
||||
55 This is a big file
|
||||
56 This is a big file
|
||||
57 This is a big file
|
||||
58 This is a big file
|
||||
59 This is a big file
|
||||
60 This is a big file
|
||||
61 This is a big file
|
||||
62 This is a big file
|
||||
63 This is a big file
|
||||
64 This is a big file
|
||||
65 This is a big file
|
||||
66 This is a big file
|
||||
67 This is a big file
|
||||
68 This is a big file
|
||||
69 This is a big file
|
||||
70 This is a big file
|
||||
71 This is a big file
|
||||
72 This is a big file
|
||||
73 This is a big file
|
||||
74 This is a big file
|
||||
75 This is a big file
|
||||
76 This is a big file
|
||||
77 This is a big file
|
||||
78 This is a big file
|
||||
79 This is a big file
|
||||
80 This is a big file
|
||||
81 This is a big file
|
||||
82 This is a big file
|
||||
83 This is a big file
|
||||
84 This is a big file
|
||||
85 This is a big file
|
||||
86 This is a big file
|
||||
87 This is a big file
|
||||
88 This is a big file
|
||||
89 This is a big file
|
||||
90 This is a big file
|
||||
91 This is a big file
|
||||
92 This is a big file
|
||||
93 This is a big file
|
||||
94 This is a big file
|
||||
95 This is a big file
|
||||
96 This is a big file
|
||||
97 This is a big file
|
||||
98 This is a big file
|
||||
99 This is a big file
|
||||
100 This is a big file
|
||||
101 This is a big file
|
||||
102 This is a big file
|
||||
103 This is a big file
|
||||
104 This is a big file
|
||||
105 This is a big file
|
||||
106 This is a big file
|
||||
107 This is a big file
|
||||
108 This is a big file
|
||||
109 This is a big file
|
||||
110 This is a big file
|
||||
111 This is a big file
|
||||
112 This is a big file
|
||||
113 This is a big file
|
||||
114 This is a big file
|
||||
115 This is a big file
|
||||
116 This is a big file
|
||||
117 This is a big file
|
||||
118 This is a big file
|
||||
119 This is a big file
|
||||
120 This is a big file
|
||||
121 This is a big file
|
||||
122 This is a big file
|
||||
123 This is a big file
|
||||
124 This is a big file
|
||||
125 This is a big file
|
||||
126 This is a big file
|
||||
127 This is a big file
|
||||
128 This is a big file
|
||||
129 This is a big file
|
||||
130 This is a big file
|
||||
131 This is a big file
|
||||
132 This is a big file
|
||||
133 This is a big file
|
||||
134 This is a big file
|
||||
135 This is a big file
|
||||
136 This is a big file
|
||||
137 This is a big file
|
||||
138 This is a big file
|
||||
139 This is a big file
|
||||
140 This is a big file
|
||||
141 This is a big file
|
||||
142 This is a big file
|
||||
143 This is a big file
|
||||
144 This is a big file
|
||||
145 This is a big file
|
||||
146 This is a big file
|
||||
147 This is a big file
|
||||
148 This is a big file
|
||||
149 This is a big file
|
||||
150 This is a big file
|
||||
151 This is a big file
|
||||
152 This is a big file
|
||||
153 This is a big file
|
||||
154 This is a big file
|
||||
155 This is a big file
|
||||
156 This is a big file
|
||||
157 This is a big file
|
||||
158 This is a big file
|
||||
159 This is a big file
|
||||
160 This is a big file
|
||||
161 This is a big file
|
||||
162 This is a big file
|
||||
163 This is a big file
|
||||
164 This is a big file
|
||||
165 This is a big file
|
||||
166 This is a big file
|
||||
167 This is a big file
|
||||
168 This is a big file
|
||||
169 This is a big file
|
||||
170 This is a big file
|
||||
171 This is a big file
|
||||
172 This is a big file
|
||||
173 This is a big file
|
||||
174 This is a big file
|
||||
175 This is a big file
|
||||
176 This is a big file
|
||||
177 This is a big file
|
||||
178 This is a big file
|
||||
179 This is a big file
|
||||
180 This is a big file
|
||||
181 This is a big file
|
||||
182 This is a big file
|
||||
183 This is a big file
|
||||
184 This is a big file
|
||||
185 This is a big file
|
||||
186 This is a big file
|
||||
187 This is a big file
|
||||
188 This is a big file
|
||||
189 This is a big file
|
||||
190 This is a big file
|
||||
191 This is a big file
|
||||
192 This is a big file
|
||||
193 This is a big file
|
||||
194 This is a big file
|
||||
195 This is a big file
|
||||
196 This is a big file
|
||||
197 This is a big file
|
||||
198 This is a big file
|
||||
199 This is a big file
|
||||
200 This is a big file
|
||||
201 This is a big file
|
||||
202 This is a big file
|
||||
203 This is a big file
|
||||
204 This is a big file
|
||||
205 This is a big file
|
||||
206 This is a big file
|
||||
207 This is a big file
|
||||
208 This is a big file
|
||||
209 This is a big file
|
||||
210 This is a big file
|
||||
211 This is a big file
|
||||
212 This is a big file
|
||||
213 This is a big file
|
||||
214 This is a big file
|
||||
215 This is a big file
|
||||
216 This is a big file
|
||||
217 This is a big file
|
||||
218 This is a big file
|
||||
219 This is a big file
|
||||
220 This is a big file
|
||||
221 This is a big file
|
||||
222 This is a big file
|
||||
223 This is a big file
|
||||
224 This is a big file
|
||||
225 This is a big file
|
||||
226 This is a big file
|
||||
227 This is a big file
|
||||
228 This is a big file
|
||||
229 This is a big file
|
||||
230 This is a big file
|
||||
231 This is a big file
|
||||
232 This is a big file
|
||||
233 This is a big file
|
||||
234 This is a big file
|
||||
235 This is a big file
|
||||
236 This is a big file
|
||||
237 This is a big file
|
||||
238 This is a big file
|
||||
239 This is a big file
|
||||
240 This is a big file
|
||||
241 This is a big file
|
||||
242 This is a big file
|
||||
243 This is a big file
|
||||
244 This is a big file
|
||||
245 This is a big file
|
||||
246 This is a big file
|
||||
247 This is a big file
|
||||
248 This is a big file
|
||||
249 This is a big file
|
||||
250 This is a big file
|
||||
251 This is a big file
|
||||
252 This is a big file
|
||||
253 This is a big file
|
||||
254 This is a big file
|
||||
255 This is a big file
|
||||
256 This is a big file
|
||||
257 This is a big file
|
||||
258 This is a big file
|
||||
259 This is a big file
|
||||
260 This is a big file
|
||||
261 This is a big file
|
||||
262 This is a big file
|
||||
263 This is a big file
|
||||
264 This is a big file
|
||||
265 This is a big file
|
||||
266 This is a big file
|
||||
267 This is a big file
|
||||
268 This is a big file
|
||||
269 This is a big file
|
||||
270 This is a big file
|
||||
271 This is a big file
|
||||
272 This is a big file
|
||||
273 This is a big file
|
||||
274 This is a big file
|
||||
275 This is a big file
|
||||
276 This is a big file
|
||||
277 This is a big file
|
||||
278 This is a big file
|
||||
279 This is a big file
|
||||
280 This is a big file
|
||||
281 This is a big file
|
||||
282 This is a big file
|
||||
283 This is a big file
|
||||
284 This is a big file
|
||||
285 This is a big file
|
||||
286 This is a big file
|
||||
287 This is a big file
|
||||
288 This is a big file
|
||||
289 This is a big file
|
||||
290 This is a big file
|
||||
291 This is a big file
|
||||
292 This is a big file
|
||||
293 This is a big file
|
||||
294 This is a big file
|
||||
295 This is a big file
|
||||
296 This is a big file
|
||||
297 This is a big file
|
||||
298 This is a big file
|
||||
299 This is a big file
|
||||
300 This is a big file
|
||||
301 This is a big file
|
||||
302 This is a big file
|
||||
303 This is a big file
|
||||
304 This is a big file
|
||||
305 This is a big file
|
||||
306 This is a big file
|
||||
307 This is a big file
|
||||
308 This is a big file
|
||||
309 This is a big file
|
||||
310 This is a big file
|
||||
311 This is a big file
|
||||
312 This is a big file
|
||||
313 This is a big file
|
||||
314 This is a big file
|
||||
315 This is a big file
|
||||
316 This is a big file
|
||||
317 This is a big file
|
||||
318 This is a big file
|
||||
319 This is a big file
|
||||
320 This is a big file
|
||||
321 This is a big file
|
||||
322 This is a big file
|
||||
323 This is a big file
|
||||
324 This is a big file
|
||||
325 This is a big file
|
||||
326 This is a big file
|
||||
327 This is a big file
|
||||
328 This is a big file
|
||||
329 This is a big file
|
||||
330 This is a big file
|
||||
331 This is a big file
|
||||
332 This is a big file
|
||||
333 This is a big file
|
||||
334 This is a big file
|
||||
335 This is a big file
|
||||
336 This is a big file
|
||||
337 This is a big file
|
||||
338 This is a big file
|
||||
339 This is a big file
|
||||
340 This is a big file
|
||||
341 This is a big file
|
||||
342 This is a big file
|
||||
343 This is a big file
|
||||
344 This is a big file
|
||||
345 This is a big file
|
||||
346 This is a big file
|
||||
347 This is a big file
|
||||
348 This is a big file
|
||||
349 This is a big file
|
||||
350 This is a big file
|
||||
351 This is a big file
|
||||
352 This is a big file
|
||||
353 This is a big file
|
||||
354 This is a big file
|
||||
355 This is a big file
|
||||
356 This is a big file
|
||||
357 This is a big file
|
||||
358 This is a big file
|
||||
359 This is a big file
|
||||
360 This is a big file
|
||||
361 This is a big file
|
||||
362 This is a big file
|
||||
363 This is a big file
|
||||
364 This is a big file
|
||||
365 This is a big file
|
||||
366 This is a big file
|
||||
367 This is a big file
|
||||
368 This is a big file
|
||||
369 This is a big file
|
||||
370 This is a big file
|
||||
371 This is a big file
|
||||
372 This is a big file
|
||||
373 This is a big file
|
||||
374 This is a big file
|
||||
375 This is a big file
|
||||
376 This is a big file
|
||||
377 This is a big file
|
||||
378 This is a big file
|
||||
379 This is a big file
|
||||
380 This is a big file
|
||||
381 This is a big file
|
||||
382 This is a big file
|
||||
383 This is a big file
|
||||
384 This is a big file
|
||||
385 This is a big file
|
||||
386 This is a big file
|
||||
387 This is a big file
|
||||
388 This is a big file
|
||||
389 This is a big file
|
||||
390 This is a big file
|
||||
391 This is a big file
|
||||
392 This is a big file
|
||||
393 This is a big file
|
||||
394 This is a big file
|
||||
395 This is a big file
|
||||
396 This is a big file
|
||||
397 This is a big file
|
||||
398 This is a big file
|
||||
399 This is a big file
|
||||
400 This is a big file
|
|
@ -28,6 +28,7 @@ import java.nio.ByteBuffer;
|
|||
import java.util.Enumeration;
|
||||
import java.util.List;
|
||||
|
||||
import javax.servlet.AsyncContext;
|
||||
import javax.servlet.RequestDispatcher;
|
||||
import javax.servlet.ServletContext;
|
||||
import javax.servlet.ServletException;
|
||||
|
@ -49,6 +50,7 @@ import org.eclipse.jetty.server.ResourceCache;
|
|||
import org.eclipse.jetty.server.Response;
|
||||
import org.eclipse.jetty.server.handler.ContextHandler;
|
||||
import org.eclipse.jetty.util.BufferUtil;
|
||||
import org.eclipse.jetty.util.Callback;
|
||||
import org.eclipse.jetty.util.IO;
|
||||
import org.eclipse.jetty.util.MultiPartOutputStream;
|
||||
import org.eclipse.jetty.util.QuotedStringTokenizer;
|
||||
|
@ -856,33 +858,55 @@ public class DefaultServlet extends HttpServlet implements ResourceFactory
|
|||
{
|
||||
resource.writeTo(out,0,content_length);
|
||||
}
|
||||
// else if we can't do a bypass write because of wrapping
|
||||
else if (content==null || written || !(out instanceof HttpOutput))
|
||||
{
|
||||
// write normally
|
||||
writeHeaders(response,content,written?-1:content_length);
|
||||
ByteBuffer buffer = (content==null)?null:content.getIndirectBuffer();
|
||||
if (buffer!=null)
|
||||
BufferUtil.writeTo(buffer,out);
|
||||
else
|
||||
resource.writeTo(out,0,content_length);
|
||||
}
|
||||
// else do a bypass write
|
||||
else
|
||||
{
|
||||
// See if a direct methods can be used?
|
||||
if (content!=null && !written && out instanceof HttpOutput)
|
||||
// write the headers
|
||||
if (response instanceof Response)
|
||||
{
|
||||
if (response instanceof Response)
|
||||
{
|
||||
writeOptionHeaders(((Response)response).getHttpFields());
|
||||
((HttpOutput)out).sendContent(content);
|
||||
}
|
||||
else
|
||||
{
|
||||
writeHeaders(response,content,content_length);
|
||||
((HttpOutput)out).sendContent(content.getResource());
|
||||
}
|
||||
Response r = (Response)response;
|
||||
writeOptionHeaders(r.getHttpFields());
|
||||
r.setHeaders(content);
|
||||
}
|
||||
else
|
||||
{
|
||||
// Write headers normally
|
||||
writeHeaders(response,content,written?-1:content_length);
|
||||
writeHeaders(response,content,content_length);
|
||||
|
||||
// Write content normally
|
||||
ByteBuffer buffer = (content==null)?null:content.getIndirectBuffer();
|
||||
if (buffer!=null)
|
||||
BufferUtil.writeTo(buffer,out);
|
||||
else
|
||||
resource.writeTo(out,0,content_length);
|
||||
// write the content asynchronously if supported
|
||||
if (request.isAsyncSupported())
|
||||
{
|
||||
final AsyncContext context = request.startAsync();
|
||||
|
||||
((HttpOutput)out).sendContent(content,new Callback()
|
||||
{
|
||||
@Override
|
||||
public void succeeded()
|
||||
{
|
||||
context.complete();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void failed(Throwable x)
|
||||
{
|
||||
LOG.debug(x);
|
||||
context.complete();
|
||||
}
|
||||
});
|
||||
}
|
||||
// otherwise write content blocking
|
||||
else
|
||||
{
|
||||
((HttpOutput)out).sendContent(content);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -104,8 +104,8 @@ public class Holder<T> extends AbstractLifeCycle implements Dumpable
|
|||
{
|
||||
//if no class already loaded and no classname, make servlet permanently unavailable
|
||||
if (_class==null && (_className==null || _className.equals("")))
|
||||
throw new UnavailableException("No class for Servlet or Filter for "+_name, -1);
|
||||
|
||||
throw new UnavailableException("No class for Servlet or Filter for "+_name);
|
||||
|
||||
//try to load class
|
||||
if (_class==null)
|
||||
{
|
||||
|
@ -118,7 +118,7 @@ public class Holder<T> extends AbstractLifeCycle implements Dumpable
|
|||
catch (Exception e)
|
||||
{
|
||||
LOG.warn(e);
|
||||
throw new UnavailableException(e.getMessage(), -1);
|
||||
throw new UnavailableException(e.getMessage());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -102,7 +102,7 @@ public class ServletHandler extends ScopedHandler
|
|||
private int _matchAfterIndex = -1; //index of 1st programmatic FilterMapping with isMatchAfter=true
|
||||
private boolean _filterChainsCached=true;
|
||||
private int _maxFilterChainsCacheSize=512;
|
||||
private boolean _startWithUnavailable=true;
|
||||
private boolean _startWithUnavailable=false;
|
||||
private IdentityService _identityService;
|
||||
|
||||
private ServletHolder[] _servlets=new ServletHolder[0];
|
||||
|
|
|
@ -288,6 +288,8 @@ public class ServletHolder extends Holder<Servlet> implements UserIdentity.Scope
|
|||
_unavailable=0;
|
||||
if (!_enabled)
|
||||
return;
|
||||
|
||||
|
||||
//check servlet has a class (ie is not a preliminary registration). If preliminary, fail startup.
|
||||
try
|
||||
{
|
||||
|
@ -296,9 +298,17 @@ public class ServletHolder extends Holder<Servlet> implements UserIdentity.Scope
|
|||
catch (UnavailableException ue)
|
||||
{
|
||||
makeUnavailable(ue);
|
||||
throw ue;
|
||||
if (_servletHandler.isStartWithUnavailable())
|
||||
{
|
||||
LOG.ignore(ue);
|
||||
return;
|
||||
}
|
||||
else
|
||||
throw ue;
|
||||
}
|
||||
|
||||
|
||||
//servlet is not an instance of javax.servlet.Servlet
|
||||
try
|
||||
{
|
||||
checkServletType();
|
||||
|
@ -306,8 +316,13 @@ public class ServletHolder extends Holder<Servlet> implements UserIdentity.Scope
|
|||
catch (UnavailableException ue)
|
||||
{
|
||||
makeUnavailable(ue);
|
||||
if (!_servletHandler.isStartWithUnavailable())
|
||||
throw ue; //servlet is not an instance of javax.servlet.Servlet
|
||||
if (_servletHandler.isStartWithUnavailable())
|
||||
{
|
||||
LOG.ignore(ue);
|
||||
return;
|
||||
}
|
||||
else
|
||||
throw ue;
|
||||
}
|
||||
|
||||
|
||||
|
|
|
@ -86,6 +86,14 @@ public class HttpTransportOverSPDY implements HttpTransport
|
|||
return requestHeaders;
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public void send(ByteBuffer responseBodyContent, boolean lastContent, Callback callback)
|
||||
{
|
||||
// TODO can this be more efficient?
|
||||
send(null,responseBodyContent, lastContent, callback);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void send(HttpGenerator.ResponseInfo info, ByteBuffer content, boolean lastContent, Callback callback)
|
||||
{
|
||||
|
@ -185,14 +193,14 @@ public class HttpTransportOverSPDY implements HttpTransport
|
|||
}
|
||||
|
||||
@Override
|
||||
public void send(HttpGenerator.ResponseInfo info, ByteBuffer content, boolean lastContent) throws EofException
|
||||
public void send(HttpGenerator.ResponseInfo info, ByteBuffer content, boolean lastContent) throws IOException
|
||||
{
|
||||
send(info, content, lastContent, streamBlocker);
|
||||
try
|
||||
{
|
||||
streamBlocker.block();
|
||||
}
|
||||
catch (InterruptedException | TimeoutException | IOException e)
|
||||
catch (Exception e)
|
||||
{
|
||||
LOG.debug(e);
|
||||
}
|
||||
|
|
|
@ -264,6 +264,8 @@ public class ProxyHTTPSPDYConnection extends HttpConnection implements HttpParse
|
|||
long contentLength = fields.getLongField(HttpHeader.CONTENT_LENGTH.asString());
|
||||
HttpGenerator.ResponseInfo info = new HttpGenerator.ResponseInfo(httpVersion, fields, contentLength, code,
|
||||
reason, false);
|
||||
|
||||
// TODO use the async send
|
||||
send(info, null, replyInfo.isClose());
|
||||
|
||||
if (replyInfo.isClose())
|
||||
|
@ -300,6 +302,7 @@ public class ProxyHTTPSPDYConnection extends HttpConnection implements HttpParse
|
|||
// Data buffer must be copied, as the ByteBuffer is pooled
|
||||
ByteBuffer byteBuffer = dataInfo.asByteBuffer(false);
|
||||
|
||||
// TODO use the async send with callback!
|
||||
send(null, byteBuffer, dataInfo.isClose());
|
||||
|
||||
if (dataInfo.isClose())
|
||||
|
|
|
@ -19,6 +19,7 @@
|
|||
package org.eclipse.jetty.util;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.InterruptedIOException;
|
||||
import java.util.concurrent.CancellationException;
|
||||
import java.util.concurrent.Semaphore;
|
||||
import java.util.concurrent.TimeoutException;
|
||||
|
@ -87,25 +88,25 @@ public class BlockingCallback implements Callback
|
|||
* This is useful for code that wants to repeatable use a FutureCallback to convert
|
||||
* an asynchronous API to a blocking API.
|
||||
* @return
|
||||
* @throws InterruptedException
|
||||
* @throws IOException
|
||||
* @throws TimeoutException
|
||||
*/
|
||||
public void block() throws InterruptedException, IOException, TimeoutException
|
||||
public void block() throws IOException
|
||||
{
|
||||
_semaphone.acquire();
|
||||
try
|
||||
{
|
||||
_semaphone.acquire();
|
||||
if (_cause==COMPLETED)
|
||||
return;
|
||||
if (_cause instanceof IOException)
|
||||
throw (IOException) _cause;
|
||||
if (_cause instanceof CancellationException)
|
||||
throw (CancellationException) _cause;
|
||||
if (_cause instanceof TimeoutException)
|
||||
throw (TimeoutException) _cause;
|
||||
throw new IOException(_cause);
|
||||
}
|
||||
catch (final InterruptedException e)
|
||||
{
|
||||
throw new InterruptedIOException(){{initCause(e);}};
|
||||
}
|
||||
finally
|
||||
{
|
||||
_done.set(false);
|
||||
|
|
|
@ -24,6 +24,7 @@ import java.io.InputStream;
|
|||
import java.io.OutputStream;
|
||||
import java.io.RandomAccessFile;
|
||||
import java.nio.Buffer;
|
||||
import java.nio.BufferOverflowException;
|
||||
import java.nio.ByteBuffer;
|
||||
import java.nio.MappedByteBuffer;
|
||||
import java.nio.channels.FileChannel;
|
||||
|
@ -326,7 +327,7 @@ public class BufferUtil
|
|||
/* ------------------------------------------------------------ */
|
||||
/**
|
||||
*/
|
||||
public static void append(ByteBuffer to, byte[] b,int off,int len)
|
||||
public static void append(ByteBuffer to, byte[] b,int off,int len) throws BufferOverflowException
|
||||
{
|
||||
int pos= flipToFill(to);
|
||||
try
|
||||
|
@ -338,6 +339,25 @@ public class BufferUtil
|
|||
flipToFlush(to,pos);
|
||||
}
|
||||
}
|
||||
|
||||
/* ------------------------------------------------------------ */
|
||||
/** Like append, but does not throw {@link BufferOverflowException}
|
||||
*/
|
||||
public static int fill(ByteBuffer to, byte[] b,int off,int len)
|
||||
{
|
||||
int pos= flipToFill(to);
|
||||
try
|
||||
{
|
||||
int remaining=to.remaining();
|
||||
int take=remaining<len?remaining:len;
|
||||
to.put(b,off,take);
|
||||
return take;
|
||||
}
|
||||
finally
|
||||
{
|
||||
flipToFlush(to,pos);
|
||||
}
|
||||
}
|
||||
|
||||
/* ------------------------------------------------------------ */
|
||||
/**
|
||||
|
@ -731,9 +751,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)
|
||||
|
|
|
@ -0,0 +1,111 @@
|
|||
//
|
||||
// ========================================================================
|
||||
// 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.util;
|
||||
|
||||
import java.util.concurrent.atomic.AtomicBoolean;
|
||||
|
||||
|
||||
/* ------------------------------------------------------------ */
|
||||
/** Iterating Callback.
|
||||
* <p>This specialised callback is used when breaking up an
|
||||
* asynchronous task into smaller asynchronous tasks. A typical pattern
|
||||
* is that a successful callback is used to schedule the next sub task, but
|
||||
* if that task completes quickly and uses the calling thread to callback
|
||||
* the success notification, this can result in a growing stack depth.
|
||||
* </p>
|
||||
* <p>To avoid this issue, this callback uses an Atomicboolean to note
|
||||
* if the success callback has been called during the processing of a
|
||||
* sub task, and if so then the processing iterates rather than recurses.
|
||||
* </p>
|
||||
* <p>This callback is passed to the asynchronous handling of each sub
|
||||
* task and a call the {@link #succeeded()} on this call back represents
|
||||
* completion of the subtask. Only once all the subtasks are completed is
|
||||
* the {@link Callback#succeeded()} method called on the {@link Callback} instance
|
||||
* passed the the {@link #IteratingCallback(Callback)} constructor.</p>
|
||||
*
|
||||
*/
|
||||
public abstract class IteratingCallback implements Callback
|
||||
{
|
||||
final AtomicBoolean _iterating = new AtomicBoolean();
|
||||
final Callback _callback;
|
||||
|
||||
|
||||
public IteratingCallback(Callback callback)
|
||||
{
|
||||
_callback=callback;
|
||||
}
|
||||
|
||||
/* ------------------------------------------------------------ */
|
||||
/**
|
||||
* Process a subtask.
|
||||
* <p>Called by {@link #iterate()} to process a sub task of the overall task
|
||||
* <p>
|
||||
* @return True if the total task is complete. If false is returned
|
||||
* then this Callback must be scheduled to receive either a call to
|
||||
* {@link #succeeded()} or {@link #failed(Throwable)}.
|
||||
* @throws Exception
|
||||
*/
|
||||
abstract protected boolean process() throws Exception;
|
||||
|
||||
/* ------------------------------------------------------------ */
|
||||
/** This method is called initially to start processing and
|
||||
* is then called by subsequent sub task success to continue
|
||||
* processing.
|
||||
*/
|
||||
public void iterate()
|
||||
{
|
||||
try
|
||||
{
|
||||
// Keep iterating as long as succeeded() is called during process()
|
||||
while(_iterating.compareAndSet(false,true))
|
||||
{
|
||||
// process and test if we are complete
|
||||
if (process())
|
||||
{
|
||||
_callback.succeeded();
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
catch(Exception e)
|
||||
{
|
||||
_iterating.set(false);
|
||||
_callback.failed(e);
|
||||
}
|
||||
finally
|
||||
{
|
||||
_iterating.set(false);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public void succeeded()
|
||||
{
|
||||
if (!_iterating.compareAndSet(true,false))
|
||||
iterate();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void failed(Throwable x)
|
||||
{
|
||||
_callback.failed(x);
|
||||
}
|
||||
|
||||
}
|
|
@ -107,6 +107,17 @@ public class ContainerLifeCycle extends AbstractLifeCycle implements Container,
|
|||
{
|
||||
l.start();
|
||||
}
|
||||
|
||||
/**
|
||||
* Stops the given lifecycle.
|
||||
*
|
||||
* @param l
|
||||
* @throws Exception
|
||||
*/
|
||||
protected void stop(LifeCycle l) throws Exception
|
||||
{
|
||||
l.stop();
|
||||
}
|
||||
|
||||
/**
|
||||
* Stops the managed lifecycle beans in the reverse order they were added.
|
||||
|
@ -124,7 +135,7 @@ public class ContainerLifeCycle extends AbstractLifeCycle implements Container,
|
|||
{
|
||||
LifeCycle l = (LifeCycle)b._bean;
|
||||
if (l.isRunning())
|
||||
l.stop();
|
||||
stop(l);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -261,7 +272,7 @@ public class ContainerLifeCycle extends AbstractLifeCycle implements Container,
|
|||
{
|
||||
LifeCycle l = (LifeCycle)o;
|
||||
if (!l.isRunning())
|
||||
l.start();
|
||||
start(l);
|
||||
}
|
||||
break;
|
||||
|
||||
|
@ -276,7 +287,7 @@ public class ContainerLifeCycle extends AbstractLifeCycle implements Container,
|
|||
else
|
||||
{
|
||||
manage(new_bean);
|
||||
l.start();
|
||||
start(l);
|
||||
}
|
||||
}
|
||||
else
|
||||
|
@ -452,6 +463,7 @@ public class ContainerLifeCycle extends AbstractLifeCycle implements Container,
|
|||
{
|
||||
if (_beans.remove(bean))
|
||||
{
|
||||
|
||||
unmanage(bean);
|
||||
|
||||
for (Container.Listener l:_listeners)
|
||||
|
@ -472,7 +484,23 @@ public class ContainerLifeCycle extends AbstractLifeCycle implements Container,
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// stop managed beans
|
||||
if (bean._managed==Managed.MANAGED && bean._bean instanceof LifeCycle)
|
||||
{
|
||||
try
|
||||
{
|
||||
stop((LifeCycle)bean._bean);
|
||||
}
|
||||
catch(RuntimeException | Error e)
|
||||
{
|
||||
throw e;
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
|
|
|
@ -22,6 +22,7 @@ package org.eclipse.jetty.util;
|
|||
import static org.junit.Assert.assertEquals;
|
||||
import static org.junit.Assert.assertTrue;
|
||||
|
||||
import java.nio.BufferOverflowException;
|
||||
import java.nio.ByteBuffer;
|
||||
import java.util.Arrays;
|
||||
|
||||
|
@ -143,6 +144,29 @@ public class BufferUtilTest
|
|||
assertEquals(2,from.remaining());
|
||||
assertEquals("1234567890",BufferUtil.toString(to));
|
||||
}
|
||||
|
||||
|
||||
|
||||
@Test
|
||||
public void testAppend() throws Exception
|
||||
{
|
||||
ByteBuffer to = BufferUtil.allocate(8);
|
||||
ByteBuffer from=BufferUtil.toBuffer("12345");
|
||||
|
||||
BufferUtil.append(to,from.array(),0,3);
|
||||
assertEquals("123",BufferUtil.toString(to));
|
||||
BufferUtil.append(to,from.array(),3,2);
|
||||
assertEquals("12345",BufferUtil.toString(to));
|
||||
|
||||
try
|
||||
{
|
||||
BufferUtil.append(to,from.array(),0,5);
|
||||
Assert.fail();
|
||||
}
|
||||
catch(BufferOverflowException e)
|
||||
{}
|
||||
}
|
||||
|
||||
|
||||
@Test
|
||||
public void testPutDirect() throws Exception
|
||||
|
|
|
@ -492,7 +492,7 @@ public class ResourceTest
|
|||
// This test is intended to run only on Windows platform
|
||||
assumeTrue(OS.IS_WINDOWS);
|
||||
|
||||
String path = __userURL.toURI().getPath().replace('/','\\')+"ResourceTest.java";
|
||||
String path = __userURL.toURI().getPath().replace('/','\\')+"resource.txt";
|
||||
System.err.println(path);
|
||||
|
||||
Resource resource = Resource.newResource(path, false);
|
||||
|
|
|
@ -57,18 +57,7 @@ public class HttpTransportOverMux implements HttpTransport
|
|||
public void send(ResponseInfo info, ByteBuffer responseBodyContent, boolean lastContent) throws IOException
|
||||
{
|
||||
send(info,responseBodyContent,lastContent,streamBlocker);
|
||||
try
|
||||
{
|
||||
streamBlocker.block();
|
||||
}
|
||||
catch (IOException e)
|
||||
{
|
||||
throw e;
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
throw new EofException(e);
|
||||
}
|
||||
streamBlocker.block();
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -87,4 +76,10 @@ public class HttpTransportOverMux implements HttpTransport
|
|||
// prepare the AddChannelResponse
|
||||
// TODO: look at HttpSender in jetty-client for generator loop logic
|
||||
}
|
||||
|
||||
@Override
|
||||
public void send(ByteBuffer responseBodyContent, boolean lastContent, Callback callback)
|
||||
{
|
||||
send(null,responseBodyContent, lastContent, callback);
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue