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.http.nio.client.methods;
28  
29  import java.io.File;
30  import java.io.FileInputStream;
31  import java.io.FileNotFoundException;
32  import java.io.IOException;
33  import java.io.InputStream;
34  import java.net.InetSocketAddress;
35  import java.net.URI;
36  import java.nio.charset.Charset;
37  import java.util.concurrent.Future;
38  
39  import org.apache.commons.io.FileUtils;
40  import org.apache.commons.io.IOUtils;
41  import org.apache.commons.io.LineIterator;
42  import org.apache.commons.io.output.FileWriterWithEncoding;
43  import org.apache.http.Consts;
44  import org.apache.http.HttpAsyncTestBase;
45  import org.apache.http.HttpEntity;
46  import org.apache.http.HttpEntityEnclosingRequest;
47  import org.apache.http.HttpException;
48  import org.apache.http.HttpHost;
49  import org.apache.http.HttpRequest;
50  import org.apache.http.HttpResponse;
51  import org.apache.http.HttpStatus;
52  import org.apache.http.client.methods.HttpPost;
53  import org.apache.http.config.ConnectionConfig;
54  import org.apache.http.entity.BasicHttpEntity;
55  import org.apache.http.entity.ContentType;
56  import org.apache.http.impl.DefaultConnectionReuseStrategy;
57  import org.apache.http.impl.DefaultHttpResponseFactory;
58  import org.apache.http.impl.nio.DefaultNHttpServerConnection;
59  import org.apache.http.impl.nio.DefaultNHttpServerConnectionFactory;
60  import org.apache.http.impl.nio.client.HttpAsyncClients;
61  import org.apache.http.nio.NHttpConnectionFactory;
62  import org.apache.http.nio.entity.NFileEntity;
63  import org.apache.http.nio.entity.NStringEntity;
64  import org.apache.http.nio.protocol.BasicAsyncRequestHandler;
65  import org.apache.http.nio.protocol.HttpAsyncExpectationVerifier;
66  import org.apache.http.nio.protocol.HttpAsyncRequestHandlerMapper;
67  import org.apache.http.nio.protocol.HttpAsyncService;
68  import org.apache.http.nio.protocol.UriHttpAsyncRequestHandlerMapper;
69  import org.apache.http.nio.reactor.IOReactorStatus;
70  import org.apache.http.nio.reactor.ListenerEndpoint;
71  import org.apache.http.protocol.HttpContext;
72  import org.apache.http.protocol.HttpRequestHandler;
73  import org.junit.After;
74  import org.junit.AfterClass;
75  import org.junit.Assert;
76  import org.junit.Before;
77  import org.junit.BeforeClass;
78  import org.junit.Test;
79  
80  public class TestZeroCopy extends HttpAsyncTestBase {
81  
82      @Before
83      public void setUp() throws Exception {
84          initServer();
85          initConnectionManager();
86          this.httpclient = HttpAsyncClients.custom()
87                  .setConnectionManager(this.connMgr)
88                  .build();
89      }
90  
91      @After
92      public void tearDown() throws Exception {
93          shutDownClient();
94          shutDownServer();
95      }
96  
97      @Override
98      protected NHttpConnectionFactory<DefaultNHttpServerConnection> createServerConnectionFactory(
99              final ConnectionConfig config) throws Exception {
100         return new DefaultNHttpServerConnectionFactory(config);
101     }
102 
103     @Override
104     protected String getSchemeName() {
105         return "http";
106     }
107 
108     private HttpHost start(
109             final HttpAsyncRequestHandlerMapper requestHandlerResolver,
110             final HttpAsyncExpectationVerifier expectationVerifier) throws Exception {
111         final HttpAsyncService serviceHandler = new HttpAsyncService(
112                 this.serverHttpProc,
113                 new DefaultConnectionReuseStrategy(),
114                 new DefaultHttpResponseFactory(),
115                 requestHandlerResolver,
116                 expectationVerifier);
117         this.server.start(serviceHandler);
118         this.httpclient.start();
119 
120         final ListenerEndpoint endpoint = this.server.getListenerEndpoint();
121         endpoint.waitFor();
122 
123         Assert.assertEquals("Test server status", IOReactorStatus.ACTIVE, this.server.getStatus());
124         final InetSocketAddress address = (InetSocketAddress) endpoint.getAddress();
125         return new HttpHost("localhost", address.getPort(), getSchemeName());
126     }
127 
128     private static final String[] TEXT = {
129         "blah blah blah blah blah blah blah blah blah blah blah blah blah blah",
130         "yada yada yada yada yada yada yada yada yada yada yada yada yada yada",
131         "da da da da da da da da da da da da da da da da da da da da da da da da",
132         "nyet nyet nyet nyet nyet nyet nyet nyet nyet nyet nyet nyet nyet nyet"
133     };
134 
135     private static final Charset ASCII = Charset.forName("ascii");
136     private static File TEST_FILE;
137     private File tmpfile;
138 
139     @BeforeClass
140     public static void createSrcFile() throws Exception {
141         final File tmpdir = FileUtils.getTempDirectory();
142         TEST_FILE = new File(tmpdir, "src.test");
143         final FileWriterWithEncoding out = new FileWriterWithEncoding(TEST_FILE, ASCII);
144         try {
145             for (int i = 0; i < 500; i++) {
146                 for (final String line: TEXT) {
147                     out.write(line);
148                     out.write("\r\n");
149                 }
150             }
151         } finally {
152             out.close();
153         }
154     }
155 
156     @AfterClass
157     public static void deleteSrcFile() throws Exception {
158         if (TEST_FILE != null) {
159             TEST_FILE.delete();
160             TEST_FILE = null;
161         }
162     }
163 
164     @After
165     public void cleanUp() throws Exception {
166         if (this.tmpfile != null && this.tmpfile.exists()) {
167             this.tmpfile.delete();
168         }
169     }
170 
171     static class TestZeroCopyPost extends BaseZeroCopyRequestProducer {
172 
173         private final boolean forceChunking;
174 
175         protected TestZeroCopyPost(
176                 final String requestURI,
177                 final boolean forceChunking) throws FileNotFoundException {
178             super(URI.create(requestURI), TEST_FILE, ContentType.create("text/plain"));
179             this.forceChunking = forceChunking;
180         }
181 
182         @Override
183         protected HttpEntityEnclosingRequest createRequest(final URI requestURI, final HttpEntity entity) {
184             final HttpPost httppost = new HttpPost(requestURI);
185             if (this.forceChunking) {
186                 final BasicHttpEntity chunkedEntity = new BasicHttpEntity();
187                 chunkedEntity.setChunked(true);
188                 httppost.setEntity(chunkedEntity);
189             } else {
190                 httppost.setEntity(entity);
191             }
192             return httppost;
193         }
194 
195     }
196 
197     static class TestZeroCopyConsumer extends ZeroCopyConsumer<Integer> {
198 
199         public TestZeroCopyConsumer(final File file) throws FileNotFoundException {
200             super(file);
201         }
202 
203         @Override
204         protected Integer process(
205                 final HttpResponse response,
206                 final File file,
207                 final ContentType contentType) {
208             return response.getStatusLine().getStatusCode();
209         }
210 
211     }
212 
213     static class TestHandler implements HttpRequestHandler {
214 
215         private final boolean forceChunking;
216 
217         TestHandler(final boolean forceChunking) {
218             super();
219             this.forceChunking = forceChunking;
220         }
221 
222         public void handle(
223                 final HttpRequest request,
224                 final HttpResponse response,
225                 final HttpContext context) throws HttpException, IOException {
226             HttpEntity requestEntity = null;
227             if (request instanceof HttpEntityEnclosingRequest) {
228                 requestEntity = ((HttpEntityEnclosingRequest) request).getEntity();
229             }
230             if (requestEntity == null) {
231                 response.setEntity(new NStringEntity("Empty content"));
232                 return;
233             }
234 
235             boolean ok = true;
236 
237             final InputStream instream = requestEntity.getContent();
238             try {
239                 final ContentType contentType = ContentType.getOrDefault(requestEntity);
240                 Charset charset = contentType.getCharset();
241                 if (charset == null) {
242                     charset = Consts.ISO_8859_1;
243                 }
244                 final LineIterator it = IOUtils.lineIterator(instream, charset.name());
245                 int count = 0;
246                 while (it.hasNext()) {
247                     final String line = it.next();
248                     final int i = count % TEXT.length;
249                     final String expected = TEXT[i];
250                     if (!line.equals(expected)) {
251                         ok = false;
252                         break;
253                     }
254                     count++;
255                 }
256             } finally {
257                 instream.close();
258             }
259             if (ok) {
260                 final NFileEntity responseEntity = new NFileEntity(TEST_FILE,
261                         ContentType.create("text/plian"));
262                 if (this.forceChunking) {
263                     responseEntity.setChunked(true);
264                 }
265                 response.setEntity(responseEntity);
266             } else {
267                 response.setEntity(new NStringEntity("Invalid content"));
268             }
269         }
270     }
271 
272     @Test
273     public void testTwoWayZeroCopy() throws Exception {
274         final UriHttpAsyncRequestHandlerMapper registry = new UriHttpAsyncRequestHandlerMapper();
275         registry.register("*", new BasicAsyncRequestHandler(new TestHandler(false)));
276         final HttpHost target = start(registry, null);
277 
278         final File tmpdir = FileUtils.getTempDirectory();
279         this.tmpfile = new File(tmpdir, "dst.test");
280         final TestZeroCopyPost httppost = new TestZeroCopyPost(target.toURI() + "/bounce", false);
281         final TestZeroCopyConsumer consumer = new TestZeroCopyConsumer(this.tmpfile);
282         final Future<Integer> future = this.httpclient.execute(httppost, consumer, null);
283         final Integer status = future.get();
284         Assert.assertNotNull(status);
285         Assert.assertEquals(HttpStatus.SC_OK, status.intValue());
286         final InputStream instream = new FileInputStream(this.tmpfile);
287         try {
288             final LineIterator it = IOUtils.lineIterator(instream, ASCII.name());
289             int count = 0;
290             while (it.hasNext()) {
291                 final String line = it.next();
292                 final int i = count % TEXT.length;
293                 final String expected = TEXT[i];
294                 Assert.assertEquals(expected, line);
295                 count++;
296             }
297         } finally {
298             instream.close();
299         }
300     }
301 
302     @Test
303     public void testZeroCopyFallback() throws Exception {
304         final UriHttpAsyncRequestHandlerMapper registry = new UriHttpAsyncRequestHandlerMapper();
305         registry.register("*", new BasicAsyncRequestHandler(new TestHandler(true)));
306         final HttpHost target = start(registry, null);
307         final File tmpdir = FileUtils.getTempDirectory();
308         this.tmpfile = new File(tmpdir, "dst.test");
309         final TestZeroCopyPost httppost = new TestZeroCopyPost(target.toURI() + "/bounce", true);
310         final TestZeroCopyConsumer consumer = new TestZeroCopyConsumer(this.tmpfile);
311         final Future<Integer> future = this.httpclient.execute(httppost, consumer, null);
312         final Integer status = future.get();
313         Assert.assertNotNull(status);
314         Assert.assertEquals(HttpStatus.SC_OK, status.intValue());
315         final InputStream instream = new FileInputStream(this.tmpfile);
316         try {
317             final LineIterator it = IOUtils.lineIterator(instream, ASCII.name());
318             int count = 0;
319             while (it.hasNext()) {
320                 final String line = it.next();
321                 final int i = count % TEXT.length;
322                 final String expected = TEXT[i];
323                 Assert.assertEquals(expected, line);
324                 count++;
325             }
326         } finally {
327             instream.close();
328         }
329     }
330 
331 }