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.client5.testing.async;
29  
30  import java.io.IOException;
31  import java.net.URI;
32  import java.net.URISyntaxException;
33  import java.nio.ByteBuffer;
34  import java.nio.charset.StandardCharsets;
35  import java.util.List;
36  import java.util.concurrent.atomic.AtomicReference;
37  
38  import org.apache.hc.core5.http.ContentType;
39  import org.apache.hc.core5.http.EntityDetails;
40  import org.apache.hc.core5.http.Header;
41  import org.apache.hc.core5.http.HttpException;
42  import org.apache.hc.core5.http.HttpRequest;
43  import org.apache.hc.core5.http.HttpResponse;
44  import org.apache.hc.core5.http.HttpStatus;
45  import org.apache.hc.core5.http.MethodNotSupportedException;
46  import org.apache.hc.core5.http.ProtocolException;
47  import org.apache.hc.core5.http.message.BasicHttpResponse;
48  import org.apache.hc.core5.http.nio.AsyncEntityProducer;
49  import org.apache.hc.core5.http.nio.AsyncServerExchangeHandler;
50  import org.apache.hc.core5.http.nio.CapacityChannel;
51  import org.apache.hc.core5.http.nio.DataStreamChannel;
52  import org.apache.hc.core5.http.nio.ResponseChannel;
53  import org.apache.hc.core5.http.nio.StreamChannel;
54  import org.apache.hc.core5.http.nio.entity.AbstractBinAsyncEntityProducer;
55  import org.apache.hc.core5.http.protocol.HttpContext;
56  import org.apache.hc.core5.util.Asserts;
57  
58  /**
59   * A handler that generates random data.
60   */
61  public class AsyncRandomHandler implements AsyncServerExchangeHandler {
62  
63      private final AtomicReference<AsyncEntityProducer> entityProducerRef;
64  
65      public AsyncRandomHandler() {
66          this.entityProducerRef = new AtomicReference<>();
67      }
68  
69      @Override
70      public void releaseResources() {
71          final AsyncEntityProducer producer = entityProducerRef.getAndSet(null);
72          if (producer != null) {
73              producer.releaseResources();
74          }
75      }
76  
77      @Override
78      public void handleRequest(
79              final HttpRequest request,
80              final EntityDetails entityDetails,
81              final ResponseChannel responseChannel,
82              final HttpContext context) throws HttpException, IOException {
83          final String method = request.getMethod();
84          if (!"GET".equalsIgnoreCase(method) &&
85                  !"HEAD".equalsIgnoreCase(method) &&
86                  !"POST".equalsIgnoreCase(method) &&
87                  !"PUT".equalsIgnoreCase(method)) {
88              throw new MethodNotSupportedException(method + " not supported by " + getClass().getName());
89          }
90          final URI uri;
91          try {
92              uri = request.getUri();
93          } catch (final URISyntaxException ex) {
94              throw new ProtocolException(ex.getMessage(), ex);
95          }
96          final String path = uri.getPath();
97          final int slash = path.lastIndexOf('/');
98          if (slash != -1) {
99              final String payload = path.substring(slash + 1);
100             final long n;
101             if (!payload.isEmpty()) {
102                 try {
103                     n = Long.parseLong(payload);
104                 } catch (final NumberFormatException ex) {
105                     throw new ProtocolException("Invalid request path: " + path);
106                 }
107             } else {
108                 // random length, but make sure at least something is sent
109                 n = 1 + (int)(Math.random() * 79.0);
110             }
111             final HttpResponse response = new BasicHttpResponse(HttpStatus.SC_OK);
112             final AsyncEntityProducer entityProducer = new RandomBinAsyncEntityProducer(n);
113             entityProducerRef.set(entityProducer);
114             responseChannel.sendResponse(response, entityProducer, context);
115         } else {
116             throw new ProtocolException("Invalid request path: " + path);
117         }
118     }
119 
120     @Override
121     public void updateCapacity(final CapacityChannel capacityChannel) throws IOException {
122         capacityChannel.update(Integer.MAX_VALUE);
123     }
124 
125     @Override
126     public void consume(final ByteBuffer src) throws IOException {
127     }
128 
129     @Override
130     public void streamEnd(final List<? extends Header> trailers) throws HttpException, IOException {
131     }
132 
133     @Override
134     public int available() {
135         final AsyncEntityProducer producer = entityProducerRef.get();
136         Asserts.notNull(producer, "Entity producer");
137         return producer.available();
138     }
139 
140     @Override
141     public void produce(final DataStreamChannel channel) throws IOException {
142         final AsyncEntityProducer producer = entityProducerRef.get();
143         Asserts.notNull(producer, "Entity producer");
144         producer.produce(channel);
145     }
146 
147     @Override
148     public void failed(final Exception cause) {
149         releaseResources();
150     }
151 
152     /**
153      * An entity that generates random data.
154      */
155     public static class RandomBinAsyncEntityProducer extends AbstractBinAsyncEntityProducer {
156 
157         /** The range from which to generate random data. */
158         private final static byte[] RANGE = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789"
159                 .getBytes(StandardCharsets.US_ASCII);
160 
161         /** The length of the random data to generate. */
162         private final long length;
163         private long remaining;
164         private final ByteBuffer buffer;
165 
166         public RandomBinAsyncEntityProducer(final long len) {
167             super(512, ContentType.DEFAULT_TEXT);
168             length = len;
169             remaining = len;
170             buffer = ByteBuffer.allocate(1024);
171         }
172 
173         @Override
174         public void releaseResources() {
175             remaining = length;
176         }
177 
178         @Override
179         public boolean isRepeatable() {
180             return true;
181         }
182 
183         @Override
184         public long getContentLength() {
185             return length;
186         }
187 
188         @Override
189         public int availableData() {
190             return Integer.MAX_VALUE;
191         }
192 
193         @Override
194         protected void produceData(final StreamChannel<ByteBuffer> channel) throws IOException {
195             final int chunk = Math.min((int) (remaining < Integer.MAX_VALUE ? remaining : Integer.MAX_VALUE), buffer.remaining());
196             for (int i = 0; i < chunk; i++) {
197                 final byte b = RANGE[(int) (Math.random() * RANGE.length)];
198                 buffer.put(b);
199             }
200             remaining -= chunk;
201 
202             buffer.flip();
203             channel.write(buffer);
204             buffer.compact();
205 
206             if (remaining <= 0 && buffer.position() == 0) {
207                 channel.endStream();
208             }
209         }
210 
211         @Override
212         public void failed(final  Exception cause) {
213         }
214 
215     }
216 
217 }