diff --git a/examples/embedded/src/main/java/org/eclipse/jetty/embedded/FastFileServer.java b/examples/embedded/src/main/java/org/eclipse/jetty/embedded/FastFileServer.java new file mode 100644 index 00000000000..dd819acdf7b --- /dev/null +++ b/examples/embedded/src/main/java/org/eclipse/jetty/embedded/FastFileServer.java @@ -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. + * + *

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.

+ *

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

+ */ +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() implements HttpParser.RequestHandler, Runnable return _version; } + BlockingCallback getWriteBlockingCallback() + { + return _writeblock; + } + /** * @return the number of requests handled by this connection */ @@ -587,9 +592,7 @@ public class HttpChannel implements HttpParser.RequestHandler, 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 implements HttpParser.RequestHandler, 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 implements HttpParser.RequestHandler, 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 implements HttpParser.RequestHandler, Runnable } /** - *

Requests to write (in a blocking way) the given response content buffer, - * committing the response if needed.

+ *

Blocking write, committing the response if needed.

* * @param content the content buffer to write * @param complete whether the content is complete for the response @@ -704,7 +654,21 @@ public class HttpChannel implements HttpParser.RequestHandler, Runnable */ protected void write(ByteBuffer content, boolean complete) throws IOException { - sendResponse(null, content, complete); + sendResponse(null,content,complete,_writeblock); + _writeblock.block(); + } + + /** + *

Non-Blocking write, committing the response if needed.

+ * + * @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 implements HttpParser.RequestHandler, 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(); + } + + } } diff --git a/jetty-server/src/main/java/org/eclipse/jetty/server/HttpChannelState.java b/jetty-server/src/main/java/org/eclipse/jetty/server/HttpChannelState.java index b61f1cbe489..f2e7d3c3fd3 100644 --- a/jetty-server/src/main/java/org/eclipse/jetty/server/HttpChannelState.java +++ b/jetty-server/src/main/java/org/eclipse/jetty/server/HttpChannelState.java @@ -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) diff --git a/jetty-server/src/main/java/org/eclipse/jetty/server/HttpConnection.java b/jetty-server/src/main/java/org/eclipse/jetty/server/HttpConnection.java index ab5708ddc82..2217f0a2b38 100644 --- a/jetty-server/src/main/java/org/eclipse/jetty/server/HttpConnection.java +++ b/jetty-server/src/main/java/org/eclipse/jetty/server/HttpConnection.java @@ -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_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); + } + } + } + } + } } diff --git a/jetty-server/src/main/java/org/eclipse/jetty/server/HttpOutput.java b/jetty-server/src/main/java/org/eclipse/jetty/server/HttpOutput.java index e55e9054144..05502aad606 100644 --- a/jetty-server/src/main/java/org/eclipse/jetty/server/HttpOutput.java +++ b/jetty-server/src/main/java/org/eclipse/jetty/server/HttpOutput.java @@ -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); + } + } } diff --git a/jetty-server/src/main/java/org/eclipse/jetty/server/HttpTransport.java b/jetty-server/src/main/java/org/eclipse/jetty/server/HttpTransport.java index 0ebcfccd697..ef62bdc86f2 100644 --- a/jetty-server/src/main/java/org/eclipse/jetty/server/HttpTransport.java +++ b/jetty-server/src/main/java/org/eclipse/jetty/server/HttpTransport.java @@ -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(); } diff --git a/jetty-server/src/main/java/org/eclipse/jetty/server/Iso88591HttpWriter.java b/jetty-server/src/main/java/org/eclipse/jetty/server/Iso88591HttpWriter.java index c9c4f9eba4d..27802cfb176 100644 --- a/jetty-server/src/main/java/org/eclipse/jetty/server/Iso88591HttpWriter.java +++ b/jetty-server/src/main/java/org/eclipse/jetty/server/Iso88591HttpWriter.java @@ -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(); diff --git a/jetty-server/src/main/java/org/eclipse/jetty/server/Request.java b/jetty-server/src/main/java/org/eclipse/jetty/server/Request.java index 7cb8466450c..c19a8508cd6 100644 --- a/jetty-server/src/main/java/org/eclipse/jetty/server/Request.java +++ b/jetty-server/src/main/java/org/eclipse/jetty/server/Request.java @@ -1401,7 +1401,7 @@ public class Request implements HttpServletRequest @Override public boolean isAsyncStarted() { - return getHttpChannelState().isAsync(); + return getHttpChannelState().isAsyncStarted(); } diff --git a/jetty-server/src/main/java/org/eclipse/jetty/server/Response.java b/jetty-server/src/main/java/org/eclipse/jetty/server/Response.java index 2971cb0e0d1..8c146320f84 100644 --- a/jetty-server/src/main/java/org/eclipse/jetty/server/Response.java +++ b/jetty-server/src/main/java/org/eclipse/jetty/server/Response.java @@ -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() diff --git a/jetty-server/src/main/java/org/eclipse/jetty/server/handler/ResourceHandler.java b/jetty-server/src/main/java/org/eclipse/jetty/server/handler/ResourceHandler.java index 2c6b3d0b881..21e4bc8371f 100644 --- a/jetty-server/src/main/java/org/eclipse/jetty/server/handler/ResourceHandler.java +++ b/jetty-server/src/main/java/org/eclipse/jetty/server/handler/ResourceHandler.java @@ -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()); + } + } } } diff --git a/jetty-server/src/main/java/org/eclipse/jetty/server/session/HashSessionManager.java b/jetty-server/src/main/java/org/eclipse/jetty/server/session/HashSessionManager.java index 7acf6e98ac7..1c6676ac1a6 100644 --- a/jetty-server/src/main/java/org/eclipse/jetty/server/session/HashSessionManager.java +++ b/jetty-server/src/main/java/org/eclipse/jetty/server/session/HashSessionManager.java @@ -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) { diff --git a/jetty-server/src/main/java/org/eclipse/jetty/server/session/HashedSession.java b/jetty-server/src/main/java/org/eclipse/jetty/server/session/HashedSession.java index 15e6022499c..a1616463759 100644 --- a/jetty-server/src/main/java/org/eclipse/jetty/server/session/HashedSession.java +++ b/jetty-server/src/main/java/org/eclipse/jetty/server/session/HashedSession.java @@ -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(); } } diff --git a/jetty-server/src/main/java/org/eclipse/jetty/server/session/JDBCSessionIdManager.java b/jetty-server/src/main/java/org/eclipse/jetty/server/session/JDBCSessionIdManager.java index d81b4468ff6..15deaefb557 100644 --- a/jetty-server/src/main/java/org/eclipse/jetty/server/session/JDBCSessionIdManager.java +++ b/jetty-server/src/main/java/org/eclipse/jetty/server/session/JDBCSessionIdManager.java @@ -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()); diff --git a/jetty-server/src/test/java/org/eclipse/jetty/server/HttpManyWaysToCommitTest.java b/jetty-server/src/test/java/org/eclipse/jetty/server/HttpManyWaysToCommitTest.java index a6b4e79b785..c567873b91b 100644 --- a/jetty-server/src/test/java/org/eclipse/jetty/server/HttpManyWaysToCommitTest.java +++ b/jetty-server/src/test/java/org/eclipse/jetty/server/HttpManyWaysToCommitTest.java @@ -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 { diff --git a/jetty-server/src/test/java/org/eclipse/jetty/server/ResponseTest.java b/jetty-server/src/test/java/org/eclipse/jetty/server/ResponseTest.java index 21f7427590c..c634153883c 100644 --- a/jetty-server/src/test/java/org/eclipse/jetty/server/ResponseTest.java +++ b/jetty-server/src/test/java/org/eclipse/jetty/server/ResponseTest.java @@ -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() diff --git a/jetty-server/src/test/java/org/eclipse/jetty/server/handler/ResourceHandlerTest.java b/jetty-server/src/test/java/org/eclipse/jetty/server/handler/ResourceHandlerTest.java index 0c971a787d0..1a63f0a6c53 100644 --- a/jetty-server/src/test/java/org/eclipse/jetty/server/handler/ResourceHandlerTest.java +++ b/jetty-server/src/test/java/org/eclipse/jetty/server/handler/ResourceHandlerTest.java @@ -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")); + } + } } diff --git a/jetty-server/src/test/resources/simple/big.txt b/jetty-server/src/test/resources/simple/big.txt new file mode 100644 index 00000000000..a6d57f05f62 --- /dev/null +++ b/jetty-server/src/test/resources/simple/big.txt @@ -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 diff --git a/jetty-servlet/src/main/java/org/eclipse/jetty/servlet/DefaultServlet.java b/jetty-servlet/src/main/java/org/eclipse/jetty/servlet/DefaultServlet.java index ac1dca9ca56..a21c1a6965c 100644 --- a/jetty-servlet/src/main/java/org/eclipse/jetty/servlet/DefaultServlet.java +++ b/jetty-servlet/src/main/java/org/eclipse/jetty/servlet/DefaultServlet.java @@ -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); } } } diff --git a/jetty-servlet/src/main/java/org/eclipse/jetty/servlet/Holder.java b/jetty-servlet/src/main/java/org/eclipse/jetty/servlet/Holder.java index 0b7a1fb8f0e..9c12e3ec947 100644 --- a/jetty-servlet/src/main/java/org/eclipse/jetty/servlet/Holder.java +++ b/jetty-servlet/src/main/java/org/eclipse/jetty/servlet/Holder.java @@ -104,8 +104,8 @@ public class Holder 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 extends AbstractLifeCycle implements Dumpable catch (Exception e) { LOG.warn(e); - throw new UnavailableException(e.getMessage(), -1); + throw new UnavailableException(e.getMessage()); } } } diff --git a/jetty-servlet/src/main/java/org/eclipse/jetty/servlet/ServletHandler.java b/jetty-servlet/src/main/java/org/eclipse/jetty/servlet/ServletHandler.java index 1132ac09cae..392d0a8c449 100644 --- a/jetty-servlet/src/main/java/org/eclipse/jetty/servlet/ServletHandler.java +++ b/jetty-servlet/src/main/java/org/eclipse/jetty/servlet/ServletHandler.java @@ -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]; diff --git a/jetty-servlet/src/main/java/org/eclipse/jetty/servlet/ServletHolder.java b/jetty-servlet/src/main/java/org/eclipse/jetty/servlet/ServletHolder.java index 77b3d4bc2c7..297300199f9 100644 --- a/jetty-servlet/src/main/java/org/eclipse/jetty/servlet/ServletHolder.java +++ b/jetty-servlet/src/main/java/org/eclipse/jetty/servlet/ServletHolder.java @@ -288,6 +288,8 @@ public class ServletHolder extends Holder 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 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 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; } diff --git a/jetty-spdy/spdy-http-server/src/main/java/org/eclipse/jetty/spdy/server/http/HttpTransportOverSPDY.java b/jetty-spdy/spdy-http-server/src/main/java/org/eclipse/jetty/spdy/server/http/HttpTransportOverSPDY.java index 30d443fcf2a..b67bfffaa37 100644 --- a/jetty-spdy/spdy-http-server/src/main/java/org/eclipse/jetty/spdy/server/http/HttpTransportOverSPDY.java +++ b/jetty-spdy/spdy-http-server/src/main/java/org/eclipse/jetty/spdy/server/http/HttpTransportOverSPDY.java @@ -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); } diff --git a/jetty-spdy/spdy-http-server/src/main/java/org/eclipse/jetty/spdy/server/proxy/ProxyHTTPSPDYConnection.java b/jetty-spdy/spdy-http-server/src/main/java/org/eclipse/jetty/spdy/server/proxy/ProxyHTTPSPDYConnection.java index 2c05ec77899..602255fa070 100644 --- a/jetty-spdy/spdy-http-server/src/main/java/org/eclipse/jetty/spdy/server/proxy/ProxyHTTPSPDYConnection.java +++ b/jetty-spdy/spdy-http-server/src/main/java/org/eclipse/jetty/spdy/server/proxy/ProxyHTTPSPDYConnection.java @@ -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()) diff --git a/jetty-util/src/main/java/org/eclipse/jetty/util/BlockingCallback.java b/jetty-util/src/main/java/org/eclipse/jetty/util/BlockingCallback.java index 32e2b1cd89b..c5eb3228bb9 100644 --- a/jetty-util/src/main/java/org/eclipse/jetty/util/BlockingCallback.java +++ b/jetty-util/src/main/java/org/eclipse/jetty/util/BlockingCallback.java @@ -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); diff --git a/jetty-util/src/main/java/org/eclipse/jetty/util/BufferUtil.java b/jetty-util/src/main/java/org/eclipse/jetty/util/BufferUtil.java index 1a7f8a334cb..0dbfff9eb05 100644 --- a/jetty-util/src/main/java/org/eclipse/jetty/util/BufferUtil.java +++ b/jetty-util/src/main/java/org/eclipse/jetty/util/BufferUtil.java @@ -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=remainingThis 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. + *

+ *

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. + *

+ *

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.

+ * + */ +public abstract class IteratingCallback implements Callback +{ + final AtomicBoolean _iterating = new AtomicBoolean(); + final Callback _callback; + + + public IteratingCallback(Callback callback) + { + _callback=callback; + } + + /* ------------------------------------------------------------ */ + /** + * Process a subtask. + *

Called by {@link #iterate()} to process a sub task of the overall task + *

+ * @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); + } + +} diff --git a/jetty-util/src/main/java/org/eclipse/jetty/util/component/ContainerLifeCycle.java b/jetty-util/src/main/java/org/eclipse/jetty/util/component/ContainerLifeCycle.java index cda43cb6c9a..02f57e5f322 100644 --- a/jetty-util/src/main/java/org/eclipse/jetty/util/component/ContainerLifeCycle.java +++ b/jetty-util/src/main/java/org/eclipse/jetty/util/component/ContainerLifeCycle.java @@ -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; diff --git a/jetty-util/src/test/java/org/eclipse/jetty/util/BufferUtilTest.java b/jetty-util/src/test/java/org/eclipse/jetty/util/BufferUtilTest.java index f48355bebd6..47fb5515dac 100644 --- a/jetty-util/src/test/java/org/eclipse/jetty/util/BufferUtilTest.java +++ b/jetty-util/src/test/java/org/eclipse/jetty/util/BufferUtilTest.java @@ -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 diff --git a/jetty-util/src/test/java/org/eclipse/jetty/util/resource/ResourceTest.java b/jetty-util/src/test/java/org/eclipse/jetty/util/resource/ResourceTest.java index c77d735d748..1c5889fd15b 100644 --- a/jetty-util/src/test/java/org/eclipse/jetty/util/resource/ResourceTest.java +++ b/jetty-util/src/test/java/org/eclipse/jetty/util/resource/ResourceTest.java @@ -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); diff --git a/jetty-websocket/websocket-server/src/main/java/org/eclipse/jetty/websocket/server/mux/HttpTransportOverMux.java b/jetty-websocket/websocket-server/src/main/java/org/eclipse/jetty/websocket/server/mux/HttpTransportOverMux.java index 0d2fef5d0b4..cc442b1c600 100644 --- a/jetty-websocket/websocket-server/src/main/java/org/eclipse/jetty/websocket/server/mux/HttpTransportOverMux.java +++ b/jetty-websocket/websocket-server/src/main/java/org/eclipse/jetty/websocket/server/mux/HttpTransportOverMux.java @@ -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); + } }