1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
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
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 }