View Javadoc
1   /*
2    * ====================================================================
3    * Licensed to the Apache Software Foundation (ASF) under one
4    * or more contributor license agreements.  See the NOTICE file
5    * distributed with this work for additional information
6    * regarding copyright ownership.  The ASF licenses this file
7    * to you under the Apache License, Version 2.0 (the
8    * "License"); you may not use this file except in compliance
9    * with the License.  You may obtain a copy of the License at
10   *
11   *   http://www.apache.org/licenses/LICENSE-2.0
12   *
13   * Unless required by applicable law or agreed to in writing,
14   * software distributed under the License is distributed on an
15   * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
16   * KIND, either express or implied.  See the License for the
17   * specific language governing permissions and limitations
18   * under the License.
19   * ====================================================================
20   *
21   * This software consists of voluntary contributions made by many
22   * individuals on behalf of the Apache Software Foundation.  For more
23   * information on the Apache Software Foundation, please see
24   * <http://www.apache.org/>.
25   *
26   */
27  
28  package org.apache.http.nio.integration;
29  
30  import java.io.IOException;
31  import java.net.InetSocketAddress;
32  import java.nio.ByteBuffer;
33  import java.nio.channels.WritableByteChannel;
34  import java.util.concurrent.ExecutionException;
35  import java.util.concurrent.Future;
36  
37  import org.apache.http.Consts;
38  import org.apache.http.HttpEntity;
39  import org.apache.http.HttpHost;
40  import org.apache.http.HttpResponse;
41  import org.apache.http.HttpStatus;
42  import org.apache.http.MalformedChunkCodingException;
43  import org.apache.http.TruncatedChunkException;
44  import org.apache.http.entity.ContentLengthStrategy;
45  import org.apache.http.entity.ContentType;
46  import org.apache.http.entity.InputStreamEntity;
47  import org.apache.http.impl.io.HttpTransportMetricsImpl;
48  import org.apache.http.impl.nio.DefaultNHttpServerConnection;
49  import org.apache.http.impl.nio.codecs.AbstractContentEncoder;
50  import org.apache.http.message.BasicHttpRequest;
51  import org.apache.http.nio.ContentDecoder;
52  import org.apache.http.nio.ContentEncoder;
53  import org.apache.http.nio.IOControl;
54  import org.apache.http.nio.entity.ContentInputStream;
55  import org.apache.http.nio.protocol.AbstractAsyncResponseConsumer;
56  import org.apache.http.nio.protocol.BasicAsyncRequestHandler;
57  import org.apache.http.nio.protocol.BasicAsyncRequestProducer;
58  import org.apache.http.nio.reactor.IOSession;
59  import org.apache.http.nio.reactor.ListenerEndpoint;
60  import org.apache.http.nio.reactor.SessionOutputBuffer;
61  import org.apache.http.nio.testserver.HttpCoreNIOTestBase;
62  import org.apache.http.nio.testserver.LoggingNHttpServerConnection;
63  import org.apache.http.nio.testserver.ServerConnectionFactory;
64  import org.apache.http.nio.util.HeapByteBufferAllocator;
65  import org.apache.http.nio.util.SimpleInputBuffer;
66  import org.apache.http.protocol.HttpContext;
67  import org.apache.http.util.CharArrayBuffer;
68  import org.apache.http.util.EntityUtils;
69  import org.junit.After;
70  import org.junit.Assert;
71  import org.junit.Before;
72  import org.junit.Test;
73  
74  /**
75   * Tests for handling truncated chunks.
76   */
77  public class TestTruncatedChunks extends HttpCoreNIOTestBase {
78  
79      @Before
80      public void setUp() throws Exception {
81          initServer();
82          initClient();
83      }
84  
85      @After
86      public void tearDown() throws Exception {
87          shutDownClient();
88          shutDownServer();
89      }
90  
91      @Override
92      protected ServerConnectionFactory createServerConnectionFactory() throws Exception {
93          return new CustomServerConnectionFactory();
94      }
95  
96      private static final byte[] GARBAGE = new byte[] {'1', '2', '3', '4', '5' };
97  
98      static class BrokenChunkEncoder extends AbstractContentEncoder {
99  
100         private final CharArrayBuffer lineBuffer;
101         private boolean done;
102 
103         public BrokenChunkEncoder(
104                 final WritableByteChannel channel,
105                 final SessionOutputBuffer buffer,
106                 final HttpTransportMetricsImpl metrics) {
107             super(channel, buffer, metrics);
108             this.lineBuffer = new CharArrayBuffer(16);
109         }
110 
111         @Override
112         public void complete() throws IOException {
113             super.complete();
114         }
115 
116         @Override
117         public int write(final ByteBuffer src) throws IOException {
118             final int chunk;
119             if (!this.done) {
120                 this.lineBuffer.clear();
121                 this.lineBuffer.append(Integer.toHexString(GARBAGE.length * 10));
122                 this.buffer.writeLine(this.lineBuffer);
123                 this.buffer.write(ByteBuffer.wrap(GARBAGE));
124                 this.done = true;
125                 chunk = GARBAGE.length;
126             } else {
127                 chunk = 0;
128             }
129             final long bytesWritten = this.buffer.flush(this.channel);
130             if (bytesWritten > 0) {
131                 this.metrics.incrementBytesTransferred(bytesWritten);
132             }
133             if (!this.buffer.hasData()) {
134                 this.channel.close();
135             }
136             return chunk;
137         }
138 
139     }
140 
141     static class CustomServerConnectionFactory extends ServerConnectionFactory {
142 
143         public CustomServerConnectionFactory() {
144             super();
145         }
146 
147         @Override
148         public DefaultNHttpServerConnection createConnection(final IOSession session) {
149             return new LoggingNHttpServerConnection(session) {
150 
151                 @Override
152                 protected ContentEncoder createContentEncoder(
153                         final long len,
154                         final WritableByteChannel channel,
155                         final SessionOutputBuffer buffer,
156                         final HttpTransportMetricsImpl metrics) {
157                     if (len == ContentLengthStrategy.CHUNKED) {
158                         return new BrokenChunkEncoder(channel, buffer, metrics);
159                     } else {
160                         return super.createContentEncoder(len, channel, buffer, metrics);
161                     }
162                 }
163 
164             };
165         }
166 
167     }
168 
169     @Test
170     public void testTruncatedChunkException() throws Exception {
171         this.server.registerHandler("*", new BasicAsyncRequestHandler(new SimpleRequestHandler(true)));
172         this.server.start();
173         this.client.start();
174 
175         final ListenerEndpoint endpoint = this.server.getListenerEndpoint();
176         endpoint.waitFor();
177 
178         final String pattern = RndTestPatternGenerator.generateText();
179         final int count = RndTestPatternGenerator.generateCount(1000);
180 
181         final HttpHost target = new HttpHost("localhost", ((InetSocketAddress)endpoint.getAddress()).getPort());
182         final BasicHttpRequest request = new BasicHttpRequest("GET", pattern + "x" + count);
183         final Future<HttpResponse> future = this.client.execute(target, request);
184         try {
185             future.get();
186             Assert.fail("ExecutionException should have been thrown");
187         } catch (final ExecutionException ex) {
188             final Throwable cause = ex.getCause();
189             Assert.assertTrue(cause instanceof MalformedChunkCodingException);
190         }
191     }
192 
193     static class LenientAsyncResponseConsumer extends AbstractAsyncResponseConsumer<HttpResponse> {
194 
195         private final SimpleInputBuffer buffer;
196         private volatile HttpResponse response;
197 
198         public LenientAsyncResponseConsumer() {
199             super();
200             this.buffer = new SimpleInputBuffer(2048, HeapByteBufferAllocator.INSTANCE);
201         }
202 
203         @Override
204         protected void onResponseReceived(final HttpResponse response) {
205             this.response = response;
206         }
207 
208         @Override
209         protected void onEntityEnclosed(final HttpEntity entity, final ContentType contentType) {
210         }
211 
212         @Override
213         protected void onContentReceived(
214                 final ContentDecoder decoder, final IOControl ioctrl) throws IOException {
215             boolean finished = false;
216             try {
217                 this.buffer.consumeContent(decoder);
218                 if (decoder.isCompleted()) {
219                     finished = true;
220                 }
221             } catch (final TruncatedChunkException ex) {
222                 this.buffer.shutdown();
223                 finished = true;
224             }
225             if (finished) {
226                 this.response.setEntity(
227                         new InputStreamEntity(new ContentInputStream(this.buffer), -1));
228             }
229         }
230 
231         @Override
232         protected void releaseResources() {
233         }
234 
235         @Override
236         protected HttpResponse buildResult(final HttpContext context) {
237             return this.response;
238         }
239 
240     }
241 
242     @Test
243     public void testIgnoreTruncatedChunkException() throws Exception {
244         this.server.registerHandler("*", new BasicAsyncRequestHandler(new SimpleRequestHandler(true)));
245         this.server.start();
246         this.client.start();
247 
248         final ListenerEndpoint endpoint = this.server.getListenerEndpoint();
249         endpoint.waitFor();
250 
251         final String pattern = RndTestPatternGenerator.generateText();
252         final int count = RndTestPatternGenerator.generateCount(1000);
253 
254         final HttpHost target = new HttpHost("localhost", ((InetSocketAddress)endpoint.getAddress()).getPort());
255         final BasicHttpRequest request = new BasicHttpRequest("GET", pattern + "x" + count);
256         final Future<HttpResponse> future = this.client.execute(
257                 new BasicAsyncRequestProducer(target, request),
258                 new LenientAsyncResponseConsumer(),
259                 null, null);
260 
261         final HttpResponse response = future.get();
262         Assert.assertNotNull(response);
263         Assert.assertEquals(HttpStatus.SC_OK, response.getStatusLine().getStatusCode());
264         Assert.assertEquals(new String(GARBAGE, Consts.ISO_8859_1.name()),
265                 EntityUtils.toString(response.getEntity()));
266     }
267 
268 }