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.hc.core5.http.impl.nio;
29  
30  import java.io.File;
31  import java.io.IOException;
32  import java.io.RandomAccessFile;
33  import java.nio.ByteBuffer;
34  import java.nio.channels.FileChannel;
35  import java.nio.channels.ReadableByteChannel;
36  import java.nio.charset.StandardCharsets;
37  
38  import org.apache.hc.core5.http.ConnectionClosedException;
39  import org.apache.hc.core5.http.ReadableByteChannelMock;
40  import org.apache.hc.core5.http.impl.BasicHttpTransportMetrics;
41  import org.apache.hc.core5.http.nio.SessionInputBuffer;
42  import org.junit.After;
43  import org.junit.Assert;
44  import org.junit.Test;
45  
46  /**
47   * Simple tests for {@link LengthDelimitedDecoder}.
48   */
49  public class TestLengthDelimitedDecoder {
50  
51      private File tmpfile;
52  
53      protected File createTempFile() throws IOException {
54          this.tmpfile = File.createTempFile("testFile", ".txt");
55          return this.tmpfile;
56      }
57  
58      @After
59      public void deleteTempFile() {
60          if (this.tmpfile != null && this.tmpfile.exists()) {
61              this.tmpfile.delete();
62          }
63      }
64  
65      @Test
66      public void testBasicDecoding() throws Exception {
67          final ReadableByteChannel channel = new ReadableByteChannelMock(
68                  new String[] {"stuff;", "more stuff"}, StandardCharsets.US_ASCII);
69  
70          final SessionInputBuffer inbuf = new SessionInputBufferImpl(1024, 256, 0, StandardCharsets.US_ASCII);
71          final BasicHttpTransportMetrics metrics = new BasicHttpTransportMetrics();
72          final LengthDelimitedDecoder decoder = new LengthDelimitedDecoder(
73                  channel, inbuf, metrics, 16);
74  
75          final ByteBuffer dst = ByteBuffer.allocate(1024);
76  
77          int bytesRead = decoder.read(dst);
78          Assert.assertEquals(6, bytesRead);
79          Assert.assertEquals("stuff;", CodecTestUtils.convert(dst));
80          Assert.assertFalse(decoder.isCompleted());
81          Assert.assertEquals(6, metrics.getBytesTransferred());
82  
83          dst.clear();
84          bytesRead = decoder.read(dst);
85          Assert.assertEquals(10, bytesRead);
86          Assert.assertEquals("more stuff", CodecTestUtils.convert(dst));
87          Assert.assertTrue(decoder.isCompleted());
88          Assert.assertEquals(16, metrics.getBytesTransferred());
89  
90          dst.clear();
91          bytesRead = decoder.read(dst);
92          Assert.assertEquals(-1, bytesRead);
93          Assert.assertTrue(decoder.isCompleted());
94          Assert.assertEquals(16, metrics.getBytesTransferred());
95  
96          Assert.assertEquals("[content length: 16; pos: 16; completed: true]", decoder.toString());
97      }
98  
99      @Test
100     public void testCodingBeyondContentLimit() throws Exception {
101         final ReadableByteChannel channel = new ReadableByteChannelMock(
102                 new String[] {
103                         "stuff;",
104                         "more stuff; and a lot more stuff"}, StandardCharsets.US_ASCII);
105 
106         final SessionInputBuffer inbuf = new SessionInputBufferImpl(1024, 256, 0, StandardCharsets.US_ASCII);
107         final BasicHttpTransportMetrics metrics = new BasicHttpTransportMetrics();
108         final LengthDelimitedDecoder decoder = new LengthDelimitedDecoder(
109                 channel, inbuf, metrics, 16);
110 
111         final ByteBuffer dst = ByteBuffer.allocate(1024);
112 
113         int bytesRead = decoder.read(dst);
114         Assert.assertEquals(6, bytesRead);
115         Assert.assertEquals("stuff;", CodecTestUtils.convert(dst));
116         Assert.assertFalse(decoder.isCompleted());
117         Assert.assertEquals(6, metrics.getBytesTransferred());
118 
119         dst.clear();
120         bytesRead = decoder.read(dst);
121         Assert.assertEquals(10, bytesRead);
122         Assert.assertEquals("more stuff", CodecTestUtils.convert(dst));
123         Assert.assertTrue(decoder.isCompleted());
124         Assert.assertEquals(16, metrics.getBytesTransferred());
125 
126         dst.clear();
127         bytesRead = decoder.read(dst);
128         Assert.assertEquals(-1, bytesRead);
129         Assert.assertTrue(decoder.isCompleted());
130         Assert.assertEquals(16, metrics.getBytesTransferred());
131     }
132 
133     @Test
134     public void testBasicDecodingSmallBuffer() throws Exception {
135         final ReadableByteChannel channel = new ReadableByteChannelMock(
136                 new String[] {"stuff;", "more stuff"}, StandardCharsets.US_ASCII);
137 
138         final SessionInputBuffer inbuf = new SessionInputBufferImpl(1024, 256, 0, StandardCharsets.US_ASCII);
139         final BasicHttpTransportMetrics metrics = new BasicHttpTransportMetrics();
140         final LengthDelimitedDecoder decoder = new LengthDelimitedDecoder(
141                 channel, inbuf, metrics, 16);
142 
143         final ByteBuffer dst = ByteBuffer.allocate(4);
144 
145         int bytesRead = decoder.read(dst);
146         Assert.assertEquals(4, bytesRead);
147         Assert.assertEquals("stuf", CodecTestUtils.convert(dst));
148         Assert.assertFalse(decoder.isCompleted());
149         Assert.assertEquals(4, metrics.getBytesTransferred());
150 
151         dst.clear();
152         bytesRead = decoder.read(dst);
153         Assert.assertEquals(2, bytesRead);
154         Assert.assertEquals("f;", CodecTestUtils.convert(dst));
155         Assert.assertFalse(decoder.isCompleted());
156         Assert.assertEquals(6, metrics.getBytesTransferred());
157 
158         dst.clear();
159         bytesRead = decoder.read(dst);
160         Assert.assertEquals(4, bytesRead);
161         Assert.assertEquals("more", CodecTestUtils.convert(dst));
162         Assert.assertFalse(decoder.isCompleted());
163         Assert.assertEquals(10, metrics.getBytesTransferred());
164 
165         dst.clear();
166         bytesRead = decoder.read(dst);
167         Assert.assertEquals(4, bytesRead);
168         Assert.assertEquals(" stu", CodecTestUtils.convert(dst));
169         Assert.assertFalse(decoder.isCompleted());
170         Assert.assertEquals(14, metrics.getBytesTransferred());
171 
172         dst.clear();
173         bytesRead = decoder.read(dst);
174         Assert.assertEquals(2, bytesRead);
175         Assert.assertEquals("ff", CodecTestUtils.convert(dst));
176         Assert.assertTrue(decoder.isCompleted());
177         Assert.assertEquals(16, metrics.getBytesTransferred());
178 
179         dst.clear();
180         bytesRead = decoder.read(dst);
181         Assert.assertEquals(-1, bytesRead);
182         Assert.assertTrue(decoder.isCompleted());
183         Assert.assertEquals(16, metrics.getBytesTransferred());
184     }
185 
186     @Test
187     public void testDecodingFromSessionBuffer1() throws Exception {
188         final ReadableByteChannel channel = new ReadableByteChannelMock(
189                 new String[] {"stuff;", "more stuff"}, StandardCharsets.US_ASCII);
190 
191         final SessionInputBuffer inbuf = new SessionInputBufferImpl(1024, 256, 0, StandardCharsets.US_ASCII);
192         final BasicHttpTransportMetrics metrics = new BasicHttpTransportMetrics();
193 
194         inbuf.fill(channel);
195 
196         Assert.assertEquals(6, inbuf.length());
197 
198         final LengthDelimitedDecoder decoder = new LengthDelimitedDecoder(
199                 channel, inbuf, metrics, 16);
200 
201         final ByteBuffer dst = ByteBuffer.allocate(1024);
202 
203         int bytesRead = decoder.read(dst);
204         Assert.assertEquals(6, bytesRead);
205         Assert.assertEquals("stuff;", CodecTestUtils.convert(dst));
206         Assert.assertFalse(decoder.isCompleted());
207         Assert.assertEquals(0, metrics.getBytesTransferred());
208 
209         dst.clear();
210         bytesRead = decoder.read(dst);
211         Assert.assertEquals(10, bytesRead);
212         Assert.assertEquals("more stuff", CodecTestUtils.convert(dst));
213         Assert.assertTrue(decoder.isCompleted());
214         Assert.assertEquals(10, metrics.getBytesTransferred());
215 
216         dst.clear();
217         bytesRead = decoder.read(dst);
218         Assert.assertEquals(-1, bytesRead);
219         Assert.assertTrue(decoder.isCompleted());
220         Assert.assertEquals(10, metrics.getBytesTransferred());
221     }
222 
223     @Test
224     public void testDecodingFromSessionBuffer2() throws Exception {
225         final ReadableByteChannel channel = new ReadableByteChannelMock(
226                 new String[] {
227                         "stuff;",
228                         "more stuff; and a lot more stuff"}, StandardCharsets.US_ASCII);
229 
230         final SessionInputBuffer inbuf = new SessionInputBufferImpl(1024, 256, 0, StandardCharsets.US_ASCII);
231         final BasicHttpTransportMetrics metrics = new BasicHttpTransportMetrics();
232 
233         inbuf.fill(channel);
234         inbuf.fill(channel);
235 
236         Assert.assertEquals(38, inbuf.length());
237 
238         final LengthDelimitedDecoder decoder = new LengthDelimitedDecoder(
239                 channel, inbuf, metrics, 16);
240 
241         final ByteBuffer dst = ByteBuffer.allocate(1024);
242 
243         int bytesRead = decoder.read(dst);
244         Assert.assertEquals(16, bytesRead);
245         Assert.assertEquals("stuff;more stuff", CodecTestUtils.convert(dst));
246         Assert.assertTrue(decoder.isCompleted());
247         Assert.assertEquals(0, metrics.getBytesTransferred());
248 
249         dst.clear();
250         bytesRead = decoder.read(dst);
251         Assert.assertEquals(-1, bytesRead);
252         Assert.assertTrue(decoder.isCompleted());
253         Assert.assertEquals(0, metrics.getBytesTransferred());
254     }
255 
256     /* ----------------- FileChannel Part testing --------------------------- */
257     @Test
258     public void testBasicDecodingFile() throws Exception {
259         final ReadableByteChannel channel = new ReadableByteChannelMock(
260                 new String[] {"stuff; ", "more stuff; ", "a lot more stuff!!!"}, StandardCharsets.US_ASCII);
261 
262         final SessionInputBuffer inbuf = new SessionInputBufferImpl(1024, 256, 0, StandardCharsets.US_ASCII);
263         final BasicHttpTransportMetrics metrics = new BasicHttpTransportMetrics();
264         final LengthDelimitedDecoder decoder = new LengthDelimitedDecoder(
265                 channel, inbuf, metrics, 36);
266 
267         createTempFile();
268         final RandomAccessFile testfile = new RandomAccessFile(this.tmpfile, "rw");
269         try {
270             final FileChannel fchannel = testfile.getChannel();
271             long pos = 0;
272             while (!decoder.isCompleted()) {
273                 final long bytesRead = decoder.transfer(fchannel, pos, 10);
274                 if (bytesRead > 0) {
275                     pos += bytesRead;
276                 }
277             }
278         } finally {
279             testfile.close();
280         }
281         Assert.assertEquals(this.tmpfile.length(), metrics.getBytesTransferred());
282         Assert.assertEquals("stuff; more stuff; a lot more stuff!",
283             CodecTestUtils.readFromFile(this.tmpfile));
284     }
285 
286     @Test
287     public void testDecodingFileWithBufferedSessionData() throws Exception {
288         final ReadableByteChannel channel = new ReadableByteChannelMock(
289                 new String[] {"stuff; ", "more stuff; ", "a lot more stuff!!!"}, StandardCharsets.US_ASCII);
290 
291         final SessionInputBuffer inbuf = new SessionInputBufferImpl(1024, 256, 0, StandardCharsets.US_ASCII);
292         final BasicHttpTransportMetrics metrics = new BasicHttpTransportMetrics();
293         final LengthDelimitedDecoder decoder = new LengthDelimitedDecoder(
294                 channel, inbuf, metrics, 36);
295 
296         final int i = inbuf.fill(channel);
297         Assert.assertEquals(7, i);
298 
299         createTempFile();
300         final RandomAccessFile testfile = new RandomAccessFile(this.tmpfile, "rw");
301         try {
302             final FileChannel fchannel = testfile.getChannel();
303             long pos = 0;
304             while (!decoder.isCompleted()) {
305                 final long bytesRead = decoder.transfer(fchannel, pos, 10);
306                 if (bytesRead > 0) {
307                     pos += bytesRead;
308                 }
309             }
310         } finally {
311             testfile.close();
312         }
313         Assert.assertEquals(this.tmpfile.length() - 7, metrics.getBytesTransferred());
314         Assert.assertEquals("stuff; more stuff; a lot more stuff!",
315             CodecTestUtils.readFromFile(this.tmpfile));
316     }
317 
318     @Test
319     public void testDecodingFileWithOffsetAndBufferedSessionData() throws Exception {
320         final ReadableByteChannel channel = new ReadableByteChannelMock(
321                 new String[] {"stuff; ", "more stuff; ", "a lot more stuff!"}, StandardCharsets.US_ASCII);
322 
323         final SessionInputBuffer inbuf = new SessionInputBufferImpl(1024, 256, 0, StandardCharsets.US_ASCII);
324         final BasicHttpTransportMetrics metrics = new BasicHttpTransportMetrics();
325         final LengthDelimitedDecoder decoder = new LengthDelimitedDecoder(
326                 channel, inbuf, metrics, 36);
327 
328         final int i = inbuf.fill(channel);
329         Assert.assertEquals(7, i);
330 
331         final byte[] beginning =  "beginning; ".getBytes(StandardCharsets.US_ASCII);
332 
333         createTempFile();
334         RandomAccessFile testfile = new RandomAccessFile(this.tmpfile, "rw");
335         try {
336             testfile.write(beginning);
337         } finally {
338             testfile.close();
339         }
340 
341         testfile = new RandomAccessFile(this.tmpfile, "rw");
342         try {
343             final FileChannel fchannel = testfile.getChannel();
344 
345             long pos = beginning.length;
346             while (!decoder.isCompleted()) {
347                 if(testfile.length() < pos) {
348                     testfile.setLength(pos);
349                 }
350                 final long bytesRead = decoder.transfer(fchannel, pos, 10);
351                 if (bytesRead > 0) {
352                     pos += bytesRead;
353                 }
354             }
355         } finally {
356             testfile.close();
357         }
358 
359         // count everything except the initial 7 bytes that went to the session buffer
360         Assert.assertEquals(this.tmpfile.length() - 7 - beginning.length, metrics.getBytesTransferred());
361         Assert.assertEquals("beginning; stuff; more stuff; a lot more stuff!",
362             CodecTestUtils.readFromFile(this.tmpfile));
363     }
364 
365     @Test
366     public void testWriteBeyondFileSize() throws Exception {
367         final ReadableByteChannel channel = new ReadableByteChannelMock(
368                 new String[] {"a"}, StandardCharsets.US_ASCII);
369 
370         final SessionInputBuffer inbuf = new SessionInputBufferImpl(1024, 256, 0, StandardCharsets.US_ASCII);
371         final BasicHttpTransportMetrics metrics = new BasicHttpTransportMetrics();
372         final LengthDelimitedDecoder decoder = new LengthDelimitedDecoder(
373                 channel, inbuf, metrics, 1);
374 
375         createTempFile();
376         final RandomAccessFile testfile = new RandomAccessFile(this.tmpfile, "rw");
377         try {
378             final FileChannel fchannel = testfile.getChannel();
379             Assert.assertEquals(0, testfile.length());
380             try {
381                 decoder.transfer(fchannel, 5, 10);
382                 Assert.fail("IOException should have been thrown");
383             } catch(final IOException expected) {
384             }
385         } finally {
386             testfile.close();
387         }
388     }
389 
390     @Test
391     public void testCodingBeyondContentLimitFile() throws Exception {
392         final ReadableByteChannel channel = new ReadableByteChannelMock(
393                 new String[] {
394                         "stuff;",
395                         "more stuff; and a lot more stuff"}, StandardCharsets.US_ASCII);
396 
397         final SessionInputBuffer inbuf = new SessionInputBufferImpl(1024, 256, 0, StandardCharsets.US_ASCII);
398         final BasicHttpTransportMetrics metrics = new BasicHttpTransportMetrics();
399         final LengthDelimitedDecoder decoder = new LengthDelimitedDecoder(
400                 channel, inbuf, metrics, 16);
401 
402         createTempFile();
403         final RandomAccessFile testfile  = new RandomAccessFile(this.tmpfile, "rw");
404         try {
405             final FileChannel fchannel = testfile.getChannel();
406 
407             long bytesRead = decoder.transfer(fchannel, 0, 6);
408             Assert.assertEquals(6, bytesRead);
409             Assert.assertFalse(decoder.isCompleted());
410             Assert.assertEquals(6, metrics.getBytesTransferred());
411 
412             bytesRead = decoder.transfer(fchannel,0 , 10);
413             Assert.assertEquals(10, bytesRead);
414             Assert.assertTrue(decoder.isCompleted());
415             Assert.assertEquals(16, metrics.getBytesTransferred());
416 
417             bytesRead = decoder.transfer(fchannel, 0, 1);
418             Assert.assertEquals(-1, bytesRead);
419             Assert.assertTrue(decoder.isCompleted());
420             Assert.assertEquals(16, metrics.getBytesTransferred());
421         } finally {
422             testfile.close();
423         }
424     }
425 
426     @Test
427     public void testInvalidConstructor() {
428         final ReadableByteChannel channel = new ReadableByteChannelMock(
429                 new String[] {"stuff;", "more stuff"}, StandardCharsets.US_ASCII);
430 
431         final SessionInputBuffer inbuf = new SessionInputBufferImpl(1024, 256, 0, StandardCharsets.US_ASCII);
432         final BasicHttpTransportMetrics metrics = new BasicHttpTransportMetrics();
433         try {
434             new LengthDelimitedDecoder(null, null, null, 10);
435             Assert.fail("IllegalArgumentException should have been thrown");
436         } catch (final IllegalArgumentException ex) {
437             // ignore
438         }
439         try {
440             new LengthDelimitedDecoder(channel, null, null, 10);
441             Assert.fail("IllegalArgumentException should have been thrown");
442         } catch (final IllegalArgumentException ex) {
443             // ignore
444         }
445         try {
446             new LengthDelimitedDecoder(channel, inbuf, null, 10);
447             Assert.fail("IllegalArgumentException should have been thrown");
448         } catch (final IllegalArgumentException ex) {
449             // ignore
450         }
451         try {
452             new LengthDelimitedDecoder(channel, inbuf, metrics, -10);
453             Assert.fail("IllegalArgumentException should have been thrown");
454         } catch (final IllegalArgumentException ex) {
455             // ignore
456         }
457     }
458 
459     @Test
460     public void testInvalidInput() throws Exception {
461         final String s = "stuff";
462         final ReadableByteChannel channel = new ReadableByteChannelMock(
463                 new String[] {s}, StandardCharsets.US_ASCII);
464 
465         final SessionInputBuffer inbuf = new SessionInputBufferImpl(1024, 256, 0, StandardCharsets.US_ASCII);
466         final BasicHttpTransportMetrics metrics = new BasicHttpTransportMetrics();
467         final LengthDelimitedDecoder decoder = new LengthDelimitedDecoder(
468                 channel, inbuf, metrics, 3);
469 
470         try {
471             decoder.read(null);
472             Assert.fail("IllegalArgumentException should have been thrown");
473         } catch (final IllegalArgumentException ex) {
474             // expected
475         }
476     }
477 
478     @Test
479     public void testZeroLengthDecoding() throws Exception {
480         final ReadableByteChannel channel = new ReadableByteChannelMock(
481                 new String[] {"stuff"}, StandardCharsets.US_ASCII);
482 
483         final SessionInputBuffer inbuf = new SessionInputBufferImpl(1024, 256, 0, StandardCharsets.US_ASCII);
484         final BasicHttpTransportMetrics metrics = new BasicHttpTransportMetrics();
485         final LengthDelimitedDecoder decoder = new LengthDelimitedDecoder(
486                 channel, inbuf, metrics, 0);
487 
488         final ByteBuffer dst = ByteBuffer.allocate(1024);
489 
490         final int bytesRead = decoder.read(dst);
491         Assert.assertEquals(-1, bytesRead);
492         Assert.assertTrue(decoder.isCompleted());
493         Assert.assertEquals(0, metrics.getBytesTransferred());
494     }
495 
496     @Test(expected=ConnectionClosedException.class)
497     public void testTruncatedContent() throws Exception {
498         final ReadableByteChannel channel = new ReadableByteChannelMock(
499                 new String[] {"1234567890"}, StandardCharsets.US_ASCII);
500 
501         final SessionInputBuffer inbuf = new SessionInputBufferImpl(1024, 256, 0, StandardCharsets.US_ASCII);
502         final BasicHttpTransportMetrics metrics = new BasicHttpTransportMetrics();
503         final LengthDelimitedDecoder decoder = new LengthDelimitedDecoder(
504                 channel, inbuf, metrics, 20);
505 
506         final ByteBuffer dst = ByteBuffer.allocate(1024);
507 
508         final int bytesRead = decoder.read(dst);
509         Assert.assertEquals(10, bytesRead);
510         decoder.read(dst);
511     }
512 
513     @Test(expected=ConnectionClosedException.class)
514     public void testTruncatedContentWithFile() throws Exception {
515         final ReadableByteChannel channel = new ReadableByteChannelMock(
516                 new String[] {"1234567890"}, StandardCharsets.US_ASCII);
517 
518         final SessionInputBuffer inbuf = new SessionInputBufferImpl(1024, 256, 0, StandardCharsets.US_ASCII);
519         final BasicHttpTransportMetrics metrics = new BasicHttpTransportMetrics();
520         final LengthDelimitedDecoder decoder = new LengthDelimitedDecoder(
521                 channel, inbuf, metrics, 20);
522 
523         createTempFile();
524         final RandomAccessFile testfile  = new RandomAccessFile(this.tmpfile, "rw");
525         try {
526             final FileChannel fchannel = testfile.getChannel();
527             final long bytesRead = decoder.transfer(fchannel, 0, Integer.MAX_VALUE);
528             Assert.assertEquals(10, bytesRead);
529             decoder.transfer(fchannel, 0, Integer.MAX_VALUE);
530         } finally {
531             testfile.close();
532         }
533     }
534 
535 }