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  package org.apache.hc.core5.http.impl.io;
28  
29  import java.io.ByteArrayInputStream;
30  import java.io.ByteArrayOutputStream;
31  import java.io.InputStream;
32  import java.net.Socket;
33  import java.nio.charset.StandardCharsets;
34  
35  import org.apache.hc.core5.http.ClassicHttpRequest;
36  import org.apache.hc.core5.http.ClassicHttpResponse;
37  import org.apache.hc.core5.http.ContentType;
38  import org.apache.hc.core5.http.HttpEntity;
39  import org.apache.hc.core5.http.LengthRequiredException;
40  import org.apache.hc.core5.http.Method;
41  import org.apache.hc.core5.http.NotImplementedException;
42  import org.apache.hc.core5.http.config.Http1Config;
43  import org.apache.hc.core5.http.impl.DefaultContentLengthStrategy;
44  import org.apache.hc.core5.http.io.entity.ByteArrayEntity;
45  import org.apache.hc.core5.http.io.entity.StringEntity;
46  import org.apache.hc.core5.http.message.BasicClassicHttpRequest;
47  import org.junit.jupiter.api.Assertions;
48  import org.junit.jupiter.api.BeforeEach;
49  import org.junit.jupiter.api.Test;
50  import org.mockito.Mock;
51  import org.mockito.Mockito;
52  import org.mockito.MockitoAnnotations;
53  
54  public class TestDefaultBHttpClientConnection {
55  
56      @Mock
57      private Socket socket;
58  
59      private DefaultBHttpClientConnection conn;
60  
61      @BeforeEach
62      public void prepareMocks() {
63          MockitoAnnotations.openMocks(this);
64          conn = new DefaultBHttpClientConnection(Http1Config.DEFAULT,
65                  null, null,
66                  DefaultContentLengthStrategy.INSTANCE,
67                  DefaultContentLengthStrategy.INSTANCE,
68                  DefaultHttpRequestWriterFactory.INSTANCE,
69                  DefaultHttpResponseParserFactory.INSTANCE);
70      }
71  
72      @Test
73      public void testBasics() throws Exception {
74          Assertions.assertFalse(conn.isOpen());
75          Assertions.assertEquals("[Not bound]", conn.toString());
76      }
77  
78      @Test
79      public void testReadResponseHead() throws Exception {
80          final String s = "HTTP/1.1 200 OK\r\nUser-Agent: test\r\n\r\n";
81          final ByteArrayInputStream inStream = new ByteArrayInputStream(s.getBytes(StandardCharsets.US_ASCII));
82          Mockito.when(socket.getInputStream()).thenReturn(inStream);
83  
84          conn.bind(socket);
85  
86          Assertions.assertEquals(0, conn.getEndpointDetails().getResponseCount());
87  
88          final ClassicHttpResponse response = conn.receiveResponseHeader();
89          Assertions.assertNotNull(response);
90          Assertions.assertEquals(200, response.getCode());
91          Assertions.assertTrue(response.containsHeader("User-Agent"));
92          Assertions.assertEquals(1, conn.getEndpointDetails().getResponseCount());
93      }
94  
95      @Test
96      public void testReadResponseEntityWithoutContentLength() throws Exception {
97          final String s = "HTTP/1.1 200 OK\r\nServer: test\r\n\r\n123";
98          final ByteArrayInputStream inStream = new ByteArrayInputStream(s.getBytes(StandardCharsets.US_ASCII));
99          Mockito.when(socket.getInputStream()).thenReturn(inStream);
100 
101         conn.bind(socket);
102 
103         Assertions.assertEquals(0, conn.getEndpointDetails().getResponseCount());
104 
105         final ClassicHttpResponse response = conn.receiveResponseHeader();
106 
107         Assertions.assertNotNull(response);
108         Assertions.assertEquals(200, response.getCode());
109         Assertions.assertTrue(response.containsHeader("Server"));
110         Assertions.assertEquals(1, conn.getEndpointDetails().getResponseCount());
111 
112         conn.receiveResponseEntity(response);
113 
114         final HttpEntity entity = response.getEntity();
115         Assertions.assertNotNull(entity);
116         Assertions.assertEquals(-1, entity.getContentLength());
117         Assertions.assertEquals(1, conn.getEndpointDetails().getResponseCount());
118 
119         final InputStream content = entity.getContent();
120         Assertions.assertNotNull(content);
121         Assertions.assertEquals(3, content.available());
122         Assertions.assertEquals('1', content.read());
123         Assertions.assertEquals('2', content.read());
124         Assertions.assertEquals('3', content.read());
125     }
126 
127     @Test
128     public void testReadResponseEntityWithContentLength() throws Exception {
129         final String s = "HTTP/1.1 200 OK\r\nServer: test\r\nContent-Length: 3\r\n\r\n123";
130         final ByteArrayInputStream inStream = new ByteArrayInputStream(s.getBytes(StandardCharsets.US_ASCII));
131         Mockito.when(socket.getInputStream()).thenReturn(inStream);
132 
133         conn.bind(socket);
134 
135         Assertions.assertEquals(0, conn.getEndpointDetails().getResponseCount());
136 
137         final ClassicHttpResponse response = conn.receiveResponseHeader();
138 
139         Assertions.assertNotNull(response);
140         Assertions.assertEquals(200, response.getCode());
141         Assertions.assertTrue(response.containsHeader("Server"));
142         Assertions.assertEquals(1, conn.getEndpointDetails().getResponseCount());
143 
144         conn.receiveResponseEntity(response);
145 
146         final HttpEntity entity = response.getEntity();
147         Assertions.assertNotNull(entity);
148         Assertions.assertEquals(3, entity.getContentLength());
149         Assertions.assertEquals(1, conn.getEndpointDetails().getResponseCount());
150         final InputStream content = entity.getContent();
151         Assertions.assertNotNull(content);
152         Assertions.assertTrue(content instanceof ContentLengthInputStream);
153     }
154 
155     @Test
156     public void testReadResponseEntityChunkCoded() throws Exception {
157         final String s = "HTTP/1.1 200 OK\r\nServer: test\r\nTransfer-Encoding: " +
158                 "chunked\r\n\r\n3\r\n123\r\n0\r\n\r\n";
159         final ByteArrayInputStream inStream = new ByteArrayInputStream(s.getBytes(StandardCharsets.US_ASCII));
160         Mockito.when(socket.getInputStream()).thenReturn(inStream);
161 
162         conn.bind(socket);
163 
164         Assertions.assertEquals(0, conn.getEndpointDetails().getResponseCount());
165 
166         final ClassicHttpResponse response = conn.receiveResponseHeader();
167 
168         Assertions.assertNotNull(response);
169         Assertions.assertEquals(200, response.getCode());
170         Assertions.assertTrue(response.containsHeader("Server"));
171         Assertions.assertEquals(1, conn.getEndpointDetails().getResponseCount());
172 
173         conn.receiveResponseEntity(response);
174 
175         final HttpEntity entity = response.getEntity();
176         Assertions.assertNotNull(entity);
177         Assertions.assertEquals(-1, entity.getContentLength());
178         Assertions.assertTrue(entity.isChunked());
179         Assertions.assertEquals(1, conn.getEndpointDetails().getResponseCount());
180         final InputStream content = entity.getContent();
181         Assertions.assertNotNull(content);
182         Assertions.assertTrue(content instanceof ChunkedInputStream);
183     }
184 
185     @Test
186     public void testReadResponseEntityIdentity() throws Exception {
187         final String s = "HTTP/1.1 200 OK\r\nServer: test\r\nTransfer-Encoding: identity\r\n\r\n123";
188         final ByteArrayInputStream inStream = new ByteArrayInputStream(s.getBytes(StandardCharsets.US_ASCII));
189         Mockito.when(socket.getInputStream()).thenReturn(inStream);
190 
191         conn.bind(socket);
192 
193         Assertions.assertEquals(0, conn.getEndpointDetails().getResponseCount());
194 
195         final ClassicHttpResponse response = conn.receiveResponseHeader();
196 
197         Assertions.assertNotNull(response);
198         Assertions.assertEquals(200, response.getCode());
199         Assertions.assertTrue(response.containsHeader("Server"));
200         Assertions.assertEquals(1, conn.getEndpointDetails().getResponseCount());
201 
202         Assertions.assertThrows(NotImplementedException.class, () ->
203                 conn.receiveResponseEntity(response));
204     }
205 
206     @Test
207     public void testReadResponseNoEntity() throws Exception {
208         final String s = "HTTP/1.1 200 OK\r\nServer: test\r\n\r\n";
209         final ByteArrayInputStream inStream = new ByteArrayInputStream(s.getBytes(StandardCharsets.US_ASCII));
210         Mockito.when(socket.getInputStream()).thenReturn(inStream);
211 
212         conn.bind(socket);
213 
214         Assertions.assertEquals(0, conn.getEndpointDetails().getResponseCount());
215 
216         final ClassicHttpResponse response = conn.receiveResponseHeader();
217 
218         Assertions.assertNotNull(response);
219         Assertions.assertEquals(200, response.getCode());
220         Assertions.assertTrue(response.containsHeader("Server"));
221         Assertions.assertEquals(1, conn.getEndpointDetails().getResponseCount());
222 
223         conn.receiveResponseEntity(response);
224 
225         final HttpEntity entity = response.getEntity();
226         Assertions.assertNotNull(entity);
227         final InputStream content = entity.getContent();
228         Assertions.assertNotNull(content);
229         Assertions.assertTrue(content instanceof IdentityInputStream);
230     }
231 
232     @Test
233     public void testWriteRequestHead() throws Exception {
234         final ByteArrayOutputStream outStream = new ByteArrayOutputStream();
235         Mockito.when(socket.getOutputStream()).thenReturn(outStream);
236 
237         conn.bind(socket);
238 
239         Assertions.assertEquals(0, conn.getEndpointDetails().getRequestCount());
240 
241         final ClassicHttpRequest request = new BasicClassicHttpRequest(Method.GET, "/stuff");
242         request.addHeader("User-Agent", "test");
243 
244         conn.sendRequestHeader(request);
245         conn.flush();
246 
247         Assertions.assertEquals(1, conn.getEndpointDetails().getRequestCount());
248         final String s = new String(outStream.toByteArray(), StandardCharsets.US_ASCII);
249         Assertions.assertEquals("GET /stuff HTTP/1.1\r\nUser-Agent: test\r\n\r\n", s);
250     }
251 
252     @Test
253     public void testWriteRequestEntityWithContentLength() throws Exception {
254         final ByteArrayOutputStream outStream = new ByteArrayOutputStream();
255         Mockito.when(socket.getOutputStream()).thenReturn(outStream);
256 
257         conn.bind(socket);
258 
259         Assertions.assertEquals(0, conn.getEndpointDetails().getRequestCount());
260 
261         final ClassicHttpRequest request = new BasicClassicHttpRequest(Method.POST, "/stuff");
262         request.addHeader("User-Agent", "test");
263         request.addHeader("Content-Length", "3");
264         request.setEntity(new StringEntity("123", ContentType.TEXT_PLAIN));
265 
266         conn.sendRequestHeader(request);
267         conn.sendRequestEntity(request);
268         conn.flush();
269 
270         Assertions.assertEquals(1, conn.getEndpointDetails().getRequestCount());
271         final String s = new String(outStream.toByteArray(), StandardCharsets.US_ASCII);
272         Assertions.assertEquals("POST /stuff HTTP/1.1\r\nUser-Agent: test\r\nContent-Length: 3\r\n\r\n123", s);
273     }
274 
275     @Test
276     public void testWriteRequestEntityChunkCoded() throws Exception {
277         final ByteArrayOutputStream outStream = new ByteArrayOutputStream();
278         Mockito.when(socket.getOutputStream()).thenReturn(outStream);
279 
280         conn.bind(socket);
281 
282         Assertions.assertEquals(0, conn.getEndpointDetails().getRequestCount());
283 
284         final ClassicHttpRequest request = new BasicClassicHttpRequest(Method.POST, "/stuff");
285         request.addHeader("User-Agent", "test");
286         request.addHeader("Transfer-Encoding", "chunked");
287         request.setEntity(new StringEntity("123", ContentType.TEXT_PLAIN));
288 
289         conn.sendRequestHeader(request);
290         conn.sendRequestEntity(request);
291         conn.flush();
292 
293         Assertions.assertEquals(1, conn.getEndpointDetails().getRequestCount());
294         final String s = new String(outStream.toByteArray(), StandardCharsets.US_ASCII);
295         Assertions.assertEquals("POST /stuff HTTP/1.1\r\nUser-Agent: test\r\nTransfer-Encoding: " +
296                 "chunked\r\n\r\n3\r\n123\r\n0\r\n\r\n", s);
297     }
298 
299     @Test
300     public void testWriteRequestEntityNoContentLength() throws Exception {
301         final ByteArrayOutputStream outStream = new ByteArrayOutputStream();
302         Mockito.when(socket.getOutputStream()).thenReturn(outStream);
303 
304         conn.bind(socket);
305 
306         Assertions.assertEquals(0, conn.getEndpointDetails().getRequestCount());
307 
308         final ClassicHttpRequest request = new BasicClassicHttpRequest(Method.POST, "/stuff");
309         request.addHeader("User-Agent", "test");
310         request.setEntity(new StringEntity("123", ContentType.TEXT_PLAIN));
311 
312         conn.sendRequestHeader(request);
313         Assertions.assertThrows(LengthRequiredException.class, () ->
314                 conn.sendRequestEntity(request));
315     }
316 
317     @Test
318     public void testWriteRequestNoEntity() throws Exception {
319         final ByteArrayOutputStream outStream = new ByteArrayOutputStream();
320         Mockito.when(socket.getOutputStream()).thenReturn(outStream);
321 
322         conn.bind(socket);
323 
324         Assertions.assertEquals(0, conn.getEndpointDetails().getRequestCount());
325 
326         final ClassicHttpRequest request = new BasicClassicHttpRequest(Method.POST, "/stuff");
327         request.addHeader("User-Agent", "test");
328 
329         conn.sendRequestHeader(request);
330         conn.sendRequestEntity(request);
331         conn.flush();
332 
333         Assertions.assertEquals(1, conn.getEndpointDetails().getRequestCount());
334         final String s = new String(outStream.toByteArray(), StandardCharsets.US_ASCII);
335         Assertions.assertEquals("POST /stuff HTTP/1.1\r\nUser-Agent: test\r\n\r\n", s);
336     }
337 
338     @Test
339     public void testTerminateRequestChunkedEntity() throws Exception {
340         final ByteArrayOutputStream outStream = new ByteArrayOutputStream();
341         Mockito.when(socket.getOutputStream()).thenReturn(outStream);
342 
343         conn.bind(socket);
344 
345         Assertions.assertEquals(0, conn.getEndpointDetails().getRequestCount());
346 
347         final ClassicHttpRequest request = new BasicClassicHttpRequest(Method.POST, "/stuff");
348         request.addHeader("User-Agent", "test");
349         request.addHeader("Transfer-Encoding", "chunked");
350         final StringEntity entity = new StringEntity("123", ContentType.TEXT_PLAIN, true);
351 
352         request.setEntity(entity);
353 
354         conn.sendRequestHeader(request);
355         conn.terminateRequest(request);
356         conn.flush();
357 
358         Assertions.assertEquals(1, conn.getEndpointDetails().getRequestCount());
359         final String s = new String(outStream.toByteArray(), StandardCharsets.US_ASCII);
360         Assertions.assertEquals("POST /stuff HTTP/1.1\r\nUser-Agent: test\r\nTransfer-Encoding: " +
361                 "chunked\r\n\r\n0\r\n\r\n", s);
362         Assertions.assertTrue(conn.isConsistent());
363     }
364 
365     @Test
366     public void testTerminateRequestContentLengthShort() throws Exception {
367         final ByteArrayOutputStream outStream = new ByteArrayOutputStream();
368         Mockito.when(socket.getOutputStream()).thenReturn(outStream);
369 
370         conn.bind(socket);
371 
372         Assertions.assertEquals(0, conn.getEndpointDetails().getRequestCount());
373 
374         final ClassicHttpRequest request = new BasicClassicHttpRequest(Method.POST, "/stuff");
375         request.addHeader("User-Agent", "test");
376         request.addHeader("Content-Length", "3");
377         final StringEntity entity = new StringEntity("123", ContentType.TEXT_PLAIN, true);
378         request.setEntity(entity);
379 
380         conn.sendRequestHeader(request);
381         conn.terminateRequest(request);
382         conn.flush();
383 
384         Assertions.assertEquals(1, conn.getEndpointDetails().getRequestCount());
385         final String s = new String(outStream.toByteArray(), StandardCharsets.US_ASCII);
386         Assertions.assertEquals("POST /stuff HTTP/1.1\r\nUser-Agent: test\r\nContent-Length: " +
387                 "3\r\n\r\n123", s);
388         Assertions.assertTrue(conn.isConsistent());
389     }
390 
391     @Test
392     public void testTerminateRequestContentLengthLong() throws Exception {
393         final ByteArrayOutputStream outStream = new ByteArrayOutputStream();
394         Mockito.when(socket.getOutputStream()).thenReturn(outStream);
395 
396         conn.bind(socket);
397 
398         Assertions.assertEquals(0, conn.getEndpointDetails().getRequestCount());
399 
400         final ClassicHttpRequest request = new BasicClassicHttpRequest(Method.POST, "/stuff");
401         request.addHeader("User-Agent", "test");
402         request.addHeader("Content-Length", "3000");
403         final ByteArrayEntity entity = new ByteArrayEntity(new byte[3000], ContentType.TEXT_PLAIN, true);
404         request.setEntity(entity);
405 
406         conn.sendRequestHeader(request);
407         conn.terminateRequest(request);
408         conn.flush();
409 
410         Assertions.assertEquals(1, conn.getEndpointDetails().getRequestCount());
411         final String s = new String(outStream.toByteArray(), StandardCharsets.US_ASCII);
412         Assertions.assertEquals("POST /stuff HTTP/1.1\r\nUser-Agent: test\r\nContent-Length: " +
413                 "3000\r\n\r\n", s);
414         Assertions.assertFalse(conn.isConsistent());
415     }
416 
417 }