hc-commits mailing list archives

Site index · List index
Message view « Date » · « Thread »
Top « Date » · « Thread »
From ol...@apache.org
Subject svn commit: r1713993 - in /httpcomponents/httpcore/trunk: httpcore-ab/src/main/java/org/apache/http/benchmark/ httpcore-nio/src/main/java/org/apache/http/impl/nio/ httpcore-nio/src/main/java/org/apache/http/impl/nio/codecs/ httpcore-nio/src/test/java/o...
Date Thu, 12 Nov 2015 09:37:44 GMT
Author: olegk
Date: Thu Nov 12 09:37:44 2015
New Revision: 1713993

URL: http://svn.apache.org/viewvc?rev=1713993&view=rev
Log:
HTTPCORE-412: Support for trailing headers in outgoing HTTP messages
Based on contribution by Daneel Yaitskov <rtfm.rtfm.rtfm at gmail.com>

Added:
    httpcomponents/httpcore/trunk/httpcore/src/examples/org/apache/http/examples/ElementalHttpPostTrailers.java   (with props)
    httpcomponents/httpcore/trunk/httpcore/src/main/java/org/apache/http/TrailerSupplier.java
      - copied, changed from r1710453, httpcomponents/httpcore/trunk/httpcore-ab/src/main/java/org/apache/http/benchmark/BenchmarkConnection.java
    httpcomponents/httpcore/trunk/httpcore/src/main/java/org/apache/http/entity/HttpEntityWithTrailers.java
      - copied, changed from r1710453, httpcomponents/httpcore/trunk/httpcore/src/main/java/org/apache/http/entity/HttpEntityWrapper.java
    httpcomponents/httpcore/trunk/httpcore/src/main/java/org/apache/http/protocol/TrailerNameFormatter.java   (with props)
    httpcomponents/httpcore/trunk/httpcore/src/test/java/org/apache/http/protocol/TestTrailerNameFormatter.java   (with props)
Modified:
    httpcomponents/httpcore/trunk/httpcore-ab/src/main/java/org/apache/http/benchmark/BenchmarkConnection.java
    httpcomponents/httpcore/trunk/httpcore-nio/src/main/java/org/apache/http/impl/nio/DefaultNHttpClientConnection.java
    httpcomponents/httpcore/trunk/httpcore-nio/src/main/java/org/apache/http/impl/nio/DefaultNHttpServerConnection.java
    httpcomponents/httpcore/trunk/httpcore-nio/src/main/java/org/apache/http/impl/nio/NHttpConnectionBase.java
    httpcomponents/httpcore/trunk/httpcore-nio/src/main/java/org/apache/http/impl/nio/codecs/ChunkEncoder.java
    httpcomponents/httpcore/trunk/httpcore-nio/src/test/java/org/apache/http/impl/nio/codecs/TestChunkEncoder.java
    httpcomponents/httpcore/trunk/httpcore-nio/src/test/java/org/apache/http/nio/integration/TestTruncatedChunks.java
    httpcomponents/httpcore/trunk/httpcore/src/main/java/org/apache/http/HttpEntity.java
    httpcomponents/httpcore/trunk/httpcore/src/main/java/org/apache/http/entity/AbstractHttpEntity.java
    httpcomponents/httpcore/trunk/httpcore/src/main/java/org/apache/http/entity/HttpEntityWrapper.java
    httpcomponents/httpcore/trunk/httpcore/src/main/java/org/apache/http/impl/BHttpConnectionBase.java
    httpcomponents/httpcore/trunk/httpcore/src/main/java/org/apache/http/impl/DefaultBHttpClientConnection.java
    httpcomponents/httpcore/trunk/httpcore/src/main/java/org/apache/http/impl/DefaultBHttpServerConnection.java
    httpcomponents/httpcore/trunk/httpcore/src/main/java/org/apache/http/impl/IncomingHttpEntity.java
    httpcomponents/httpcore/trunk/httpcore/src/main/java/org/apache/http/impl/io/ChunkedInputStream.java
    httpcomponents/httpcore/trunk/httpcore/src/main/java/org/apache/http/impl/io/ChunkedOutputStream.java
    httpcomponents/httpcore/trunk/httpcore/src/main/java/org/apache/http/message/BufferedHeader.java
    httpcomponents/httpcore/trunk/httpcore/src/main/java/org/apache/http/protocol/RequestContent.java
    httpcomponents/httpcore/trunk/httpcore/src/main/java/org/apache/http/protocol/ResponseContent.java
    httpcomponents/httpcore/trunk/httpcore/src/test/java/org/apache/http/impl/io/TestChunkCoding.java
    httpcomponents/httpcore/trunk/httpcore/src/test/java/org/apache/http/protocol/TestStandardInterceptors.java

Modified: httpcomponents/httpcore/trunk/httpcore-ab/src/main/java/org/apache/http/benchmark/BenchmarkConnection.java
URL: http://svn.apache.org/viewvc/httpcomponents/httpcore/trunk/httpcore-ab/src/main/java/org/apache/http/benchmark/BenchmarkConnection.java?rev=1713993&r1=1713992&r2=1713993&view=diff
==============================================================================
--- httpcomponents/httpcore/trunk/httpcore-ab/src/main/java/org/apache/http/benchmark/BenchmarkConnection.java (original)
+++ httpcomponents/httpcore/trunk/httpcore-ab/src/main/java/org/apache/http/benchmark/BenchmarkConnection.java Thu Nov 12 09:37:44 2015
@@ -29,6 +29,7 @@ package org.apache.http.benchmark;
 import java.io.InputStream;
 import java.io.OutputStream;
 
+import org.apache.http.TrailerSupplier;
 import org.apache.http.impl.DefaultBHttpClientConnection;
 import org.apache.http.io.SessionInputBuffer;
 import org.apache.http.io.SessionOutputBuffer;
@@ -43,8 +44,12 @@ class BenchmarkConnection extends Defaul
     }
 
     @Override
-    protected OutputStream createContentOutputStream(final long len, final SessionOutputBuffer outbuffer) {
-        return new CountingOutputStream(super.createContentOutputStream(len, outbuffer), this.stats);
+    protected OutputStream createContentOutputStream(final long len,
+                                                     final SessionOutputBuffer outbuffer,
+                                                     final TrailerSupplier trailers) {
+        return new CountingOutputStream(
+                super.createContentOutputStream(len, outbuffer, trailers),
+                this.stats);
     }
 
     @Override

Modified: httpcomponents/httpcore/trunk/httpcore-nio/src/main/java/org/apache/http/impl/nio/DefaultNHttpClientConnection.java
URL: http://svn.apache.org/viewvc/httpcomponents/httpcore/trunk/httpcore-nio/src/main/java/org/apache/http/impl/nio/DefaultNHttpClientConnection.java?rev=1713993&r1=1713992&r2=1713993&view=diff
==============================================================================
--- httpcomponents/httpcore/trunk/httpcore-nio/src/main/java/org/apache/http/impl/nio/DefaultNHttpClientConnection.java (original)
+++ httpcomponents/httpcore/trunk/httpcore-nio/src/main/java/org/apache/http/impl/nio/DefaultNHttpClientConnection.java Thu Nov 12 09:37:44 2015
@@ -265,8 +265,8 @@ public class DefaultNHttpClientConnectio
         onRequestSubmitted(request);
         this.requestWriter.write(request, this.outbuf);
         this.hasBufferedOutput = this.outbuf.hasData();
-
-        if (request.getEntity() != null) {
+        final HttpEntity entity = request.getEntity();
+        if (entity != null) {
             this.request = request;
             final long len = this.outgoingContentStrategy.determineLength(request);
             if (len == ContentLengthStrategy.UNDEFINED) {
@@ -276,7 +276,8 @@ public class DefaultNHttpClientConnectio
                     len,
                     this.session.channel(),
                     this.outbuf,
-                    this.outTransportMetrics);
+                    this.outTransportMetrics,
+                    entity.getTrailers());
         }
         this.connMetrics.incrementRequestCount();
         this.session.setEvent(EventMask.WRITE);

Modified: httpcomponents/httpcore/trunk/httpcore-nio/src/main/java/org/apache/http/impl/nio/DefaultNHttpServerConnection.java
URL: http://svn.apache.org/viewvc/httpcomponents/httpcore/trunk/httpcore-nio/src/main/java/org/apache/http/impl/nio/DefaultNHttpServerConnection.java?rev=1713993&r1=1713992&r2=1713993&view=diff
==============================================================================
--- httpcomponents/httpcore/trunk/httpcore-nio/src/main/java/org/apache/http/impl/nio/DefaultNHttpServerConnection.java (original)
+++ httpcomponents/httpcore/trunk/httpcore-nio/src/main/java/org/apache/http/impl/nio/DefaultNHttpServerConnection.java Thu Nov 12 09:37:44 2015
@@ -269,14 +269,16 @@ public class DefaultNHttpServerConnectio
 
         if (response.getStatusLine().getStatusCode() >= 200) {
             this.connMetrics.incrementResponseCount();
-            if (response.getEntity() != null) {
+            final HttpEntity entity = response.getEntity();
+            if (entity != null) {
                 this.response = response;
                 final long len = this.outgoingContentStrategy.determineLength(response);
                 this.contentEncoder = createContentEncoder(
                         len,
                         this.session.channel(),
                         this.outbuf,
-                        this.outTransportMetrics);
+                        this.outTransportMetrics,
+                        entity.getTrailers());
             }
         }
 

Modified: httpcomponents/httpcore/trunk/httpcore-nio/src/main/java/org/apache/http/impl/nio/NHttpConnectionBase.java
URL: http://svn.apache.org/viewvc/httpcomponents/httpcore/trunk/httpcore-nio/src/main/java/org/apache/http/impl/nio/NHttpConnectionBase.java?rev=1713993&r1=1713992&r2=1713993&view=diff
==============================================================================
--- httpcomponents/httpcore/trunk/httpcore-nio/src/main/java/org/apache/http/impl/nio/NHttpConnectionBase.java (original)
+++ httpcomponents/httpcore/trunk/httpcore-nio/src/main/java/org/apache/http/impl/nio/NHttpConnectionBase.java Thu Nov 12 09:37:44 2015
@@ -43,6 +43,7 @@ import org.apache.http.HttpHeaders;
 import org.apache.http.HttpMessage;
 import org.apache.http.HttpRequest;
 import org.apache.http.HttpResponse;
+import org.apache.http.TrailerSupplier;
 import org.apache.http.config.MessageConstraints;
 import org.apache.http.entity.ContentLengthStrategy;
 import org.apache.http.impl.HttpConnectionMetricsImpl;
@@ -225,12 +226,13 @@ class NHttpConnectionBase implements NHt
     /**
      * Factory method for {@link ContentEncoder} instances.
      *
-     * @param len content length, if known, {@link ContentLengthStrategy#CHUNKED} or
-     *   {@link ContentLengthStrategy#UNDEFINED}, if unknown.
+     * @param len content length, if known, {@link org.apache.http.entity.ContentLengthStrategy#CHUNKED} or
+     *   {@link org.apache.http.entity.ContentLengthStrategy#UNDEFINED}, if unknown.
      * @param channel the session channel.
      * @param buffer the session buffer.
      * @param metrics transport metrics.
      *
+     * @param trailers
      * @return content encoder.
      *
      * @since 4.1
@@ -239,11 +241,12 @@ class NHttpConnectionBase implements NHt
             final long len,
             final WritableByteChannel channel,
             final SessionOutputBuffer buffer,
-            final HttpTransportMetricsImpl metrics) {
+            final HttpTransportMetricsImpl metrics,
+            final TrailerSupplier trailers) {
         if (len >= 0) {
             return new LengthDelimitedEncoder(channel, buffer, metrics, len, this.fragmentSizeHint);
         } else if (len == ContentLengthStrategy.CHUNKED) {
-            return new ChunkEncoder(channel, buffer, metrics, this.fragmentSizeHint);
+            return new ChunkEncoder(channel, buffer, metrics, this.fragmentSizeHint, trailers);
         } else {
             return new IdentityEncoder(channel, buffer, metrics, this.fragmentSizeHint);
         }

Modified: httpcomponents/httpcore/trunk/httpcore-nio/src/main/java/org/apache/http/impl/nio/codecs/ChunkEncoder.java
URL: http://svn.apache.org/viewvc/httpcomponents/httpcore/trunk/httpcore-nio/src/main/java/org/apache/http/impl/nio/codecs/ChunkEncoder.java?rev=1713993&r1=1713992&r2=1713993&view=diff
==============================================================================
--- httpcomponents/httpcore/trunk/httpcore-nio/src/main/java/org/apache/http/impl/nio/codecs/ChunkEncoder.java (original)
+++ httpcomponents/httpcore/trunk/httpcore-nio/src/main/java/org/apache/http/impl/nio/codecs/ChunkEncoder.java Thu Nov 12 09:37:44 2015
@@ -31,8 +31,12 @@ import java.io.IOException;
 import java.nio.ByteBuffer;
 import java.nio.channels.WritableByteChannel;
 
+import org.apache.http.FormattedHeader;
+import org.apache.http.Header;
+import org.apache.http.TrailerSupplier;
 import org.apache.http.annotation.NotThreadSafe;
 import org.apache.http.impl.io.HttpTransportMetricsImpl;
+import org.apache.http.message.BasicLineFormatter;
 import org.apache.http.nio.reactor.SessionOutputBuffer;
 import org.apache.http.util.CharArrayBuffer;
 
@@ -44,35 +48,37 @@ import org.apache.http.util.CharArrayBuf
  */
 @NotThreadSafe
 public class ChunkEncoder extends AbstractContentEncoder {
-
     private final int fragHint;
     private final CharArrayBuffer lineBuffer;
+    private final TrailerSupplier trailers;
 
     /**
-     * @since 4.3
-     *
      * @param channel underlying channel.
      * @param buffer  session buffer.
      * @param metrics transport metrics.
      * @param fragementSizeHint fragment size hint defining an minimal size of a fragment
      *   that should be written out directly to the channel bypassing the session buffer.
      *   Value {@code 0} disables fragment buffering.
+     *
+     * @since 5.0
      */
     public ChunkEncoder(
             final WritableByteChannel channel,
             final SessionOutputBuffer buffer,
             final HttpTransportMetricsImpl metrics,
-            final int fragementSizeHint) {
+            final int fragementSizeHint,
+            final TrailerSupplier trailers) {
         super(channel, buffer, metrics);
         this.fragHint = fragementSizeHint > 0 ? fragementSizeHint : 0;
         this.lineBuffer = new CharArrayBuffer(16);
+        this.trailers = trailers;
     }
 
     public ChunkEncoder(
             final WritableByteChannel channel,
             final SessionOutputBuffer buffer,
             final HttpTransportMetricsImpl metrics) {
-        this(channel, buffer, metrics, 0);
+        this(channel, buffer, metrics, 0, null);
     }
 
     @Override
@@ -130,11 +136,28 @@ public class ChunkEncoder extends Abstra
         this.lineBuffer.clear();
         this.lineBuffer.append("0");
         this.buffer.writeLine(this.lineBuffer);
+        writeTrailers();
         this.lineBuffer.clear();
         this.buffer.writeLine(this.lineBuffer);
         super.complete();
     }
 
+    private void writeTrailers() throws IOException {
+        final Header[] headers = this.trailers != null ? this.trailers.get() : null;
+        if (headers != null) {
+            for (Header header: headers) {
+                if (header instanceof FormattedHeader) {
+                    final CharArrayBuffer chbuffer = ((FormattedHeader) header).getBuffer();
+                    buffer.writeLine(chbuffer);
+                } else {
+                    this.lineBuffer.clear();
+                    BasicLineFormatter.INSTANCE.formatHeader(this.lineBuffer, header);
+                    buffer.writeLine(this.lineBuffer);
+                }
+            }
+        }
+    }
+
     @Override
     public String toString() {
         final StringBuilder sb = new StringBuilder();

Modified: httpcomponents/httpcore/trunk/httpcore-nio/src/test/java/org/apache/http/impl/nio/codecs/TestChunkEncoder.java
URL: http://svn.apache.org/viewvc/httpcomponents/httpcore/trunk/httpcore-nio/src/test/java/org/apache/http/impl/nio/codecs/TestChunkEncoder.java?rev=1713993&r1=1713992&r2=1713993&view=diff
==============================================================================
--- httpcomponents/httpcore/trunk/httpcore-nio/src/test/java/org/apache/http/impl/nio/codecs/TestChunkEncoder.java (original)
+++ httpcomponents/httpcore/trunk/httpcore-nio/src/test/java/org/apache/http/impl/nio/codecs/TestChunkEncoder.java Thu Nov 12 09:37:44 2015
@@ -27,12 +27,16 @@
 
 package org.apache.http.impl.nio.codecs;
 
+import java.io.IOException;
 import java.nio.ByteBuffer;
 
 import org.apache.http.Consts;
+import org.apache.http.Header;
+import org.apache.http.TrailerSupplier;
 import org.apache.http.WritableByteChannelMock;
 import org.apache.http.impl.io.HttpTransportMetricsImpl;
 import org.apache.http.impl.nio.reactor.SessionOutputBufferImpl;
+import org.apache.http.message.BasicHeader;
 import org.apache.http.nio.reactor.SessionOutputBuffer;
 import org.junit.Assert;
 import org.junit.Test;
@@ -120,7 +124,7 @@ public class TestChunkEncoder {
         final WritableByteChannelMock channel = Mockito.spy(new WritableByteChannelMock(1024));
         final SessionOutputBuffer outbuf = new SessionOutputBufferImpl(1024, 1024);
         final HttpTransportMetricsImpl metrics = new HttpTransportMetricsImpl();
-        final ChunkEncoder encoder = new ChunkEncoder(channel, outbuf, metrics, 1024);
+        final ChunkEncoder encoder = new ChunkEncoder(channel, outbuf, metrics, 1024, null);
 
         Assert.assertEquals(16, encoder.write(CodecTestUtils.wrap("0123456789ABCDEF")));
         Assert.assertEquals(16, encoder.write(CodecTestUtils.wrap("0123456789ABCDEF")));
@@ -229,4 +233,30 @@ public class TestChunkEncoder {
         }
     }
 
+    @Test
+    public void testTrailers() throws IOException {
+        final WritableByteChannelMock channel = new WritableByteChannelMock(64);
+        final SessionOutputBuffer outbuf = new SessionOutputBufferImpl(1024, 128);
+        final HttpTransportMetricsImpl metrics = new HttpTransportMetricsImpl();
+        final ChunkEncoder encoder = new ChunkEncoder(channel, outbuf, metrics, 0,
+                new TrailerSupplier() {
+                    @Override
+                    public Header[] get() {
+                        return new Header[] {
+                                new BasicHeader("E", ""),
+                                new BasicHeader("Y", "Z")};
+                    }
+                });
+        encoder.write(CodecTestUtils.wrap("1"));
+        encoder.write(CodecTestUtils.wrap("23"));
+        encoder.complete();
+
+        outbuf.flush(channel);
+
+        final String s = channel.dump(Consts.ASCII);
+
+        Assert.assertTrue(encoder.isCompleted());
+        Assert.assertEquals("1\r\n1\r\n2\r\n23\r\n0\r\nE: \r\nY: Z\r\n\r\n", s);
+        Assert.assertEquals("[chunk-coded; completed: true]", encoder.toString());
+    }
 }

Modified: httpcomponents/httpcore/trunk/httpcore-nio/src/test/java/org/apache/http/nio/integration/TestTruncatedChunks.java
URL: http://svn.apache.org/viewvc/httpcomponents/httpcore/trunk/httpcore-nio/src/test/java/org/apache/http/nio/integration/TestTruncatedChunks.java?rev=1713993&r1=1713992&r2=1713993&view=diff
==============================================================================
--- httpcomponents/httpcore/trunk/httpcore-nio/src/test/java/org/apache/http/nio/integration/TestTruncatedChunks.java (original)
+++ httpcomponents/httpcore/trunk/httpcore-nio/src/test/java/org/apache/http/nio/integration/TestTruncatedChunks.java Thu Nov 12 09:37:44 2015
@@ -40,6 +40,7 @@ import org.apache.http.HttpHost;
 import org.apache.http.HttpResponse;
 import org.apache.http.HttpStatus;
 import org.apache.http.MalformedChunkCodingException;
+import org.apache.http.TrailerSupplier;
 import org.apache.http.TruncatedChunkException;
 import org.apache.http.entity.ContentLengthStrategy;
 import org.apache.http.entity.ContentType;
@@ -153,11 +154,12 @@ public class TestTruncatedChunks extends
                         final long len,
                         final WritableByteChannel channel,
                         final SessionOutputBuffer buffer,
-                        final HttpTransportMetricsImpl metrics) {
+                        final HttpTransportMetricsImpl metrics,
+                        final TrailerSupplier trailers) {
                     if (len == ContentLengthStrategy.CHUNKED) {
                         return new BrokenChunkEncoder(channel, buffer, metrics);
                     } else {
-                        return super.createContentEncoder(len, channel, buffer, metrics);
+                        return super.createContentEncoder(len, channel, buffer, metrics, trailers);
                     }
                 }
 

Added: httpcomponents/httpcore/trunk/httpcore/src/examples/org/apache/http/examples/ElementalHttpPostTrailers.java
URL: http://svn.apache.org/viewvc/httpcomponents/httpcore/trunk/httpcore/src/examples/org/apache/http/examples/ElementalHttpPostTrailers.java?rev=1713993&view=auto
==============================================================================
--- httpcomponents/httpcore/trunk/httpcore/src/examples/org/apache/http/examples/ElementalHttpPostTrailers.java (added)
+++ httpcomponents/httpcore/trunk/httpcore/src/examples/org/apache/http/examples/ElementalHttpPostTrailers.java Thu Nov 12 09:37:44 2015
@@ -0,0 +1,84 @@
+/*
+ * ====================================================================
+ * 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.
+ * ====================================================================
+ *
+ * This software consists of voluntary contributions made by many
+ * individuals on behalf of the Apache Software Foundation.  For more
+ * information on the Apache Software Foundation, please see
+ * <http://www.apache.org/>.
+ *
+ */
+
+package org.apache.http.examples;
+
+import java.net.Socket;
+
+import org.apache.http.HttpEntity;
+import org.apache.http.HttpHost;
+import org.apache.http.HttpResponse;
+import org.apache.http.entity.ContentType;
+import org.apache.http.entity.HttpEntityWithTrailers;
+import org.apache.http.entity.StringEntity;
+import org.apache.http.impl.DefaultBHttpClientConnection;
+import org.apache.http.message.BasicHeader;
+import org.apache.http.message.BasicHttpRequest;
+import org.apache.http.protocol.HttpCoreContext;
+import org.apache.http.protocol.HttpProcessor;
+import org.apache.http.protocol.HttpProcessorBuilder;
+import org.apache.http.protocol.HttpRequestExecutor;
+import org.apache.http.protocol.RequestConnControl;
+import org.apache.http.protocol.RequestContent;
+import org.apache.http.protocol.RequestTargetHost;
+import org.apache.http.protocol.RequestUserAgent;
+import org.apache.http.util.EntityUtils;
+
+/**
+ * Elemental example for executing POST request with trailing headers
+ */
+public class ElementalHttpPostTrailers {
+    public static void main(String[] args) throws Exception {
+        HttpProcessor httpproc = HttpProcessorBuilder.create()
+                .add(new RequestContent())
+                .add(new RequestTargetHost())
+                .add(new RequestConnControl())
+                .add(new RequestUserAgent("Test/1.1"))
+                .build();
+        HttpRequestExecutor httpexecutor = new HttpRequestExecutor();
+        HttpCoreContext coreContext = HttpCoreContext.create();
+        HttpHost host = new HttpHost("localhost", 8080);
+        coreContext.setTargetHost(host);
+        DefaultBHttpClientConnection conn = new DefaultBHttpClientConnection(8 * 1024);
+        HttpEntity requestBody = new HttpEntityWithTrailers(
+                new StringEntity("Chunked message with trailers", ContentType.TEXT_PLAIN),
+                new BasicHeader("t1","Hello world"));
+        Socket socket = new Socket(host.getHostName(), host.getPort());
+        conn.bind(socket);
+        BasicHttpRequest request = new BasicHttpRequest("POST", "/");
+        request.setEntity(requestBody);
+        httpexecutor.preProcess(request, httpproc, coreContext);
+        HttpResponse response = httpexecutor.execute(request, conn, coreContext);
+        httpexecutor.postProcess(response, httpproc, coreContext);
+
+        System.out.println("<< Response: " + response.getStatusLine());
+        System.out.println(EntityUtils.toString(response.getEntity()));
+        System.out.println("==============");
+        conn.close();
+    }
+
+}

Propchange: httpcomponents/httpcore/trunk/httpcore/src/examples/org/apache/http/examples/ElementalHttpPostTrailers.java
------------------------------------------------------------------------------
    svn:eol-style = native

Propchange: httpcomponents/httpcore/trunk/httpcore/src/examples/org/apache/http/examples/ElementalHttpPostTrailers.java
------------------------------------------------------------------------------
    svn:keywords = Date Revision

Propchange: httpcomponents/httpcore/trunk/httpcore/src/examples/org/apache/http/examples/ElementalHttpPostTrailers.java
------------------------------------------------------------------------------
    svn:mime-type = text/plain

Modified: httpcomponents/httpcore/trunk/httpcore/src/main/java/org/apache/http/HttpEntity.java
URL: http://svn.apache.org/viewvc/httpcomponents/httpcore/trunk/httpcore/src/main/java/org/apache/http/HttpEntity.java?rev=1713993&r1=1713992&r2=1713993&view=diff
==============================================================================
--- httpcomponents/httpcore/trunk/httpcore/src/main/java/org/apache/http/HttpEntity.java (original)
+++ httpcomponents/httpcore/trunk/httpcore/src/main/java/org/apache/http/HttpEntity.java Thu Nov 12 09:37:44 2015
@@ -30,6 +30,7 @@ package org.apache.http;
 import java.io.IOException;
 import java.io.InputStream;
 import java.io.OutputStream;
+import java.util.Set;
 
 /**
  * An entity that can be sent or received with an HTTP message.
@@ -166,4 +167,20 @@ public interface HttpEntity {
      */
     boolean isStreaming(); // don't expect an exception here
 
+    /**
+     * Returns supplier of message trailers - headers sent after message body.
+     * May return {@code null} if trailers are not available.
+     *
+     * @since 5.0
+     */
+     TrailerSupplier getTrailers();
+
+    /**
+     * Preliminary declaration of trailing headers
+     * @return names of expected trailing headers
+     *
+     * @since 5.0
+     */
+     Set<String> getTrailerNames();
+
 }

Copied: httpcomponents/httpcore/trunk/httpcore/src/main/java/org/apache/http/TrailerSupplier.java (from r1710453, httpcomponents/httpcore/trunk/httpcore-ab/src/main/java/org/apache/http/benchmark/BenchmarkConnection.java)
URL: http://svn.apache.org/viewvc/httpcomponents/httpcore/trunk/httpcore/src/main/java/org/apache/http/TrailerSupplier.java?p2=httpcomponents/httpcore/trunk/httpcore/src/main/java/org/apache/http/TrailerSupplier.java&p1=httpcomponents/httpcore/trunk/httpcore-ab/src/main/java/org/apache/http/benchmark/BenchmarkConnection.java&r1=1710453&r2=1713993&rev=1713993&view=diff
==============================================================================
--- httpcomponents/httpcore/trunk/httpcore-ab/src/main/java/org/apache/http/benchmark/BenchmarkConnection.java (original)
+++ httpcomponents/httpcore/trunk/httpcore/src/main/java/org/apache/http/TrailerSupplier.java Thu Nov 12 09:37:44 2015
@@ -24,32 +24,16 @@
  * <http://www.apache.org/>.
  *
  */
-package org.apache.http.benchmark;
 
-import java.io.InputStream;
-import java.io.OutputStream;
+package org.apache.http;
 
-import org.apache.http.impl.DefaultBHttpClientConnection;
-import org.apache.http.io.SessionInputBuffer;
-import org.apache.http.io.SessionOutputBuffer;
-
-class BenchmarkConnection extends DefaultBHttpClientConnection {
-
-    private final Stats stats;
-
-    BenchmarkConnection(final int bufsize, final Stats stats) {
-        super(bufsize);
-        this.stats = stats;
-    }
-
-    @Override
-    protected OutputStream createContentOutputStream(final long len, final SessionOutputBuffer outbuffer) {
-        return new CountingOutputStream(super.createContentOutputStream(len, outbuffer), this.stats);
-    }
+/**
+ * Supplier of trailing headers to be sent after message body.
+ *
+ * @since 5.0
+ */
+public interface TrailerSupplier {
 
-    @Override
-    protected InputStream createContentInputStream(final long len, final SessionInputBuffer inbuffer) {
-        return new CountingInputStream(super.createContentInputStream(len, inbuffer), this.stats);
-    }
+    Header[] get();
 
 }

Modified: httpcomponents/httpcore/trunk/httpcore/src/main/java/org/apache/http/entity/AbstractHttpEntity.java
URL: http://svn.apache.org/viewvc/httpcomponents/httpcore/trunk/httpcore/src/main/java/org/apache/http/entity/AbstractHttpEntity.java?rev=1713993&r1=1713992&r2=1713993&view=diff
==============================================================================
--- httpcomponents/httpcore/trunk/httpcore/src/main/java/org/apache/http/entity/AbstractHttpEntity.java (original)
+++ httpcomponents/httpcore/trunk/httpcore/src/main/java/org/apache/http/entity/AbstractHttpEntity.java Thu Nov 12 09:37:44 2015
@@ -27,6 +27,10 @@
 
 package org.apache.http.entity;
 
+import java.util.Collections;
+import java.util.Set;
+
+import org.apache.http.TrailerSupplier;
 import org.apache.http.annotation.NotThreadSafe;
 
 /**
@@ -76,4 +80,14 @@ public abstract class AbstractHttpEntity
         this.chunked = b;
     }
 
+    @Override
+    public TrailerSupplier getTrailers() {
+        return null;
+    }
+
+    @Override
+    public Set<String> getTrailerNames() {
+        return Collections.emptySet();
+    }
+
 }

Copied: httpcomponents/httpcore/trunk/httpcore/src/main/java/org/apache/http/entity/HttpEntityWithTrailers.java (from r1710453, httpcomponents/httpcore/trunk/httpcore/src/main/java/org/apache/http/entity/HttpEntityWrapper.java)
URL: http://svn.apache.org/viewvc/httpcomponents/httpcore/trunk/httpcore/src/main/java/org/apache/http/entity/HttpEntityWithTrailers.java?p2=httpcomponents/httpcore/trunk/httpcore/src/main/java/org/apache/http/entity/HttpEntityWithTrailers.java&p1=httpcomponents/httpcore/trunk/httpcore/src/main/java/org/apache/http/entity/HttpEntityWrapper.java&r1=1710453&r2=1713993&rev=1713993&view=diff
==============================================================================
--- httpcomponents/httpcore/trunk/httpcore/src/main/java/org/apache/http/entity/HttpEntityWrapper.java (original)
+++ httpcomponents/httpcore/trunk/httpcore/src/main/java/org/apache/http/entity/HttpEntityWithTrailers.java Thu Nov 12 09:37:44 2015
@@ -30,33 +30,34 @@ package org.apache.http.entity;
 import java.io.IOException;
 import java.io.InputStream;
 import java.io.OutputStream;
+import java.util.LinkedHashSet;
+import java.util.Set;
 
+import org.apache.http.Header;
 import org.apache.http.HttpEntity;
+import org.apache.http.TrailerSupplier;
 import org.apache.http.annotation.NotThreadSafe;
 import org.apache.http.util.Args;
 
 /**
- * Base class for wrapping entities.
- * Keeps a {@link #wrappedEntity wrappedEntity} and delegates all
- * calls to it. Implementations of wrapping entities can derive
- * from this class and need to override only those methods that
- * should not be delegated to the wrapped entity.
+ * Wrapping entity that also includes trailers.
  *
- * @since 4.0
+ * @since 5.0
  */
 @NotThreadSafe
-public class HttpEntityWrapper implements HttpEntity {
+public class HttpEntityWithTrailers implements HttpEntity {
 
-    /** The wrapped entity. */
     private final HttpEntity wrappedEntity;
+    private final Header[] trailers;
 
     /**
      * Creates a new entity wrapper.
      */
-    public HttpEntityWrapper(final HttpEntity wrappedEntity) {
+    public HttpEntityWithTrailers(final HttpEntity wrappedEntity, final Header... trailers) {
         super();
         this.wrappedEntity = Args.notNull(wrappedEntity, "Wrapped entity");
-    } // constructor
+        this.trailers = trailers;
+    }
 
     @Override
     public boolean isRepeatable() {
@@ -65,7 +66,7 @@ public class HttpEntityWrapper implement
 
     @Override
     public boolean isChunked() {
-        return wrappedEntity.isChunked();
+        return true;
     }
 
     @Override
@@ -100,4 +101,23 @@ public class HttpEntityWrapper implement
         return wrappedEntity.isStreaming();
     }
 
+    @Override
+    public TrailerSupplier getTrailers() {
+        return new TrailerSupplier() {
+            @Override
+            public Header[] get() {
+                return trailers;
+            }
+        };
+    }
+
+    @Override
+    public Set<String> getTrailerNames() {
+        final Set<String> names = new LinkedHashSet<>();
+        for (Header trailer: trailers) {
+            names.add(trailer.getName());
+        }
+        return names;
+    }
+
 }

Modified: httpcomponents/httpcore/trunk/httpcore/src/main/java/org/apache/http/entity/HttpEntityWrapper.java
URL: http://svn.apache.org/viewvc/httpcomponents/httpcore/trunk/httpcore/src/main/java/org/apache/http/entity/HttpEntityWrapper.java?rev=1713993&r1=1713992&r2=1713993&view=diff
==============================================================================
--- httpcomponents/httpcore/trunk/httpcore/src/main/java/org/apache/http/entity/HttpEntityWrapper.java (original)
+++ httpcomponents/httpcore/trunk/httpcore/src/main/java/org/apache/http/entity/HttpEntityWrapper.java Thu Nov 12 09:37:44 2015
@@ -30,8 +30,10 @@ package org.apache.http.entity;
 import java.io.IOException;
 import java.io.InputStream;
 import java.io.OutputStream;
+import java.util.Set;
 
 import org.apache.http.HttpEntity;
+import org.apache.http.TrailerSupplier;
 import org.apache.http.annotation.NotThreadSafe;
 import org.apache.http.util.Args;
 
@@ -100,4 +102,14 @@ public class HttpEntityWrapper implement
         return wrappedEntity.isStreaming();
     }
 
+    @Override
+    public TrailerSupplier getTrailers() {
+        return wrappedEntity.getTrailers();
+    }
+
+    @Override
+    public Set<String> getTrailerNames() {
+        return wrappedEntity.getTrailerNames();
+    }
+
 }

Modified: httpcomponents/httpcore/trunk/httpcore/src/main/java/org/apache/http/impl/BHttpConnectionBase.java
URL: http://svn.apache.org/viewvc/httpcomponents/httpcore/trunk/httpcore/src/main/java/org/apache/http/impl/BHttpConnectionBase.java?rev=1713993&r1=1713992&r2=1713993&view=diff
==============================================================================
--- httpcomponents/httpcore/trunk/httpcore/src/main/java/org/apache/http/impl/BHttpConnectionBase.java (original)
+++ httpcomponents/httpcore/trunk/httpcore/src/main/java/org/apache/http/impl/BHttpConnectionBase.java Thu Nov 12 09:37:44 2015
@@ -45,6 +45,7 @@ import org.apache.http.HttpEntity;
 import org.apache.http.HttpException;
 import org.apache.http.HttpHeaders;
 import org.apache.http.HttpMessage;
+import org.apache.http.TrailerSupplier;
 import org.apache.http.config.MessageConstraints;
 import org.apache.http.entity.ContentLengthStrategy;
 import org.apache.http.impl.io.ChunkedInputStream;
@@ -138,11 +139,12 @@ class BHttpConnectionBase implements BHt
 
     protected OutputStream createContentOutputStream(
             final long len,
-            final SessionOutputBuffer outbuffer) {
+            final SessionOutputBuffer outbuffer,
+            final TrailerSupplier trailers) {
         if (len >= 0) {
             return new ContentLengthOutputStream(outbuffer, len);
         } else if (len == ContentLengthStrategy.CHUNKED) {
-            return new ChunkedOutputStream(2048, outbuffer);
+            return new ChunkedOutputStream(2048, outbuffer, trailers);
         } else {
             return new IdentityOutputStream(outbuffer);
         }

Modified: httpcomponents/httpcore/trunk/httpcore/src/main/java/org/apache/http/impl/DefaultBHttpClientConnection.java
URL: http://svn.apache.org/viewvc/httpcomponents/httpcore/trunk/httpcore/src/main/java/org/apache/http/impl/DefaultBHttpClientConnection.java?rev=1713993&r1=1713992&r2=1713993&view=diff
==============================================================================
--- httpcomponents/httpcore/trunk/httpcore/src/main/java/org/apache/http/impl/DefaultBHttpClientConnection.java (original)
+++ httpcomponents/httpcore/trunk/httpcore/src/main/java/org/apache/http/impl/DefaultBHttpClientConnection.java Thu Nov 12 09:37:44 2015
@@ -154,7 +154,7 @@ public class DefaultBHttpClientConnectio
         if (len == ContentLengthStrategy.UNDEFINED) {
             throw new LengthRequiredException("Length required");
         }
-        final OutputStream outstream = createContentOutputStream(len, this.outbuffer);
+        final OutputStream outstream = createContentOutputStream(len, this.outbuffer, entity.getTrailers());
         entity.writeTo(outstream);
         outstream.close();
     }
@@ -174,10 +174,10 @@ public class DefaultBHttpClientConnectio
         }
         final long len = this.outgoingContentStrategy.determineLength(request);
         if (len == ContentLengthStrategy.CHUNKED) {
-            final OutputStream outstream = createContentOutputStream(len, this.outbuffer);
+            final OutputStream outstream = createContentOutputStream(len, this.outbuffer, entity.getTrailers());
             outstream.close();
         } else if (len >= 0 && len <= 1024) {
-            final OutputStream outstream = createContentOutputStream(len, this.outbuffer);
+            final OutputStream outstream = createContentOutputStream(len, this.outbuffer, null);
             entity.writeTo(outstream);
             outstream.close();
         } else {
@@ -207,5 +207,4 @@ public class DefaultBHttpClientConnectio
         }
         response.setEntity(createIncomingEntity(response, this.inbuffer, len));
     }
-
 }

Modified: httpcomponents/httpcore/trunk/httpcore/src/main/java/org/apache/http/impl/DefaultBHttpServerConnection.java
URL: http://svn.apache.org/viewvc/httpcomponents/httpcore/trunk/httpcore/src/main/java/org/apache/http/impl/DefaultBHttpServerConnection.java?rev=1713993&r1=1713992&r2=1713993&view=diff
==============================================================================
--- httpcomponents/httpcore/trunk/httpcore/src/main/java/org/apache/http/impl/DefaultBHttpServerConnection.java (original)
+++ httpcomponents/httpcore/trunk/httpcore/src/main/java/org/apache/http/impl/DefaultBHttpServerConnection.java Thu Nov 12 09:37:44 2015
@@ -174,9 +174,8 @@ public class DefaultBHttpServerConnectio
             return;
         }
         final long len = this.outgoingContentStrategy.determineLength(response);
-        final OutputStream outstream = createContentOutputStream(len, this.outbuffer);
+        final OutputStream outstream = createContentOutputStream(len, this.outbuffer, entity.getTrailers());
         entity.writeTo(outstream);
         outstream.close();
     }
-
 }

Modified: httpcomponents/httpcore/trunk/httpcore/src/main/java/org/apache/http/impl/IncomingHttpEntity.java
URL: http://svn.apache.org/viewvc/httpcomponents/httpcore/trunk/httpcore/src/main/java/org/apache/http/impl/IncomingHttpEntity.java?rev=1713993&r1=1713992&r2=1713993&view=diff
==============================================================================
--- httpcomponents/httpcore/trunk/httpcore/src/main/java/org/apache/http/impl/IncomingHttpEntity.java (original)
+++ httpcomponents/httpcore/trunk/httpcore/src/main/java/org/apache/http/impl/IncomingHttpEntity.java Thu Nov 12 09:37:44 2015
@@ -29,8 +29,11 @@ package org.apache.http.impl;
 
 import java.io.IOException;
 import java.io.InputStream;
+import java.util.Collections;
+import java.util.Set;
 
 import org.apache.http.Header;
+import org.apache.http.TrailerSupplier;
 import org.apache.http.annotation.NotThreadSafe;
 import org.apache.http.entity.AbstractImmutableHttpEntity;
 import org.apache.http.impl.io.EmptyInputStream;
@@ -92,4 +95,14 @@ public class IncomingHttpEntity extends
         return content != null && content != EmptyInputStream.INSTANCE;
     }
 
+    @Override
+    public TrailerSupplier getTrailers() {
+        return null;
+    }
+
+    @Override
+    public Set<String> getTrailerNames() {
+        return Collections.emptySet();
+    }
+
 }

Modified: httpcomponents/httpcore/trunk/httpcore/src/main/java/org/apache/http/impl/io/ChunkedInputStream.java
URL: http://svn.apache.org/viewvc/httpcomponents/httpcore/trunk/httpcore/src/main/java/org/apache/http/impl/io/ChunkedInputStream.java?rev=1713993&r1=1713992&r2=1713993&view=diff
==============================================================================
--- httpcomponents/httpcore/trunk/httpcore/src/main/java/org/apache/http/impl/io/ChunkedInputStream.java (original)
+++ httpcomponents/httpcore/trunk/httpcore/src/main/java/org/apache/http/impl/io/ChunkedInputStream.java Thu Nov 12 09:37:44 2015
@@ -287,7 +287,7 @@ public class ChunkedInputStream extends
                     constraints.getMaxLineLength(),
                     null);
         } catch (final HttpException ex) {
-            final IOException ioe = new MalformedChunkCodingException("Invalid footer: "
+            final IOException ioe = new MalformedChunkCodingException("Invalid trailing header: "
                     + ex.getMessage());
             ioe.initCause(ex);
             throw ioe;

Modified: httpcomponents/httpcore/trunk/httpcore/src/main/java/org/apache/http/impl/io/ChunkedOutputStream.java
URL: http://svn.apache.org/viewvc/httpcomponents/httpcore/trunk/httpcore/src/main/java/org/apache/http/impl/io/ChunkedOutputStream.java?rev=1713993&r1=1713992&r2=1713993&view=diff
==============================================================================
--- httpcomponents/httpcore/trunk/httpcore/src/main/java/org/apache/http/impl/io/ChunkedOutputStream.java (original)
+++ httpcomponents/httpcore/trunk/httpcore/src/main/java/org/apache/http/impl/io/ChunkedOutputStream.java Thu Nov 12 09:37:44 2015
@@ -30,8 +30,12 @@ package org.apache.http.impl.io;
 import java.io.IOException;
 import java.io.OutputStream;
 
+import org.apache.http.FormattedHeader;
+import org.apache.http.Header;
+import org.apache.http.TrailerSupplier;
 import org.apache.http.annotation.NotThreadSafe;
 import org.apache.http.io.SessionOutputBuffer;
+import org.apache.http.message.BasicLineFormatter;
 import org.apache.http.util.CharArrayBuffer;
 
 /**
@@ -63,17 +67,32 @@ public class ChunkedOutputStream extends
 
     private final CharArrayBuffer linebuffer;
 
+    private final TrailerSupplier trailers;
+
     /**
      * Wraps a session output buffer and chunk-encodes the output.
      *
      * @param bufferSize The minimum chunk size (excluding last chunk)
      * @param out The session output buffer
+     *
+     * @since 5.0
      */
-    public ChunkedOutputStream(final int bufferSize, final SessionOutputBuffer out) {
+    public ChunkedOutputStream(final int bufferSize, final SessionOutputBuffer out, final TrailerSupplier trailers) {
         super();
         this.cache = new byte[bufferSize];
         this.out = out;
         this.linebuffer = new CharArrayBuffer(32);
+        this.trailers = trailers;
+    }
+
+    /**
+     * Wraps a session output buffer and chunk-encodes the output.
+     *
+     * @param bufferSize The minimum chunk size (excluding last chunk)
+     * @param out The session output buffer
+     */
+    public ChunkedOutputStream(final int bufferSize, final SessionOutputBuffer out) {
+        this(bufferSize, out, null);
     }
 
     /**
@@ -111,10 +130,27 @@ public class ChunkedOutputStream extends
         this.linebuffer.clear();
         this.linebuffer.append('0');
         this.out.writeLine(this.linebuffer);
+        writeTrailers();
         this.linebuffer.clear();
         this.out.writeLine(this.linebuffer);
     }
 
+    private void writeTrailers() throws IOException {
+        final Header[] headers = this.trailers != null ? this.trailers.get() : null;
+        if (headers != null) {
+            for (Header header: headers) {
+                if (header instanceof FormattedHeader) {
+                    final CharArrayBuffer chbuffer = ((FormattedHeader) header).getBuffer();
+                    this.out.writeLine(chbuffer);
+                } else {
+                    this.linebuffer.clear();
+                    BasicLineFormatter.INSTANCE.formatHeader(this.linebuffer, header);
+                    this.out.writeLine(this.linebuffer);
+                }
+            }
+        }
+    }
+
     // ----------------------------------------------------------- Public Methods
     /**
      * Must be called to ensure the internal cache is flushed and the closing

Modified: httpcomponents/httpcore/trunk/httpcore/src/main/java/org/apache/http/message/BufferedHeader.java
URL: http://svn.apache.org/viewvc/httpcomponents/httpcore/trunk/httpcore/src/main/java/org/apache/http/message/BufferedHeader.java?rev=1713993&r1=1713992&r2=1713993&view=diff
==============================================================================
--- httpcomponents/httpcore/trunk/httpcore/src/main/java/org/apache/http/message/BufferedHeader.java (original)
+++ httpcomponents/httpcore/trunk/httpcore/src/main/java/org/apache/http/message/BufferedHeader.java Thu Nov 12 09:37:44 2015
@@ -63,6 +63,17 @@ public class BufferedHeader implements F
     private final int valuePos;
 
     /**
+     * @since 5.0
+     */
+    public static BufferedHeader create(final CharArrayBuffer buffer) {
+        try {
+            return new BufferedHeader(buffer);
+        } catch (ParseException ex) {
+            throw new IllegalArgumentException(ex.getMessage());
+        }
+    }
+
+    /**
      * Creates a new header from a buffer.
      * The name of the header will be parsed immediately,
      * the value only if it is accessed.

Modified: httpcomponents/httpcore/trunk/httpcore/src/main/java/org/apache/http/protocol/RequestContent.java
URL: http://svn.apache.org/viewvc/httpcomponents/httpcore/trunk/httpcore/src/main/java/org/apache/http/protocol/RequestContent.java?rev=1713993&r1=1713992&r2=1713993&view=diff
==============================================================================
--- httpcomponents/httpcore/trunk/httpcore/src/main/java/org/apache/http/protocol/RequestContent.java (original)
+++ httpcomponents/httpcore/trunk/httpcore/src/main/java/org/apache/http/protocol/RequestContent.java Thu Nov 12 09:37:44 2015
@@ -28,6 +28,7 @@
 package org.apache.http.protocol;
 
 import java.io.IOException;
+import java.util.Set;
 
 import org.apache.http.HeaderElements;
 import org.apache.http.HttpEntity;
@@ -107,6 +108,10 @@ public class RequestContent implements H
                             "Chunked transfer encoding not allowed for " + ver);
                 }
                 request.addHeader(HttpHeaders.TRANSFER_ENCODING, HeaderElements.CHUNKED_ENCODING);
+                final Set<String> trailerNames = entity.getTrailerNames();
+                if (trailerNames != null && !trailerNames.isEmpty()) {
+                    request.setHeader(TrailerNameFormatter.format(entity));
+                }
             } else {
                 request.addHeader(HttpHeaders.CONTENT_LENGTH, Long.toString(entity.getContentLength()));
             }

Modified: httpcomponents/httpcore/trunk/httpcore/src/main/java/org/apache/http/protocol/ResponseContent.java
URL: http://svn.apache.org/viewvc/httpcomponents/httpcore/trunk/httpcore/src/main/java/org/apache/http/protocol/ResponseContent.java?rev=1713993&r1=1713992&r2=1713993&view=diff
==============================================================================
--- httpcomponents/httpcore/trunk/httpcore/src/main/java/org/apache/http/protocol/ResponseContent.java (original)
+++ httpcomponents/httpcore/trunk/httpcore/src/main/java/org/apache/http/protocol/ResponseContent.java Thu Nov 12 09:37:44 2015
@@ -28,6 +28,7 @@
 package org.apache.http.protocol;
 
 import java.io.IOException;
+import java.util.Set;
 
 import org.apache.http.HeaderElements;
 import org.apache.http.HttpEntity;
@@ -111,6 +112,10 @@ public class ResponseContent implements
             final long len = entity.getContentLength();
             if (entity.isChunked() && !ver.lessEquals(HttpVersion.HTTP_1_0)) {
                 response.addHeader(HttpHeaders.TRANSFER_ENCODING, HeaderElements.CHUNKED_ENCODING);
+                final Set<String> trailerNames = entity.getTrailerNames();
+                if (trailerNames != null && !trailerNames.isEmpty()) {
+                    response.setHeader(TrailerNameFormatter.format(entity));
+                }
             } else if (len >= 0) {
                 response.addHeader(HttpHeaders.CONTENT_LENGTH, Long.toString(entity.getContentLength()));
             }

Added: httpcomponents/httpcore/trunk/httpcore/src/main/java/org/apache/http/protocol/TrailerNameFormatter.java
URL: http://svn.apache.org/viewvc/httpcomponents/httpcore/trunk/httpcore/src/main/java/org/apache/http/protocol/TrailerNameFormatter.java?rev=1713993&view=auto
==============================================================================
--- httpcomponents/httpcore/trunk/httpcore/src/main/java/org/apache/http/protocol/TrailerNameFormatter.java (added)
+++ httpcomponents/httpcore/trunk/httpcore/src/main/java/org/apache/http/protocol/TrailerNameFormatter.java Thu Nov 12 09:37:44 2015
@@ -0,0 +1,71 @@
+/*
+ * ====================================================================
+ * 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.
+ * ====================================================================
+ *
+ * This software consists of voluntary contributions made by many
+ * individuals on behalf of the Apache Software Foundation.  For more
+ * information on the Apache Software Foundation, please see
+ * <http://www.apache.org/>.
+ *
+ */
+
+package org.apache.http.protocol;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+import java.util.Set;
+
+import org.apache.http.Header;
+import org.apache.http.HttpEntity;
+import org.apache.http.HttpHeaders;
+import org.apache.http.message.BufferedHeader;
+import org.apache.http.util.CharArrayBuffer;
+
+/**
+ * Utility class to generate Trailer header.
+ *
+ * @since 5.0
+ */
+public class TrailerNameFormatter {
+
+    public static Header format(final HttpEntity entity) {
+        if (entity == null) {
+            return null;
+        }
+        final Set<String> trailerNames = entity.getTrailerNames();
+        if (trailerNames != null && !trailerNames.isEmpty()) {
+            final List<String> elements = new ArrayList<>(trailerNames);
+            Collections.sort(elements);
+            final CharArrayBuffer buffer = new CharArrayBuffer(trailerNames.size() + 20);
+            buffer.append(HttpHeaders.TRAILER);
+            buffer.append(": ");
+            for (int i = 0; i < elements.size(); i++) {
+                final String element = elements.get(i);
+                if (i > 0) {
+                    buffer.append(", ");
+                }
+                buffer.append(element);
+            }
+            return BufferedHeader.create(buffer);
+        }
+        return null;
+    }
+
+}

Propchange: httpcomponents/httpcore/trunk/httpcore/src/main/java/org/apache/http/protocol/TrailerNameFormatter.java
------------------------------------------------------------------------------
    svn:eol-style = native

Propchange: httpcomponents/httpcore/trunk/httpcore/src/main/java/org/apache/http/protocol/TrailerNameFormatter.java
------------------------------------------------------------------------------
    svn:keywords = Date Revision

Propchange: httpcomponents/httpcore/trunk/httpcore/src/main/java/org/apache/http/protocol/TrailerNameFormatter.java
------------------------------------------------------------------------------
    svn:mime-type = text/plain

Modified: httpcomponents/httpcore/trunk/httpcore/src/test/java/org/apache/http/impl/io/TestChunkCoding.java
URL: http://svn.apache.org/viewvc/httpcomponents/httpcore/trunk/httpcore/src/test/java/org/apache/http/impl/io/TestChunkCoding.java?rev=1713993&r1=1713992&r2=1713993&view=diff
==============================================================================
--- httpcomponents/httpcore/trunk/httpcore/src/test/java/org/apache/http/impl/io/TestChunkCoding.java (original)
+++ httpcomponents/httpcore/trunk/httpcore/src/test/java/org/apache/http/impl/io/TestChunkCoding.java Thu Nov 12 09:37:44 2015
@@ -38,11 +38,13 @@ import org.apache.http.Consts;
 import org.apache.http.Header;
 import org.apache.http.MalformedChunkCodingException;
 import org.apache.http.MessageConstraintException;
+import org.apache.http.TrailerSupplier;
 import org.apache.http.TruncatedChunkException;
 import org.apache.http.config.MessageConstraints;
 import org.apache.http.impl.SessionInputBufferMock;
 import org.apache.http.impl.SessionOutputBufferMock;
 import org.apache.http.io.SessionInputBuffer;
+import org.apache.http.message.BasicHeader;
 import org.junit.Assert;
 import org.junit.Test;
 
@@ -362,7 +364,28 @@ public class TestChunkCoding {
         final String output = new String(result.toByteArray(), Consts.ISO_8859_1);
         Assert.assertEquals(input, output);
         in.close();
-}
+    }
+
+    @Test
+    public void testChunkedOutputStreamWithTrailers() throws IOException {
+        final SessionOutputBufferMock buffer = new SessionOutputBufferMock();
+        final Header[] trailers = new Header[] {
+                new BasicHeader("E", ""),
+                new BasicHeader("Y", "Z")
+        };
+        final ChunkedOutputStream out = new ChunkedOutputStream(2, buffer, new TrailerSupplier() {
+            @Override
+            public Header[] get() {
+                return trailers;
+            }
+        });
+        out.write('x');
+        out.finish();
+        out.close();
+
+        final String content = new String(buffer.getData(), Consts.ASCII);
+        Assert.assertEquals("1\r\nx\r\n0\r\nE: \r\nY: Z\r\n\r\n", content);
+    }
 
     @Test
     public void testChunkedOutputStream() throws IOException {
@@ -375,28 +398,8 @@ public class TestChunkCoding {
         out.finish();
         out.close();
 
-        final byte [] rawdata =  buffer.getData();
-
-        Assert.assertEquals(19, rawdata.length);
-        Assert.assertEquals('2', rawdata[0]);
-        Assert.assertEquals('\r', rawdata[1]);
-        Assert.assertEquals('\n', rawdata[2]);
-        Assert.assertEquals('1', rawdata[3]);
-        Assert.assertEquals('2', rawdata[4]);
-        Assert.assertEquals('\r', rawdata[5]);
-        Assert.assertEquals('\n', rawdata[6]);
-        Assert.assertEquals('2', rawdata[7]);
-        Assert.assertEquals('\r', rawdata[8]);
-        Assert.assertEquals('\n', rawdata[9]);
-        Assert.assertEquals('3', rawdata[10]);
-        Assert.assertEquals('4', rawdata[11]);
-        Assert.assertEquals('\r', rawdata[12]);
-        Assert.assertEquals('\n', rawdata[13]);
-        Assert.assertEquals('0', rawdata[14]);
-        Assert.assertEquals('\r', rawdata[15]);
-        Assert.assertEquals('\n', rawdata[16]);
-        Assert.assertEquals('\r', rawdata[17]);
-        Assert.assertEquals('\n', rawdata[18]);
+        final String content = new String(buffer.getData(), Consts.ASCII);
+        Assert.assertEquals("2\r\n12\r\n2\r\n34\r\n0\r\n\r\n", content);
     }
 
     @Test
@@ -407,47 +410,20 @@ public class TestChunkCoding {
         out.finish();
         out.close();
 
-        final byte [] rawdata =  buffer.getData();
-
-        Assert.assertEquals(14, rawdata.length);
-        Assert.assertEquals('4', rawdata[0]);
-        Assert.assertEquals('\r', rawdata[1]);
-        Assert.assertEquals('\n', rawdata[2]);
-        Assert.assertEquals('1', rawdata[3]);
-        Assert.assertEquals('2', rawdata[4]);
-        Assert.assertEquals('3', rawdata[5]);
-        Assert.assertEquals('4', rawdata[6]);
-        Assert.assertEquals('\r', rawdata[7]);
-        Assert.assertEquals('\n', rawdata[8]);
-        Assert.assertEquals('0', rawdata[9]);
-        Assert.assertEquals('\r', rawdata[10]);
-        Assert.assertEquals('\n', rawdata[11]);
-        Assert.assertEquals('\r', rawdata[12]);
-        Assert.assertEquals('\n', rawdata[13]);
+        final String content = new String(buffer.getData(), Consts.ASCII);
+        Assert.assertEquals("4\r\n1234\r\n0\r\n\r\n", content);
     }
 
     @Test
     public void testChunkedOutputStreamSmallChunk() throws IOException {
-        final ByteArrayOutputStream buffer = new ByteArrayOutputStream();
-        final ChunkedOutputStream out = new ChunkedOutputStream(2, new SessionOutputBufferMock(buffer));
+        final SessionOutputBufferMock buffer = new SessionOutputBufferMock();
+        final ChunkedOutputStream out = new ChunkedOutputStream(2, buffer);
         out.write('1');
         out.finish();
         out.close();
 
-        final byte [] rawdata =  buffer.toByteArray();
-
-        Assert.assertEquals(11, rawdata.length);
-        Assert.assertEquals('1', rawdata[0]);
-        Assert.assertEquals('\r', rawdata[1]);
-        Assert.assertEquals('\n', rawdata[2]);
-        Assert.assertEquals('1', rawdata[3]);
-        Assert.assertEquals('\r', rawdata[4]);
-        Assert.assertEquals('\n', rawdata[5]);
-        Assert.assertEquals('0', rawdata[6]);
-        Assert.assertEquals('\r', rawdata[7]);
-        Assert.assertEquals('\n', rawdata[8]);
-        Assert.assertEquals('\r', rawdata[9]);
-        Assert.assertEquals('\n', rawdata[10]);
+        final String content = new String(buffer.getData(), Consts.ASCII);
+        Assert.assertEquals("1\r\n1\r\n0\r\n\r\n", content);
     }
 
     @Test

Modified: httpcomponents/httpcore/trunk/httpcore/src/test/java/org/apache/http/protocol/TestStandardInterceptors.java
URL: http://svn.apache.org/viewvc/httpcomponents/httpcore/trunk/httpcore/src/test/java/org/apache/http/protocol/TestStandardInterceptors.java?rev=1713993&r1=1713992&r2=1713993&view=diff
==============================================================================
--- httpcomponents/httpcore/trunk/httpcore/src/test/java/org/apache/http/protocol/TestStandardInterceptors.java (original)
+++ httpcomponents/httpcore/trunk/httpcore/src/test/java/org/apache/http/protocol/TestStandardInterceptors.java Thu Nov 12 09:37:44 2015
@@ -41,6 +41,7 @@ import org.apache.http.HttpStatus;
 import org.apache.http.HttpVersion;
 import org.apache.http.ProtocolException;
 import org.apache.http.entity.BasicHttpEntity;
+import org.apache.http.entity.HttpEntityWithTrailers;
 import org.apache.http.entity.StringEntity;
 import org.apache.http.message.BasicHeader;
 import org.apache.http.message.BasicHttpRequest;
@@ -300,6 +301,24 @@ public class TestStandardInterceptors {
     }
 
     @Test
+    public void testRequestContentEntityWithTrailers() throws Exception {
+        final HttpContext context = new BasicHttpContext(null);
+        final BasicHttpRequest request = new BasicHttpRequest("POST", "/");
+        final String s = "whatever";
+        final StringEntity entity = new StringEntity(s, Consts.ASCII);
+        request.setEntity(new HttpEntityWithTrailers(entity,
+                new BasicHeader("h1", "this"), new BasicHeader("h1", "that"), new BasicHeader("h2", "this and that")));
+
+        final RequestContent interceptor = new RequestContent();
+        interceptor.process(request, context);
+        final Header header1 = request.getFirstHeader(HttpHeaders.TRANSFER_ENCODING);
+        Assert.assertNotNull(header1);
+        final Header header2 = request.getFirstHeader(HttpHeaders.TRAILER);
+        Assert.assertNotNull(header2);
+        Assert.assertEquals("h1, h2", header2.getValue());
+    }
+
+    @Test
     public void testRequestExpectContinueGenerated() throws Exception {
         final HttpCoreContext context = HttpCoreContext.create();
         final BasicHttpRequest request = new BasicHttpRequest("POST", "/");
@@ -899,6 +918,24 @@ public class TestStandardInterceptors {
     }
 
     @Test
+    public void testResponseContentEntityWithTrailers() throws Exception {
+        final HttpContext context = new BasicHttpContext(null);
+        final HttpResponse response = new BasicHttpResponse(HttpVersion.HTTP_1_1, HttpStatus.SC_OK, "OK");
+        final String s = "whatever";
+        final StringEntity entity = new StringEntity(s, Consts.ASCII);
+        response.setEntity(new HttpEntityWithTrailers(entity,
+                new BasicHeader("h1", "this"), new BasicHeader("h1", "that"), new BasicHeader("h2", "this and that")));
+
+        final ResponseContent interceptor = new ResponseContent();
+        interceptor.process(response, context);
+        final Header header1 = response.getFirstHeader(HttpHeaders.TRANSFER_ENCODING);
+        Assert.assertNotNull(header1);
+        final Header header2 = response.getFirstHeader(HttpHeaders.TRAILER);
+        Assert.assertNotNull(header2);
+        Assert.assertEquals("h1, h2", header2.getValue());
+    }
+
+    @Test
     public void testResponseDateGenerated() throws Exception {
         final HttpContext context = new BasicHttpContext(null);
         final HttpResponse response = new BasicHttpResponse(HttpVersion.HTTP_1_1, HttpStatus.SC_OK, "OK");

Added: httpcomponents/httpcore/trunk/httpcore/src/test/java/org/apache/http/protocol/TestTrailerNameFormatter.java
URL: http://svn.apache.org/viewvc/httpcomponents/httpcore/trunk/httpcore/src/test/java/org/apache/http/protocol/TestTrailerNameFormatter.java?rev=1713993&view=auto
==============================================================================
--- httpcomponents/httpcore/trunk/httpcore/src/test/java/org/apache/http/protocol/TestTrailerNameFormatter.java (added)
+++ httpcomponents/httpcore/trunk/httpcore/src/test/java/org/apache/http/protocol/TestTrailerNameFormatter.java Thu Nov 12 09:37:44 2015
@@ -0,0 +1,76 @@
+/*
+ * ====================================================================
+ * 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.
+ * ====================================================================
+ *
+ * This software consists of voluntary contributions made by many
+ * individuals on behalf of the Apache Software Foundation.  For more
+ * information on the Apache Software Foundation, please see
+ * <http://www.apache.org/>.
+ *
+ */
+
+package org.apache.http.protocol;
+
+import org.apache.http.Consts;
+import org.apache.http.Header;
+import org.apache.http.HttpEntity;
+import org.apache.http.entity.HttpEntityWithTrailers;
+import org.apache.http.entity.StringEntity;
+import org.apache.http.message.BasicHeader;
+import org.junit.Assert;
+import org.junit.Test;
+
+public class TestTrailerNameFormatter {
+
+    @Test
+    public void testTrailerFormatting() throws Exception {
+        final HttpEntity entity = new HttpEntityWithTrailers(
+                new StringEntity("some stuff with trailers", Consts.ASCII),
+                new BasicHeader("z", "this"), new BasicHeader("b", "that"), new BasicHeader("a", "this and that"));
+        final Header header = TrailerNameFormatter.format(entity);
+        Assert.assertNotNull(header);
+        Assert.assertEquals("a, b, z", header.getValue());
+    }
+
+    @Test
+    public void testTrailerFormattingSameName() throws Exception {
+        final HttpEntity entity = new HttpEntityWithTrailers(
+                new StringEntity("some stuff with trailers", Consts.ASCII),
+                new BasicHeader("a", "this"), new BasicHeader("a", "that"), new BasicHeader("a", "this and that"));
+        final Header header = TrailerNameFormatter.format(entity);
+        Assert.assertNotNull(header);
+        Assert.assertEquals("a", header.getValue());
+    }
+
+    @Test
+    public void testTrailerNoTrailers() throws Exception {
+        final HttpEntity entity = new HttpEntityWithTrailers(
+                new StringEntity("some stuff and no trailers", Consts.ASCII));
+        final Header header = TrailerNameFormatter.format(entity);
+        Assert.assertNull(header);
+    }
+
+    @Test
+    public void testTrailerNullTrailers() throws Exception {
+        final HttpEntity entity = new StringEntity("some stuff and no trailers", Consts.ASCII);
+        final Header header = TrailerNameFormatter.format(entity);
+        Assert.assertNull(header);
+    }
+
+}

Propchange: httpcomponents/httpcore/trunk/httpcore/src/test/java/org/apache/http/protocol/TestTrailerNameFormatter.java
------------------------------------------------------------------------------
    svn:eol-style = native

Propchange: httpcomponents/httpcore/trunk/httpcore/src/test/java/org/apache/http/protocol/TestTrailerNameFormatter.java
------------------------------------------------------------------------------
    svn:keywords = Date Revision

Propchange: httpcomponents/httpcore/trunk/httpcore/src/test/java/org/apache/http/protocol/TestTrailerNameFormatter.java
------------------------------------------------------------------------------
    svn:mime-type = text/plain



Mime
View raw message