You are viewing a plain text version of this content. The canonical link for it is here.
Posted to dev@tomcat.apache.org by ma...@apache.org on 2017/05/25 20:05:55 UTC
svn commit: r1796186 - in /tomcat/trunk: java/javax/servlet/http/
java/org/apache/catalina/connector/ java/org/apache/coyote/
java/org/apache/coyote/http11/ java/org/apache/coyote/http11/filters/
java/org/apache/coyote/http2/ test/org/apache/catalina/f...
Author: markt
Date: Thu May 25 20:05:55 2017
New Revision: 1796186
URL: http://svn.apache.org/viewvc?rev=1796186&view=rev
Log:
Servlet 4.0
Implement writing trailer headers where the protocol supports it
Added:
tomcat/trunk/webapps/examples/WEB-INF/classes/trailers/
tomcat/trunk/webapps/examples/WEB-INF/classes/trailers/ResponseTrailers.java (with props)
Modified:
tomcat/trunk/java/javax/servlet/http/HttpServletResponse.java
tomcat/trunk/java/javax/servlet/http/HttpServletResponseWrapper.java
tomcat/trunk/java/org/apache/catalina/connector/Response.java
tomcat/trunk/java/org/apache/catalina/connector/ResponseFacade.java
tomcat/trunk/java/org/apache/coyote/AbstractProcessor.java
tomcat/trunk/java/org/apache/coyote/ActionCode.java
tomcat/trunk/java/org/apache/coyote/LocalStrings.properties
tomcat/trunk/java/org/apache/coyote/Response.java
tomcat/trunk/java/org/apache/coyote/http11/Http11OutputBuffer.java
tomcat/trunk/java/org/apache/coyote/http11/Http11Processor.java
tomcat/trunk/java/org/apache/coyote/http11/filters/ChunkedOutputFilter.java
tomcat/trunk/java/org/apache/coyote/http2/Http2AsyncUpgradeHandler.java
tomcat/trunk/java/org/apache/coyote/http2/Http2UpgradeHandler.java
tomcat/trunk/java/org/apache/coyote/http2/LocalStrings.properties
tomcat/trunk/java/org/apache/coyote/http2/Stream.java
tomcat/trunk/java/org/apache/coyote/http2/StreamProcessor.java
tomcat/trunk/test/org/apache/catalina/filters/TesterHttpServletResponse.java
tomcat/trunk/test/org/apache/coyote/http2/TestStream.java
tomcat/trunk/webapps/docs/changelog.xml
tomcat/trunk/webapps/examples/WEB-INF/web.xml
tomcat/trunk/webapps/examples/servlets/index.html
Modified: tomcat/trunk/java/javax/servlet/http/HttpServletResponse.java
URL: http://svn.apache.org/viewvc/tomcat/trunk/java/javax/servlet/http/HttpServletResponse.java?rev=1796186&r1=1796185&r2=1796186&view=diff
==============================================================================
--- tomcat/trunk/java/javax/servlet/http/HttpServletResponse.java (original)
+++ tomcat/trunk/java/javax/servlet/http/HttpServletResponse.java Thu May 25 20:05:55 2017
@@ -18,6 +18,8 @@ package javax.servlet.http;
import java.io.IOException;
import java.util.Collection;
+import java.util.Map;
+import java.util.function.Supplier;
import javax.servlet.ServletResponse;
@@ -340,6 +342,25 @@ public interface HttpServletResponse ext
*/
public Collection<String> getHeaderNames();
+ /**
+ * Configure the supplier of the trailer headers. The supplier will be
+ * called in the scope of the thread that completes the response.
+ * <b>
+ * Trailers that don't meet the requirements of RFC 7230, section 4.1.2 will
+ * be ignored.
+ *
+ * @param supplier The supplier for the trailer headers
+ *
+ * @throws IllegalStateException if this method is called when the
+ * underlying protocol does not support trailer headers or if using
+ * HTTP/1.1 and the response has already been committed
+ *
+ * @since Servlet 4.0
+ */
+ public default void setTrailerFields(Supplier<Map<String, String>> supplier) {
+ // NO-OP
+ }
+
/*
* Server status codes; see RFC 2068.
*/
Modified: tomcat/trunk/java/javax/servlet/http/HttpServletResponseWrapper.java
URL: http://svn.apache.org/viewvc/tomcat/trunk/java/javax/servlet/http/HttpServletResponseWrapper.java?rev=1796186&r1=1796185&r2=1796186&view=diff
==============================================================================
--- tomcat/trunk/java/javax/servlet/http/HttpServletResponseWrapper.java (original)
+++ tomcat/trunk/java/javax/servlet/http/HttpServletResponseWrapper.java Thu May 25 20:05:55 2017
@@ -18,6 +18,8 @@ package javax.servlet.http;
import java.io.IOException;
import java.util.Collection;
+import java.util.Map;
+import java.util.function.Supplier;
import javax.servlet.ServletResponseWrapper;
@@ -266,4 +268,18 @@ public class HttpServletResponseWrapper
public Collection<String> getHeaderNames() {
return this._getHttpServletResponse().getHeaderNames();
}
+
+ /**
+ * {@inheritDoc}
+ * <p>
+ * The default implementation is to call
+ * {@link HttpServletResponse#setTrailerFields(Supplier)}
+ * on the wrapper {@link HttpServletResponse}.
+ *
+ * @since Servlet 4.0
+ */
+ @Override
+ public void setTrailerFields(Supplier<Map<String, String>> supplier) {
+ this._getHttpServletResponse().setTrailerFields(supplier);
+ }
}
Modified: tomcat/trunk/java/org/apache/catalina/connector/Response.java
URL: http://svn.apache.org/viewvc/tomcat/trunk/java/org/apache/catalina/connector/Response.java?rev=1796186&r1=1796185&r2=1796186&view=diff
==============================================================================
--- tomcat/trunk/java/org/apache/catalina/connector/Response.java (original)
+++ tomcat/trunk/java/org/apache/catalina/connector/Response.java Thu May 25 20:05:55 2017
@@ -33,9 +33,11 @@ import java.util.Collection;
import java.util.Enumeration;
import java.util.List;
import java.util.Locale;
+import java.util.Map;
import java.util.TimeZone;
import java.util.Vector;
import java.util.concurrent.atomic.AtomicInteger;
+import java.util.function.Supplier;
import javax.servlet.ServletOutputStream;
import javax.servlet.ServletResponse;
@@ -1152,6 +1154,12 @@ public class Response implements HttpSer
}
+ @Override
+ public void setTrailerFields(Supplier<Map<String, String>> supplier) {
+ getCoyoteResponse().setTrailerFields(supplier);
+ }
+
+
/**
* Encode the session identifier associated with this response
* into the specified redirect URL, if necessary.
Modified: tomcat/trunk/java/org/apache/catalina/connector/ResponseFacade.java
URL: http://svn.apache.org/viewvc/tomcat/trunk/java/org/apache/catalina/connector/ResponseFacade.java?rev=1796186&r1=1796185&r2=1796186&view=diff
==============================================================================
--- tomcat/trunk/java/org/apache/catalina/connector/ResponseFacade.java (original)
+++ tomcat/trunk/java/org/apache/catalina/connector/ResponseFacade.java Thu May 25 20:05:55 2017
@@ -24,6 +24,8 @@ import java.security.PrivilegedActionExc
import java.security.PrivilegedExceptionAction;
import java.util.Collection;
import java.util.Locale;
+import java.util.Map;
+import java.util.function.Supplier;
import javax.servlet.ServletOutputStream;
import javax.servlet.http.Cookie;
@@ -640,4 +642,10 @@ public class ResponseFacade implements H
public Collection<String> getHeaders(String name) {
return response.getHeaders(name);
}
+
+
+ @Override
+ public void setTrailerFields(Supplier<Map<String, String>> supplier) {
+ response.setTrailerFields(supplier);
+ }
}
Modified: tomcat/trunk/java/org/apache/coyote/AbstractProcessor.java
URL: http://svn.apache.org/viewvc/tomcat/trunk/java/org/apache/coyote/AbstractProcessor.java?rev=1796186&r1=1796185&r2=1796186&view=diff
==============================================================================
--- tomcat/trunk/java/org/apache/coyote/AbstractProcessor.java (original)
+++ tomcat/trunk/java/org/apache/coyote/AbstractProcessor.java Thu May 25 20:05:55 2017
@@ -492,6 +492,11 @@ public abstract class AbstractProcessor
result.set(isTrailerFieldsReady());
break;
}
+ case IS_TRAILER_FIELDS_SUPPORTED: {
+ AtomicBoolean result = (AtomicBoolean) param;
+ result.set(isTrailerFieldsSupported());
+ break;
+ }
}
}
@@ -770,6 +775,18 @@ public abstract class AbstractProcessor
/**
+ * Protocols that support trailer fields should override this method and
+ * return {@code true}.
+ *
+ * @return {@code true} if trailer fields are supported by this processor,
+ * otherwise {@code false}.
+ */
+ protected boolean isTrailerFieldsSupported() {
+ return false;
+ }
+
+
+ /**
* Flush any pending writes. Used during non-blocking writes to flush any
* remaining data from a previous incomplete write.
*
Modified: tomcat/trunk/java/org/apache/coyote/ActionCode.java
URL: http://svn.apache.org/viewvc/tomcat/trunk/java/org/apache/coyote/ActionCode.java?rev=1796186&r1=1796185&r2=1796186&view=diff
==============================================================================
--- tomcat/trunk/java/org/apache/coyote/ActionCode.java (original)
+++ tomcat/trunk/java/org/apache/coyote/ActionCode.java Thu May 25 20:05:55 2017
@@ -252,5 +252,12 @@ public enum ActionCode {
* true if it is known that request trailer fields are not supported so an
* empty collection of trailers can then be read.
*/
- IS_TRAILER_FIELDS_READY
+ IS_TRAILER_FIELDS_READY,
+
+ /**
+ * Are HTTP trailer fields supported for the current response? Note that
+ * once an HTTP/1.1 response has been committed, it will no longer support
+ * trailer fields.
+ */
+ IS_TRAILER_FIELDS_SUPPORTED
}
Modified: tomcat/trunk/java/org/apache/coyote/LocalStrings.properties
URL: http://svn.apache.org/viewvc/tomcat/trunk/java/org/apache/coyote/LocalStrings.properties?rev=1796186&r1=1796185&r2=1796186&view=diff
==============================================================================
--- tomcat/trunk/java/org/apache/coyote/LocalStrings.properties (original)
+++ tomcat/trunk/java/org/apache/coyote/LocalStrings.properties Thu May 25 20:05:55 2017
@@ -49,5 +49,6 @@ request.readListenerSet=The non-blocking
response.encoding.invalid=The encoding [{0}] is not recognised by the JRE
response.notAsync=It is only valid to switch to non-blocking IO within async processing or HTTP upgrade processing
response.notNonBlocking=It is invalid to call isReady() when the response has not been put into non-blocking mode
+response.noTrailers.notSupported=A trailer fields supplier may not be set for this response. Either the underlying protocol does not support trailer fields or the protocol requires that the supplier is set before the response is committed
response.nullWriteListener=The listener passed to setWriteListener() may not be null
response.writeListenerSet=The non-blocking write listener has already been set
Modified: tomcat/trunk/java/org/apache/coyote/Response.java
URL: http://svn.apache.org/viewvc/tomcat/trunk/java/org/apache/coyote/Response.java?rev=1796186&r1=1796185&r2=1796186&view=diff
==============================================================================
--- tomcat/trunk/java/org/apache/coyote/Response.java (original)
+++ tomcat/trunk/java/org/apache/coyote/Response.java Thu May 25 20:05:55 2017
@@ -22,7 +22,9 @@ import java.io.UnsupportedEncodingExcept
import java.nio.ByteBuffer;
import java.nio.charset.Charset;
import java.util.Locale;
+import java.util.Map;
import java.util.concurrent.atomic.AtomicBoolean;
+import java.util.function.Supplier;
import javax.servlet.WriteListener;
@@ -78,6 +80,8 @@ public final class Response {
final MimeHeaders headers = new MimeHeaders();
+ private Supplier<Map<String,String>> trailerFieldsSupplier = null;
+
/**
* Associated output buffer.
*/
@@ -322,6 +326,22 @@ public final class Response {
}
+ public void setTrailerFields(Supplier<Map<String, String>> supplier) {
+ AtomicBoolean trailerFieldsSupported = new AtomicBoolean(false);
+ action(ActionCode.IS_TRAILER_FIELDS_SUPPORTED, trailerFieldsSupported);
+ if (!trailerFieldsSupported.get()) {
+ throw new IllegalStateException(sm.getString("response.noTrailers.notSupported"));
+ }
+
+ this.trailerFieldsSupplier = supplier;
+ }
+
+
+ public Supplier<Map<String, String>> getTrailerFields() {
+ return trailerFieldsSupplier;
+ }
+
+
/**
* Set internal fields for special header names.
* Called from set/addHeader.
@@ -530,6 +550,7 @@ public final class Response {
commitTime = -1;
errorException = null;
headers.clear();
+ trailerFieldsSupplier = null;
// Servlet 3.1 non-blocking write listener
listener = null;
fireListener = false;
Modified: tomcat/trunk/java/org/apache/coyote/http11/Http11OutputBuffer.java
URL: http://svn.apache.org/viewvc/tomcat/trunk/java/org/apache/coyote/http11/Http11OutputBuffer.java?rev=1796186&r1=1796185&r2=1796186&view=diff
==============================================================================
--- tomcat/trunk/java/org/apache/coyote/http11/Http11OutputBuffer.java (original)
+++ tomcat/trunk/java/org/apache/coyote/http11/Http11OutputBuffer.java Thu May 25 20:05:55 2017
@@ -524,6 +524,16 @@ public class Http11OutputBuffer implemen
}
+ boolean isChunking() {
+ for (int i = 0; i < lastActiveFilter; i++) {
+ if (activeFilters[i] == filterLibrary[Constants.CHUNKED_FILTER]) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+
// ------------------------------------------ SocketOutputBuffer Inner Class
/**
Modified: tomcat/trunk/java/org/apache/coyote/http11/Http11Processor.java
URL: http://svn.apache.org/viewvc/tomcat/trunk/java/org/apache/coyote/http11/Http11Processor.java?rev=1796186&r1=1796185&r2=1796186&view=diff
==============================================================================
--- tomcat/trunk/java/org/apache/coyote/http11/Http11Processor.java (original)
+++ tomcat/trunk/java/org/apache/coyote/http11/Http11Processor.java Thu May 25 20:05:55 2017
@@ -900,23 +900,25 @@ public class Http11Processor extends Abs
long contentLength = response.getContentLengthLong();
boolean connectionClosePresent = false;
- if (contentLength != -1) {
+ if (http11 && response.getTrailerFields() != null) {
+ // If trailer fields are set, always use chunking
+ outputBuffer.addActiveFilter(outputFilters[Constants.CHUNKED_FILTER]);
+ contentDelimitation = true;
+ headers.addValue(Constants.TRANSFERENCODING).setString(Constants.CHUNKED);
+ } else if (contentLength != -1) {
headers.setValue("Content-Length").setLong(contentLength);
- outputBuffer.addActiveFilter
- (outputFilters[Constants.IDENTITY_FILTER]);
+ outputBuffer.addActiveFilter(outputFilters[Constants.IDENTITY_FILTER]);
contentDelimitation = true;
} else {
// If the response code supports an entity body and we're on
// HTTP 1.1 then we chunk unless we have a Connection: close header
connectionClosePresent = isConnectionClose(headers);
- if (entityBody && http11 && !connectionClosePresent) {
- outputBuffer.addActiveFilter
- (outputFilters[Constants.CHUNKED_FILTER]);
+ if (http11 && entityBody && !connectionClosePresent) {
+ outputBuffer.addActiveFilter(outputFilters[Constants.CHUNKED_FILTER]);
contentDelimitation = true;
headers.addValue(Constants.TRANSFERENCODING).setString(Constants.CHUNKED);
} else {
- outputBuffer.addActiveFilter
- (outputFilters[Constants.IDENTITY_FILTER]);
+ outputBuffer.addActiveFilter(outputFilters[Constants.IDENTITY_FILTER]);
}
}
@@ -1317,6 +1319,24 @@ public class Http11Processor extends Abs
}
+ @Override
+ protected boolean isTrailerFieldsSupported() {
+ // Request must be HTTP/1.1 to support trailer fields
+ if (!http11) {
+ return false;
+ }
+
+ // If the response is not yet committed, chunked encoding can be used
+ // and the trailer fields sent
+ if (!response.isCommitted()) {
+ return true;
+ }
+
+ // Response has been committed - need to see if chunked is being used
+ return outputBuffer.isChunking();
+ }
+
+
/**
* Trigger sendfile processing if required.
*
Modified: tomcat/trunk/java/org/apache/coyote/http11/filters/ChunkedOutputFilter.java
URL: http://svn.apache.org/viewvc/tomcat/trunk/java/org/apache/coyote/http11/filters/ChunkedOutputFilter.java?rev=1796186&r1=1796185&r2=1796186&view=diff
==============================================================================
--- tomcat/trunk/java/org/apache/coyote/http11/filters/ChunkedOutputFilter.java (original)
+++ tomcat/trunk/java/org/apache/coyote/http11/filters/ChunkedOutputFilter.java Thu May 25 20:05:55 2017
@@ -17,12 +17,20 @@
package org.apache.coyote.http11.filters;
import java.io.IOException;
+import java.io.OutputStreamWriter;
import java.nio.ByteBuffer;
+import java.nio.charset.StandardCharsets;
+import java.util.HashSet;
+import java.util.Locale;
+import java.util.Map;
+import java.util.Set;
+import java.util.function.Supplier;
import org.apache.coyote.OutputBuffer;
import org.apache.coyote.Response;
import org.apache.coyote.http11.OutputFilter;
import org.apache.tomcat.util.buf.HexUtils;
+import org.apache.tomcat.util.http.fileupload.ByteArrayOutputStream;
/**
* Chunked output filter.
@@ -31,9 +39,30 @@ import org.apache.tomcat.util.buf.HexUti
*/
public class ChunkedOutputFilter implements OutputFilter {
+ private static final byte[] LAST_CHUNK_BYTES = {(byte) '0', (byte) '\r', (byte) '\n'};
+ private static final byte[] CRLF_BYTES = {(byte) '\r', (byte) '\n'};
private static final byte[] END_CHUNK_BYTES =
{(byte) '0', (byte) '\r', (byte) '\n', (byte) '\r', (byte) '\n'};
+ private static final Set<String> disallowedTrailerFieldNames = new HashSet<>();
+
+ static {
+ // Always add these in lower case
+ disallowedTrailerFieldNames.add("age");
+ disallowedTrailerFieldNames.add("cache-control");
+ disallowedTrailerFieldNames.add("content-length");
+ disallowedTrailerFieldNames.add("content-encoding");
+ disallowedTrailerFieldNames.add("content-range");
+ disallowedTrailerFieldNames.add("content-type");
+ disallowedTrailerFieldNames.add("date");
+ disallowedTrailerFieldNames.add("expires");
+ disallowedTrailerFieldNames.add("location");
+ disallowedTrailerFieldNames.add("retry-after");
+ disallowedTrailerFieldNames.add("trailer");
+ disallowedTrailerFieldNames.add("transfer-encoding");
+ disallowedTrailerFieldNames.add("vary");
+ disallowedTrailerFieldNames.add("warning");
+ }
/**
* Next buffer in the pipeline.
@@ -47,12 +76,17 @@ public class ChunkedOutputFilter impleme
protected final ByteBuffer chunkHeader = ByteBuffer.allocate(10);
+ protected final ByteBuffer lastChunk = ByteBuffer.wrap(LAST_CHUNK_BYTES);
+ protected final ByteBuffer crlfChunk = ByteBuffer.wrap(CRLF_BYTES);
/**
* End chunk.
*/
protected final ByteBuffer endChunk = ByteBuffer.wrap(END_CHUNK_BYTES);
+ private Response response;
+
+
public ChunkedOutputFilter() {
chunkHeader.put(8, (byte) '\r');
chunkHeader.put(9, (byte) '\n');
@@ -112,7 +146,7 @@ public class ChunkedOutputFilter impleme
*/
@Override
public void setResponse(Response response) {
- // NOOP: No need for parameters from response in this filter
+ this.response = response;
}
@@ -132,9 +166,40 @@ public class ChunkedOutputFilter impleme
@Override
public long end() throws IOException {
- // Write end chunk
- buffer.doWrite(endChunk);
- endChunk.position(0).limit(endChunk.capacity());
+ Supplier<Map<String,String>> trailerFieldsSupplier = response.getTrailerFields();
+ Map<String,String> trailerFields = null;
+
+ if (trailerFieldsSupplier != null) {
+ trailerFields = trailerFieldsSupplier.get();
+ }
+
+ if (trailerFields == null) {
+ // Write end chunk
+ buffer.doWrite(endChunk);
+ endChunk.position(0).limit(endChunk.capacity());
+ } else {
+ buffer.doWrite(lastChunk);
+ lastChunk.position(0).limit(lastChunk.capacity());
+
+ ByteArrayOutputStream baos = new ByteArrayOutputStream(1024);
+ OutputStreamWriter osw = new OutputStreamWriter(baos, StandardCharsets.ISO_8859_1);
+ for (Map.Entry<String,String> trailerField : trailerFields.entrySet()) {
+ // Ignore disallowed headers
+ if (disallowedTrailerFieldNames.contains(
+ trailerField.getKey().toLowerCase(Locale.ENGLISH))) {
+ continue;
+ }
+ osw.write(trailerField.getKey());
+ osw.write(':');
+ osw.write(trailerField.getValue());
+ osw.write("\r\n");
+ }
+ osw.close();
+ buffer.doWrite(ByteBuffer.wrap(baos.toByteArray()));
+
+ buffer.doWrite(crlfChunk);
+ crlfChunk.position(0).limit(crlfChunk.capacity());
+ }
return 0;
}
@@ -145,6 +210,6 @@ public class ChunkedOutputFilter impleme
*/
@Override
public void recycle() {
- // NOOP: Nothing to recycle
+ response = null;
}
}
Modified: tomcat/trunk/java/org/apache/coyote/http2/Http2AsyncUpgradeHandler.java
URL: http://svn.apache.org/viewvc/tomcat/trunk/java/org/apache/coyote/http2/Http2AsyncUpgradeHandler.java?rev=1796186&r1=1796185&r2=1796186&view=diff
==============================================================================
--- tomcat/trunk/java/org/apache/coyote/http2/Http2AsyncUpgradeHandler.java (original)
+++ tomcat/trunk/java/org/apache/coyote/http2/Http2AsyncUpgradeHandler.java Thu May 25 20:05:55 2017
@@ -136,6 +136,9 @@ public class Http2AsyncUpgradeHandler ex
void writeHeaders(Stream stream, int pushedStreamId, MimeHeaders mimeHeaders,
boolean endOfStream, int payloadSize) throws IOException {
doWriteHeaders(stream, pushedStreamId, mimeHeaders, endOfStream, payloadSize);
+ if (endOfStream) {
+ stream.sentEndOfStream();
+ }
}
Modified: tomcat/trunk/java/org/apache/coyote/http2/Http2UpgradeHandler.java
URL: http://svn.apache.org/viewvc/tomcat/trunk/java/org/apache/coyote/http2/Http2UpgradeHandler.java?rev=1796186&r1=1796185&r2=1796186&view=diff
==============================================================================
--- tomcat/trunk/java/org/apache/coyote/http2/Http2UpgradeHandler.java (original)
+++ tomcat/trunk/java/org/apache/coyote/http2/Http2UpgradeHandler.java Thu May 25 20:05:55 2017
@@ -531,6 +531,9 @@ class Http2UpgradeHandler extends Abstra
synchronized (socketWrapper) {
doWriteHeaders(stream, pushedStreamId, mimeHeaders, endOfStream, payloadSize);
}
+ if (endOfStream) {
+ stream.sentEndOfStream();
+ }
}
Modified: tomcat/trunk/java/org/apache/coyote/http2/LocalStrings.properties
URL: http://svn.apache.org/viewvc/tomcat/trunk/java/org/apache/coyote/http2/LocalStrings.properties?rev=1796186&r1=1796185&r2=1796186&view=diff
==============================================================================
--- tomcat/trunk/java/org/apache/coyote/http2/LocalStrings.properties (original)
+++ tomcat/trunk/java/org/apache/coyote/http2/LocalStrings.properties Thu May 25 20:05:55 2017
@@ -143,7 +143,7 @@ upgradeHandler.windowSizeTooBig=Connecti
upgradeHandler.windowSizeReservationInterrupted=Connection [{0}], Stream [{1}], reservation for [{2}] bytes
upgradeHandler.writeBody=Connection [{0}], Stream [{1}], Data length [{2}]
upgradeHandler.writeHeaders=Connection [{0}], Stream [{1}]
-upgradeHandler.writePushHeaders=Connection [{0}], Stream [{1}], Pushed stream [{2}]
+upgradeHandler.writePushHeaders=Connection [{0}], Stream [{1}], Pushed stream [{2}], EndOfStream [{3}]
writeStateMachine.endWrite.ise=It is illegal to specify [{0}] for the new state once a write has completed
writeStateMachine.ise=It is illegal to call [{0}()] in state [{1}]
\ No newline at end of file
Modified: tomcat/trunk/java/org/apache/coyote/http2/Stream.java
URL: http://svn.apache.org/viewvc/tomcat/trunk/java/org/apache/coyote/http2/Stream.java?rev=1796186&r1=1796185&r2=1796186&view=diff
==============================================================================
--- tomcat/trunk/java/org/apache/coyote/http2/Stream.java (original)
+++ tomcat/trunk/java/org/apache/coyote/http2/Stream.java Thu May 25 20:05:55 2017
@@ -22,8 +22,11 @@ import java.nio.charset.StandardCharsets
import java.security.AccessController;
import java.security.PrivilegedActionException;
import java.security.PrivilegedExceptionAction;
+import java.util.Collections;
import java.util.Iterator;
import java.util.Locale;
+import java.util.Map;
+import java.util.function.Supplier;
import org.apache.coyote.ActionCode;
import org.apache.coyote.CloseNowException;
@@ -35,6 +38,7 @@ import org.apache.coyote.http2.HpackDeco
import org.apache.juli.logging.Log;
import org.apache.juli.logging.LogFactory;
import org.apache.tomcat.util.buf.ByteChunk;
+import org.apache.tomcat.util.buf.MessageBytes;
import org.apache.tomcat.util.http.FastHttpDateFormat;
import org.apache.tomcat.util.http.MimeHeaders;
import org.apache.tomcat.util.net.ApplicationBufferHandler;
@@ -392,11 +396,42 @@ class Stream extends AbstractStream impl
final void writeHeaders() throws IOException {
prepareHeaders(coyoteResponse);
- boolean endOfStream = getOutputBuffer().hasNoBody();
+ boolean endOfStream = getOutputBuffer().hasNoBody() &&
+ coyoteResponse.getTrailerFields() == null;
// TODO: Is 1k the optimal value?
handler.writeHeaders(this, 0, coyoteResponse.getMimeHeaders(), endOfStream, 1024);
}
+
+ final void writeTrailers() throws IOException {
+ Supplier<Map<String,String>> supplier = coyoteResponse.getTrailerFields();
+ if (supplier == null) {
+ // No supplier was set, end of stream will already have been sent
+ return;
+ }
+
+ // We can re-use the MimeHeaders from the response since they have
+ // already been processed by the encoder at this point
+ MimeHeaders mimeHeaders = coyoteResponse.getMimeHeaders();
+ mimeHeaders.recycle();
+
+ Map<String,String> headerMap = supplier.get();
+ if (headerMap == null) {
+ headerMap = Collections.emptyMap();
+ }
+
+ // Copy the contents of the Map to the MimeHeaders
+ // TODO: Is there benefit in refactoring this? Is MimeHeaders too
+ // heavyweight? Can we reduce the copy/conversions?
+ for (Map.Entry<String, String> headerEntry : headerMap.entrySet()) {
+ MessageBytes mb = mimeHeaders.addValue(headerEntry.getKey());
+ mb.setString(headerEntry.getValue());
+ }
+
+ handler.writeHeaders(this, 0, mimeHeaders, true, 1024);
+ }
+
+
final void writeAck() throws IOException {
// TODO: Is 64 too big? Just the status header with compression
handler.writeHeaders(this, 0, ACK_HEADERS, false, 64);
@@ -586,12 +621,17 @@ class Stream extends AbstractStream impl
}
- public boolean isTrailerFieldsReady() {
+ boolean isTrailerFieldsReady() {
// Once EndOfStream has been received, canRead will be false
return !state.canRead();
}
+ boolean isTrailerFieldsSupported() {
+ return !getOutputBuffer().endOfStreamSent;
+ }
+
+
private static void push(final Http2UpgradeHandler handler, final Request request,
final Stream stream) throws IOException {
if (org.apache.coyote.Constants.IS_SECURITY_ENABLED) {
@@ -716,7 +756,8 @@ class Stream extends AbstractStream impl
if (closed && !endOfStreamSent) {
// Handling this special case here is simpler than trying
// to modify the following code to handle it.
- handler.writeBody(Stream.this, buffer, 0, true);
+ handler.writeBody(Stream.this, buffer, 0,
+ coyoteResponse.getTrailerFields() == null);
}
// Buffer is empty. Nothing to do.
return false;
@@ -735,7 +776,8 @@ class Stream extends AbstractStream impl
handler.reserveWindowSize(Stream.this, streamReservation);
// Do the write
handler.writeBody(Stream.this, buffer, connectionReservation,
- !writeInProgress && closed && left == connectionReservation);
+ !writeInProgress && closed && left == connectionReservation &&
+ coyoteResponse.getTrailerFields() == null);
streamReservation -= connectionReservation;
left -= connectionReservation;
}
@@ -760,6 +802,7 @@ class Stream extends AbstractStream impl
final void close() throws IOException {
closed = true;
flushData();
+ writeTrailers();
}
/**
Modified: tomcat/trunk/java/org/apache/coyote/http2/StreamProcessor.java
URL: http://svn.apache.org/viewvc/tomcat/trunk/java/org/apache/coyote/http2/StreamProcessor.java?rev=1796186&r1=1796185&r2=1796186&view=diff
==============================================================================
--- tomcat/trunk/java/org/apache/coyote/http2/StreamProcessor.java (original)
+++ tomcat/trunk/java/org/apache/coyote/http2/StreamProcessor.java Thu May 25 20:05:55 2017
@@ -190,7 +190,7 @@ class StreamProcessor extends AbstractPr
synchronized (this) {
/*
* TODO Check if this sync is necessary.
- * Compare with superrclass that uses SocketWrapper
+ * Compare with superclass that uses SocketWrapper
*/
while (dispatches != null && dispatches.hasNext()) {
DispatchType dispatchType = dispatches.next();
@@ -223,6 +223,12 @@ class StreamProcessor extends AbstractPr
}
+ @Override
+ protected boolean isTrailerFieldsSupported() {
+ return stream.isTrailerFieldsSupported();
+ }
+
+
@Override
public final void recycle() {
// StreamProcessor instances are not re-used.
Modified: tomcat/trunk/test/org/apache/catalina/filters/TesterHttpServletResponse.java
URL: http://svn.apache.org/viewvc/tomcat/trunk/test/org/apache/catalina/filters/TesterHttpServletResponse.java?rev=1796186&r1=1796185&r2=1796186&view=diff
==============================================================================
--- tomcat/trunk/test/org/apache/catalina/filters/TesterHttpServletResponse.java (original)
+++ tomcat/trunk/test/org/apache/catalina/filters/TesterHttpServletResponse.java Thu May 25 20:05:55 2017
@@ -24,6 +24,8 @@ import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
import java.util.Locale;
+import java.util.Map;
+import java.util.function.Supplier;
import javax.servlet.ServletOutputStream;
import javax.servlet.ServletResponse;
@@ -247,4 +249,6 @@ public class TesterHttpServletResponse i
public void setStatus(int status, String message) {/* NOOP */}
@Override
public void setContentLengthLong(long length) {/* NOOP */}
+ @Override
+ public void setTrailerFields(Supplier<Map<String, String>> supplier) { /* NOOP */ }
}
Modified: tomcat/trunk/test/org/apache/coyote/http2/TestStream.java
URL: http://svn.apache.org/viewvc/tomcat/trunk/test/org/apache/coyote/http2/TestStream.java?rev=1796186&r1=1796185&r2=1796186&view=diff
==============================================================================
--- tomcat/trunk/test/org/apache/coyote/http2/TestStream.java (original)
+++ tomcat/trunk/test/org/apache/coyote/http2/TestStream.java Thu May 25 20:05:55 2017
@@ -30,6 +30,9 @@ import org.junit.Test;
import org.apache.catalina.Context;
import org.apache.catalina.startup.Tomcat;
+import trailers.ResponseTrailers;
+
+
public class TestStream extends Http2TestBase {
/*
@@ -73,6 +76,53 @@ public class TestStream extends Http2Tes
"3-EndOfStream\n", output.getTrace());
}
+
+ @Test
+ public void testResponseTrailerFields() throws Exception {
+
+ enableHttp2();
+
+ Tomcat tomcat = getTomcatInstance();
+
+ Context ctxt = tomcat.addContext("", null);
+ Tomcat.addServlet(ctxt, "simple", new SimpleServlet());
+ ctxt.addServletMappingDecoded("/simple", "simple");
+ Tomcat.addServlet(ctxt, "trailers", new ResponseTrailers());
+ ctxt.addServletMappingDecoded("/trailers", "trailers");
+
+ tomcat.start();
+
+ openClientConnection();
+ doHttpUpgrade();
+ sendClientPreface();
+ validateHttp2InitialResponse();
+
+ byte[] frameHeader = new byte[9];
+ ByteBuffer headersPayload = ByteBuffer.allocate(128);
+ buildGetRequest(frameHeader, headersPayload, null, 3, "/trailers");
+ writeFrame(frameHeader, headersPayload);
+
+ // Headers
+ parser.readFrame(true);
+ // Body
+ parser.readFrame(true);
+ // Trailers
+ parser.readFrame(true);
+
+ Assert.assertEquals(
+ "3-HeadersStart\n" +
+ "3-Header-[:status]-[200]\n" +
+ "3-Header-[content-type]-[text/plain;charset=UTF-8]\n" +
+ "3-Header-[date]-[Wed, 11 Nov 2015 19:18:42 GMT]\n" +
+ "3-HeadersEnd\n" +
+ "3-Body-43\n" +
+ "3-HeadersStart\n" +
+ "3-Header-[x-trailer-2]-[Trailer value two]\n" +
+ "3-Header-[x-trailer-1]-[Trailer value one]\n" +
+ "3-HeadersEnd\n" +
+ "3-EndOfStream\n", output.getTrace());
+ }
+
private static final class PathParam extends HttpServlet {
Modified: tomcat/trunk/webapps/docs/changelog.xml
URL: http://svn.apache.org/viewvc/tomcat/trunk/webapps/docs/changelog.xml?rev=1796186&r1=1796185&r2=1796186&view=diff
==============================================================================
--- tomcat/trunk/webapps/docs/changelog.xml (original)
+++ tomcat/trunk/webapps/docs/changelog.xml Thu May 25 20:05:55 2017
@@ -61,6 +61,10 @@
<bug>61101</bug>: CORS filter should set Vary header in response.
Submitted by Rick Riemer. (remm)
</fix>
+ <update>
+ Update the Servlet 4.0 implementation to add support for setting
+ trailer fields for HTTP responses. (markt)
+ </update>
</changelog>
</subsection>
<subsection name="Coyote">
Added: tomcat/trunk/webapps/examples/WEB-INF/classes/trailers/ResponseTrailers.java
URL: http://svn.apache.org/viewvc/tomcat/trunk/webapps/examples/WEB-INF/classes/trailers/ResponseTrailers.java?rev=1796186&view=auto
==============================================================================
--- tomcat/trunk/webapps/examples/WEB-INF/classes/trailers/ResponseTrailers.java (added)
+++ tomcat/trunk/webapps/examples/WEB-INF/classes/trailers/ResponseTrailers.java Thu May 25 20:05:55 2017
@@ -0,0 +1,68 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package trailers;
+
+import java.io.IOException;
+import java.io.PrintWriter;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.function.Supplier;
+
+import javax.servlet.ServletException;
+import javax.servlet.http.HttpServlet;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+
+/**
+ * This example writes some trailer fields to the HTTP response.
+ */
+public class ResponseTrailers extends HttpServlet {
+
+ private static final long serialVersionUID = 1L;
+ private static final Supplier<Map<String,String>> TRAILER_FIELD_SUPPLIER =
+ new TrailerFieldSupplier();
+
+
+ @Override
+ protected void doGet(HttpServletRequest req, HttpServletResponse resp)
+ throws ServletException, IOException {
+
+ resp.setTrailerFields(TRAILER_FIELD_SUPPLIER);
+ resp.setContentType("text/plain");
+ resp.setCharacterEncoding("UTF-8");
+
+ PrintWriter pw = resp.getWriter();
+
+ pw.print("This reponse should include trailer fields.");
+ }
+
+
+ private static class TrailerFieldSupplier implements Supplier<Map<String,String>> {
+
+ private static final Map<String,String> trailerFields = new HashMap<>();
+
+ static {
+ trailerFields.put("x-trailer-1", "Trailer value one");
+ trailerFields.put("x-trailer-2", "Trailer value two");
+ }
+
+ @Override
+ public Map<String, String> get() {
+ return trailerFields;
+ }
+ }
+}
Propchange: tomcat/trunk/webapps/examples/WEB-INF/classes/trailers/ResponseTrailers.java
------------------------------------------------------------------------------
svn:eol-style = native
Modified: tomcat/trunk/webapps/examples/WEB-INF/web.xml
URL: http://svn.apache.org/viewvc/tomcat/trunk/webapps/examples/WEB-INF/web.xml?rev=1796186&r1=1796185&r2=1796186&view=diff
==============================================================================
--- tomcat/trunk/webapps/examples/WEB-INF/web.xml (original)
+++ tomcat/trunk/webapps/examples/WEB-INF/web.xml Thu May 25 20:05:55 2017
@@ -389,6 +389,16 @@
<url-pattern>/servlets/serverpush/simpleimage</url-pattern>
</servlet-mapping>
+ <!-- Trailer examples -->
+ <servlet>
+ <servlet-name>responsetrailer</servlet-name>
+ <servlet-class>trailers.ResponseTrailers</servlet-class>
+ </servlet>
+ <servlet-mapping>
+ <servlet-name>responsetrailer</servlet-name>
+ <url-pattern>/servlets/trailers/response</url-pattern>
+ </servlet-mapping>
+
<welcome-file-list>
<welcome-file>index.html</welcome-file>
<welcome-file>index.xhtml</welcome-file>
Modified: tomcat/trunk/webapps/examples/servlets/index.html
URL: http://svn.apache.org/viewvc/tomcat/trunk/webapps/examples/servlets/index.html?rev=1796186&r1=1796185&r2=1796186&view=diff
==============================================================================
--- tomcat/trunk/webapps/examples/servlets/index.html (original)
+++ tomcat/trunk/webapps/examples/servlets/index.html Thu May 25 20:05:55 2017
@@ -176,6 +176,17 @@ for clarity.</p>
<td style="width: 30%;"></td>
</tr>
+<tr>
+ <th colspan="3">Servlet 4.0 Trailer Field examples</th>
+</tr>
+<tr>
+ <td>Response trailer fields</td>
+ <td style="width: 30%;">
+ <a href="trailers/response"><img src="images/execute.gif" alt=""> Execute</a>
+ </td>
+ <td style="width: 30%;"></td>
+</tr>
+
</table>
</body>
---------------------------------------------------------------------
To unsubscribe, e-mail: dev-unsubscribe@tomcat.apache.org
For additional commands, e-mail: dev-help@tomcat.apache.org
Re: svn commit: r1796186 - in /tomcat/trunk: java/javax/servlet/http/
java/org/apache/catalina/connector/ java/org/apache/coyote/
java/org/apache/coyote/http11/ java/org/apache/coyote/http11/filters/
java/org/apache/coyote/http2/ test/org/apache/catalina/f...
Posted by Josh Soref <js...@gmail.com>.
mark wrote:
> - * Compare with superrclass that uses SocketWrapper
> + * Compare with superclass that uses SocketWrapper
Fwiw, I finally posted my spelling scripts at https://github.com/jsoref/spelling
If you wanted to, you could have a script that did:
```sh
mv repo.words repo.old;
f repo > repo.words
diff -U0 repo.old repo.words|perl -ne 'next unless s/^\+([^+])/$1/; print'
```
It doesn't really matter if you run the script after every commit or
between releases. The result will show you only the newly added
"tokens"
For what it's worth, if you ran that script against before/after this
commit, you'd see that `reponse` was added:
+ pw.print("This reponse should include trailer fields.");
---------------------------------------------------------------------
To unsubscribe, e-mail: dev-unsubscribe@tomcat.apache.org
For additional commands, e-mail: dev-help@tomcat.apache.org