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  import java.util.concurrent.TimeUnit;
37  
38  import org.apache.http.Consts;
39  import org.apache.http.HttpEntity;
40  import org.apache.http.HttpHost;
41  import org.apache.http.HttpResponse;
42  import org.apache.http.HttpStatus;
43  import org.apache.http.MalformedChunkCodingException;
44  import org.apache.http.TruncatedChunkException;
45  import org.apache.http.entity.ContentLengthStrategy;
46  import org.apache.http.entity.ContentType;
47  import org.apache.http.entity.InputStreamEntity;
48  import org.apache.http.impl.io.HttpTransportMetricsImpl;
49  import org.apache.http.impl.nio.DefaultNHttpServerConnection;
50  import org.apache.http.impl.nio.codecs.AbstractContentEncoder;
51  import org.apache.http.message.BasicHttpRequest;
52  import org.apache.http.nio.ContentDecoder;
53  import org.apache.http.nio.ContentEncoder;
54  import org.apache.http.nio.IOControl;
55  import org.apache.http.nio.entity.ContentInputStream;
56  import org.apache.http.nio.protocol.AbstractAsyncResponseConsumer;
57  import org.apache.http.nio.protocol.BasicAsyncRequestHandler;
58  import org.apache.http.nio.protocol.BasicAsyncRequestProducer;
59  import org.apache.http.nio.reactor.IOSession;
60  import org.apache.http.nio.reactor.ListenerEndpoint;
61  import org.apache.http.nio.reactor.SessionOutputBuffer;
62  import org.apache.http.nio.testserver.HttpCoreNIOTestBase;
63  import org.apache.http.nio.testserver.LoggingNHttpServerConnection;
64  import org.apache.http.nio.testserver.ServerConnectionFactory;
65  import org.apache.http.nio.util.HeapByteBufferAllocator;
66  import org.apache.http.nio.util.SimpleInputBuffer;
67  import org.apache.http.protocol.HttpContext;
68  import org.apache.http.util.CharArrayBuffer;
69  import org.apache.http.util.EntityUtils;
70  import org.junit.After;
71  import org.junit.Assert;
72  import org.junit.Before;
73  import org.junit.Test;
74  
75  /**
76   * Tests for handling truncated chunks.
77   */
78  public class TestTruncatedChunks extends HttpCoreNIOTestBase {
79  
80      private final static long RESULT_TIMEOUT_SEC = 30;
81  
82      @Before
83      public void setUp() throws Exception {
84          initServer();
85          initClient();
86      }
87  
88      @After
89      public void tearDown() throws Exception {
90          shutDownClient();
91          shutDownServer();
92      }
93  
94      @Override
95      protected ServerConnectionFactory createServerConnectionFactory() throws Exception {
96          return new CustomServerConnectionFactory();
97      }
98  
99      private static final byte[] GARBAGE = new byte[] {'1', '2', '3', '4', '5' };
100 
101     static class BrokenChunkEncoder extends AbstractContentEncoder {
102 
103         private final CharArrayBuffer lineBuffer;
104         private boolean done;
105 
106         public BrokenChunkEncoder(
107                 final WritableByteChannel channel,
108                 final SessionOutputBuffer buffer,
109                 final HttpTransportMetricsImpl metrics) {
110             super(channel, buffer, metrics);
111             this.lineBuffer = new CharArrayBuffer(16);
112         }
113 
114         @Override
115         public void complete() throws IOException {
116             super.complete();
117         }
118 
119         @Override
120         public int write(final ByteBuffer src) throws IOException {
121             final int chunk;
122             if (!this.done) {
123                 this.lineBuffer.clear();
124                 this.lineBuffer.append(Integer.toHexString(GARBAGE.length * 10));
125                 this.buffer.writeLine(this.lineBuffer);
126                 this.buffer.write(ByteBuffer.wrap(GARBAGE));
127                 this.done = true;
128                 chunk = GARBAGE.length;
129             } else {
130                 chunk = 0;
131             }
132             final long bytesWritten = this.buffer.flush(this.channel);
133             if (bytesWritten > 0) {
134                 this.metrics.incrementBytesTransferred(bytesWritten);
135             }
136             if (!this.buffer.hasData()) {
137                 this.channel.close();
138             }
139             return chunk;
140         }
141 
142     }
143 
144     static class CustomServerConnectionFactory extends ServerConnectionFactory {
145 
146         public CustomServerConnectionFactory() {
147             super();
148         }
149 
150         @Override
151         public DefaultNHttpServerConnection createConnection(final IOSession session) {
152             return new LoggingNHttpServerConnection(session) {
153 
154                 @Override
155                 protected ContentEncoder createContentEncoder(
156                         final long len,
157                         final WritableByteChannel channel,
158                         final SessionOutputBuffer buffer,
159                         final HttpTransportMetricsImpl metrics) {
160                     if (len == ContentLengthStrategy.CHUNKED) {
161                         return new BrokenChunkEncoder(channel, buffer, metrics);
162                     } else {
163                         return super.createContentEncoder(len, channel, buffer, metrics);
164                     }
165                 }
166 
167             };
168         }
169 
170     }
171 
172     @Test
173     public void testTruncatedChunkException() throws Exception {
174         this.server.registerHandler("*", new BasicAsyncRequestHandler(new SimpleRequestHandler(true)));
175         this.server.start();
176         this.client.start();
177 
178         final ListenerEndpoint endpoint = this.server.getListenerEndpoint();
179         endpoint.waitFor();
180 
181         final String pattern = RndTestPatternGenerator.generateText();
182         final int count = RndTestPatternGenerator.generateCount(1000);
183 
184         final HttpHost target = new HttpHost("localhost", ((InetSocketAddress)endpoint.getAddress()).getPort());
185         final BasicHttpRequest request = new BasicHttpRequest("GET", pattern + "x" + count);
186         final Future<HttpResponse> future = this.client.execute(target, request);
187         try {
188             future.get(RESULT_TIMEOUT_SEC, TimeUnit.SECONDS);
189             Assert.fail("ExecutionException should have been thrown");
190         } catch (final ExecutionException ex) {
191             final Throwable cause = ex.getCause();
192             Assert.assertTrue(cause instanceof MalformedChunkCodingException);
193         }
194     }
195 
196     static class LenientAsyncResponseConsumer extends AbstractAsyncResponseConsumer<HttpResponse> {
197 
198         private final SimpleInputBuffer buffer;
199         private volatile HttpResponse response;
200 
201         public LenientAsyncResponseConsumer() {
202             super();
203             this.buffer = new SimpleInputBuffer(2048, HeapByteBufferAllocator.INSTANCE);
204         }
205 
206         @Override
207         protected void onResponseReceived(final HttpResponse response) {
208             this.response = response;
209         }
210 
211         @Override
212         protected void onEntityEnclosed(final HttpEntity entity, final ContentType contentType) {
213         }
214 
215         @Override
216         protected void onContentReceived(
217                 final ContentDecoder decoder, final IOControl ioControl) throws IOException {
218             boolean finished = false;
219             try {
220                 this.buffer.consumeContent(decoder);
221                 if (decoder.isCompleted()) {
222                     finished = true;
223                 }
224             } catch (final TruncatedChunkException ex) {
225                 this.buffer.shutdown();
226                 finished = true;
227             }
228             if (finished) {
229                 this.response.setEntity(
230                         new InputStreamEntity(new ContentInputStream(this.buffer), -1));
231             }
232         }
233 
234         @Override
235         protected void releaseResources() {
236         }
237 
238         @Override
239         protected HttpResponse buildResult(final HttpContext context) {
240             return this.response;
241         }
242 
243     }
244 
245     @Test
246     public void testIgnoreTruncatedChunkException() throws Exception {
247         this.server.registerHandler("*", new BasicAsyncRequestHandler(new SimpleRequestHandler(true)));
248         this.server.start();
249         this.client.start();
250 
251         final ListenerEndpoint endpoint = this.server.getListenerEndpoint();
252         endpoint.waitFor();
253 
254         final String pattern = RndTestPatternGenerator.generateText();
255         final int count = RndTestPatternGenerator.generateCount(1000);
256 
257         final HttpHost target = new HttpHost("localhost", ((InetSocketAddress)endpoint.getAddress()).getPort());
258         final BasicHttpRequest request = new BasicHttpRequest("GET", pattern + "x" + count);
259         final Future<HttpResponse> future = this.client.execute(
260                 new BasicAsyncRequestProducer(target, request),
261                 new LenientAsyncResponseConsumer(),
262                 null, null);
263 
264         final HttpResponse response = future.get(RESULT_TIMEOUT_SEC, TimeUnit.SECONDS);
265         Assert.assertNotNull(response);
266         Assert.assertEquals(HttpStatus.SC_OK, response.getStatusLine().getStatusCode());
267         Assert.assertEquals(new String(GARBAGE, Consts.ISO_8859_1.name()),
268                 EntityUtils.toString(response.getEntity()));
269     }
270 
271 }