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.ReadableByteChannelMock;
39  import org.apache.hc.core5.http.impl.BasicHttpTransportMetrics;
40  import org.apache.hc.core5.http.nio.SessionInputBuffer;
41  import org.junit.jupiter.api.AfterEach;
42  import org.junit.jupiter.api.Assertions;
43  import org.junit.jupiter.api.Test;
44  
45  /**
46   * Simple tests for {@link LengthDelimitedDecoder}.
47   */
48  public class TestIdentityDecoder {
49  
50      private File tmpfile;
51  
52      protected File createTempFile() throws IOException {
53          this.tmpfile = File.createTempFile("testFile", ".txt");
54          return this.tmpfile;
55      }
56  
57      @AfterEach
58      public void deleteTempFile() {
59          if (this.tmpfile != null && this.tmpfile.exists()) {
60              this.tmpfile.delete();
61          }
62      }
63  
64      @Test
65      public void testBasicDecoding() throws Exception {
66          final ReadableByteChannel channel = new ReadableByteChannelMock(
67                  new String[] {"stuff;", "more stuff"}, StandardCharsets.US_ASCII);
68  
69          final SessionInputBuffer inbuf = new SessionInputBufferImpl(1024, 256, 0, StandardCharsets.US_ASCII);
70          final BasicHttpTransportMetrics metrics = new BasicHttpTransportMetrics();
71          final IdentityDecoder decoder = new IdentityDecoder(channel, inbuf, metrics);
72  
73          final ByteBuffer dst = ByteBuffer.allocate(1024);
74  
75          int bytesRead = decoder.read(dst);
76          Assertions.assertEquals(6, bytesRead);
77          Assertions.assertEquals("stuff;", CodecTestUtils.convert(dst));
78          Assertions.assertFalse(decoder.isCompleted());
79          Assertions.assertEquals(6, metrics.getBytesTransferred());
80  
81          dst.clear();
82          bytesRead = decoder.read(dst);
83          Assertions.assertEquals(10, bytesRead);
84          Assertions.assertEquals("more stuff", CodecTestUtils.convert(dst));
85          Assertions.assertFalse(decoder.isCompleted());
86          Assertions.assertEquals(16, metrics.getBytesTransferred());
87  
88          dst.clear();
89          bytesRead = decoder.read(dst);
90          Assertions.assertEquals(-1, bytesRead);
91          Assertions.assertTrue(decoder.isCompleted());
92          Assertions.assertEquals(16, metrics.getBytesTransferred());
93  
94          dst.clear();
95          bytesRead = decoder.read(dst);
96          Assertions.assertEquals(-1, bytesRead);
97          Assertions.assertTrue(decoder.isCompleted());
98          Assertions.assertEquals(16, metrics.getBytesTransferred());
99  
100         Assertions.assertEquals("[identity; completed: true]", decoder.toString());
101     }
102 
103     @Test
104     public void testDecodingFromSessionBuffer() throws Exception {
105         final ReadableByteChannel channel = new ReadableByteChannelMock(
106                 new String[] {"stuff;", "more stuff"}, StandardCharsets.US_ASCII);
107 
108         final SessionInputBuffer inbuf = new SessionInputBufferImpl(1024, 256, 0, StandardCharsets.US_ASCII);
109         final BasicHttpTransportMetrics metrics = new BasicHttpTransportMetrics();
110 
111         inbuf.fill(channel);
112 
113         Assertions.assertEquals(6, inbuf.length());
114 
115         final IdentityDecoder decoder = new IdentityDecoder(channel, inbuf, metrics);
116 
117         final ByteBuffer dst = ByteBuffer.allocate(1024);
118 
119         int bytesRead = decoder.read(dst);
120         Assertions.assertEquals(6, bytesRead);
121         Assertions.assertEquals("stuff;", CodecTestUtils.convert(dst));
122         Assertions.assertFalse(decoder.isCompleted());
123         Assertions.assertEquals(0, metrics.getBytesTransferred()); // doesn't count if from session buffer
124 
125         dst.clear();
126         bytesRead = decoder.read(dst);
127         Assertions.assertEquals(10, bytesRead);
128         Assertions.assertEquals("more stuff", CodecTestUtils.convert(dst));
129         Assertions.assertFalse(decoder.isCompleted());
130         Assertions.assertEquals(10, metrics.getBytesTransferred());
131 
132         dst.clear();
133         bytesRead = decoder.read(dst);
134         Assertions.assertEquals(-1, bytesRead);
135         Assertions.assertTrue(decoder.isCompleted());
136         Assertions.assertEquals(10, metrics.getBytesTransferred());
137 
138         dst.clear();
139         bytesRead = decoder.read(dst);
140         Assertions.assertEquals(-1, bytesRead);
141         Assertions.assertTrue(decoder.isCompleted());
142         Assertions.assertEquals(10, metrics.getBytesTransferred());
143 
144     }
145 
146     @Test
147     public void testBasicDecodingFile() throws Exception {
148         final ReadableByteChannel channel = new ReadableByteChannelMock(
149                 new String[] {"stuff; ", "more stuff; ", "a lot more stuff!"}, StandardCharsets.US_ASCII);
150 
151         final SessionInputBuffer inbuf = new SessionInputBufferImpl(1024, 256, 0, StandardCharsets.US_ASCII);
152         final BasicHttpTransportMetrics metrics = new BasicHttpTransportMetrics();
153         final IdentityDecoder decoder = new IdentityDecoder(
154                 channel, inbuf, metrics);
155 
156         createTempFile();
157         try (RandomAccessFile testfile = new RandomAccessFile(this.tmpfile, "rw")) {
158             final FileChannel fchannel = testfile.getChannel();
159             long pos = 0;
160             while (!decoder.isCompleted()) {
161                 final long bytesRead = decoder.transfer(fchannel, pos, 10);
162                 if (bytesRead > 0) {
163                     pos += bytesRead;
164                 }
165             }
166 
167             Assertions.assertEquals(testfile.length(), metrics.getBytesTransferred());
168         }
169         Assertions.assertEquals("stuff; more stuff; a lot more stuff!",
170             CodecTestUtils.readFromFile(this.tmpfile));
171     }
172 
173     @Test
174     public void testDecodingFileWithBufferedSessionData() throws Exception {
175         final ReadableByteChannel channel = new ReadableByteChannelMock(
176                 new String[] {"stuff; ", "more stuff; ", "a lot more stuff!"}, StandardCharsets.US_ASCII);
177 
178         final SessionInputBuffer inbuf = new SessionInputBufferImpl(1024, 256, 0, StandardCharsets.US_ASCII);
179         final BasicHttpTransportMetrics metrics = new BasicHttpTransportMetrics();
180         final IdentityDecoder decoder = new IdentityDecoder(
181                 channel, inbuf, metrics);
182 
183         final int i = inbuf.fill(channel);
184         Assertions.assertEquals(7, i);
185 
186         createTempFile();
187         try (RandomAccessFile testfile = new RandomAccessFile(this.tmpfile, "rw")) {
188             final FileChannel fchannel = testfile.getChannel();
189             long pos = 0;
190             while (!decoder.isCompleted()) {
191                 final long bytesRead = decoder.transfer(fchannel, pos, 10);
192                 if (bytesRead > 0) {
193                     pos += bytesRead;
194                 }
195             }
196 
197             // count everything except the initial 7 bytes that went to the session buffer
198             Assertions.assertEquals(testfile.length() - 7, metrics.getBytesTransferred());
199         }
200         Assertions.assertEquals("stuff; more stuff; a lot more stuff!",
201             CodecTestUtils.readFromFile(this.tmpfile));
202     }
203 
204     @Test
205     public void testDecodingFileWithOffsetAndBufferedSessionData() throws Exception {
206         final ReadableByteChannel channel = new ReadableByteChannelMock(
207                 new String[] {"stuff; ", "more stuff; ", "a lot more stuff!"}, StandardCharsets.US_ASCII);
208 
209         final SessionInputBuffer inbuf = new SessionInputBufferImpl(1024, 256, 0, StandardCharsets.US_ASCII);
210         final BasicHttpTransportMetrics metrics = new BasicHttpTransportMetrics();
211         final IdentityDecoder decoder = new IdentityDecoder(
212                 channel, inbuf, metrics);
213 
214         final int i = inbuf.fill(channel);
215         Assertions.assertEquals(7, i);
216 
217         final byte[] beginning = "beginning; ".getBytes(StandardCharsets.US_ASCII);
218 
219         createTempFile();
220         RandomAccessFile testfile = new RandomAccessFile(this.tmpfile, "rw");
221         try {
222             testfile.write(beginning);
223         } finally {
224             testfile.close();
225         }
226 
227         testfile = new RandomAccessFile(this.tmpfile, "rw");
228         try {
229             final FileChannel fchannel = testfile.getChannel();
230             long pos = beginning.length;
231             while (!decoder.isCompleted()) {
232                 if(testfile.length() < pos) {
233                     testfile.setLength(pos);
234                 }
235                 final long bytesRead = decoder.transfer(fchannel, pos, 10);
236                 if (bytesRead > 0) {
237                     pos += bytesRead;
238                 }
239             }
240 
241             // count everything except the initial 7 bytes that went to the session buffer
242             Assertions.assertEquals(testfile.length() - 7 - beginning.length, metrics.getBytesTransferred());
243         } finally {
244             testfile.close();
245         }
246 
247         Assertions.assertEquals("beginning; stuff; more stuff; a lot more stuff!",
248             CodecTestUtils.readFromFile(this.tmpfile));
249     }
250 
251     @Test
252     public void testDecodingFileWithLimit() throws Exception {
253         final ReadableByteChannel channel = new ReadableByteChannelMock(
254                 new String[] {"stuff; more stuff; ", "a lot more stuff!"}, StandardCharsets.US_ASCII);
255 
256         final SessionInputBuffer inbuf = new SessionInputBufferImpl(1024, 256, 0, StandardCharsets.US_ASCII);
257         final BasicHttpTransportMetrics metrics = new BasicHttpTransportMetrics();
258         final IdentityDecoder decoder = new IdentityDecoder(channel, inbuf, metrics);
259 
260         final int i = inbuf.fill(channel);
261         Assertions.assertEquals(19, i);
262 
263         createTempFile();
264         try (RandomAccessFile testfile = new RandomAccessFile(this.tmpfile, "rw")) {
265             final FileChannel fchannel = testfile.getChannel();
266             long pos = 0;
267 
268             // transferred from buffer
269             long bytesRead = decoder.transfer(fchannel, pos, 1);
270             Assertions.assertEquals(1, bytesRead);
271             Assertions.assertFalse(decoder.isCompleted());
272             Assertions.assertEquals(0, metrics.getBytesTransferred());
273             pos += bytesRead;
274 
275             bytesRead = decoder.transfer(fchannel, pos, 2);
276             Assertions.assertEquals(2, bytesRead);
277             Assertions.assertFalse(decoder.isCompleted());
278             Assertions.assertEquals(0, metrics.getBytesTransferred());
279             pos += bytesRead;
280 
281             bytesRead = decoder.transfer(fchannel, pos, 17);
282             Assertions.assertEquals(16, bytesRead);
283             Assertions.assertFalse(decoder.isCompleted());
284             Assertions.assertEquals(0, metrics.getBytesTransferred());
285             pos += bytesRead;
286 
287             // transferred from channel
288             bytesRead = decoder.transfer(fchannel, pos, 1);
289             Assertions.assertEquals(1, bytesRead);
290             Assertions.assertFalse(decoder.isCompleted());
291             Assertions.assertEquals(1, metrics.getBytesTransferred());
292             pos += bytesRead;
293 
294             bytesRead = decoder.transfer(fchannel, pos, 2);
295             Assertions.assertEquals(2, bytesRead);
296             Assertions.assertFalse(decoder.isCompleted());
297             Assertions.assertEquals(3, metrics.getBytesTransferred());
298             pos += bytesRead;
299 
300             bytesRead = decoder.transfer(fchannel, pos, 15);
301             Assertions.assertEquals(14, bytesRead);
302             Assertions.assertFalse(decoder.isCompleted());
303             Assertions.assertEquals(17, metrics.getBytesTransferred());
304             pos += bytesRead;
305 
306             bytesRead = decoder.transfer(fchannel, pos, 1);
307             Assertions.assertEquals(-1, bytesRead);
308             Assertions.assertTrue(decoder.isCompleted());
309             Assertions.assertEquals(17, metrics.getBytesTransferred());
310         }
311         Assertions.assertEquals("stuff; more stuff; a lot more stuff!",
312                 CodecTestUtils.readFromFile(this.tmpfile));
313     }
314 
315     @Test
316     public void testWriteBeyondFileSize() throws Exception {
317         final ReadableByteChannel channel = new ReadableByteChannelMock(
318                 new String[] {"a"}, StandardCharsets.US_ASCII);
319 
320         final SessionInputBuffer inbuf = new SessionInputBufferImpl(1024, 256, 0, StandardCharsets.US_ASCII);
321         final BasicHttpTransportMetrics metrics = new BasicHttpTransportMetrics();
322         final IdentityDecoder decoder = new IdentityDecoder(
323                 channel, inbuf, metrics);
324 
325         createTempFile();
326         try (RandomAccessFile testfile = new RandomAccessFile(this.tmpfile, "rw")) {
327             Assertions.assertEquals(0, testfile.length());
328             final FileChannel fchannel = testfile.getChannel();
329             Assertions.assertThrows(IOException.class, () -> decoder.transfer(fchannel, 5, 10));
330         }
331     }
332 
333     @Test
334     public void testInvalidConstructor() {
335         final ReadableByteChannel channel = new ReadableByteChannelMock(
336                 new String[] {"stuff;", "more stuff"}, StandardCharsets.US_ASCII);
337 
338         final SessionInputBuffer inbuf = new SessionInputBufferImpl(1024, 256, 0, StandardCharsets.US_ASCII);
339         Assertions.assertThrows(NullPointerException.class, () -> new IdentityDecoder(null, null, null));
340         Assertions.assertThrows(NullPointerException.class, () -> new IdentityDecoder(channel, null, null));
341         Assertions.assertThrows(NullPointerException.class, () -> new IdentityDecoder(channel, inbuf, null));
342     }
343 
344     @Test
345     public void testInvalidInput() throws Exception {
346         final String s = "stuff";
347         final ReadableByteChannel channel = new ReadableByteChannelMock(
348                 new String[] {s}, StandardCharsets.US_ASCII);
349 
350         final SessionInputBuffer inbuf = new SessionInputBufferImpl(1024, 256, 0, StandardCharsets.US_ASCII);
351         final BasicHttpTransportMetrics metrics = new BasicHttpTransportMetrics();
352         final IdentityDecoder decoder = new IdentityDecoder(channel, inbuf, metrics);
353 
354         Assertions.assertThrows(NullPointerException.class, () -> decoder.read(null));
355     }
356 
357 }