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.classic;
29  
30  import java.io.IOException;
31  import java.io.InputStream;
32  import java.io.OutputStream;
33  import java.net.URI;
34  import java.net.URISyntaxException;
35  import java.nio.charset.StandardCharsets;
36  
37  import org.apache.hc.core5.http.ClassicHttpRequest;
38  import org.apache.hc.core5.http.ClassicHttpResponse;
39  import org.apache.hc.core5.http.ContentType;
40  import org.apache.hc.core5.http.HttpException;
41  import org.apache.hc.core5.http.HttpStatus;
42  import org.apache.hc.core5.http.MethodNotSupportedException;
43  import org.apache.hc.core5.http.ProtocolException;
44  import org.apache.hc.core5.http.io.HttpRequestHandler;
45  import org.apache.hc.core5.http.io.entity.AbstractHttpEntity;
46  import org.apache.hc.core5.http.protocol.HttpContext;
47  
48  /**
49   * A handler that generates random data.
50   */
51  public class  RandomHandler implements HttpRequestHandler {
52  
53      /**
54       * Handles a request by generating random data.
55       * The length of the response can be specified in the request URI
56       * as a number after the last /. For example /random/whatever/20
57       * will generate 20 random bytes in the printable ASCII range.
58       * If the request URI ends with /, a random number of random bytes
59       * is generated, but at least one.
60       *
61       * @param request   the request
62       * @param response  the response
63       * @param context   the context
64       *
65       * @throws HttpException    in case of a problem
66       * @throws IOException      in case of an IO problem
67       */
68      @Override
69      public void handle(final ClassicHttpRequest request,
70                         final ClassicHttpResponse response,
71                         final HttpContext context)
72          throws HttpException, IOException {
73  
74          final String method = request.getMethod();
75          if (!"GET".equalsIgnoreCase(method) &&
76                  !"HEAD".equalsIgnoreCase(method) &&
77                  !"POST".equalsIgnoreCase(method) &&
78                  !"PUT".equalsIgnoreCase(method)) {
79              throw new MethodNotSupportedException(method + " not supported by " + getClass().getName());
80          }
81          final URI uri;
82          try {
83              uri = request.getUri();
84          } catch (final URISyntaxException ex) {
85              throw new ProtocolException(ex.getMessage(), ex);
86          }
87          final String path = uri.getPath();
88          final int slash = path.lastIndexOf('/');
89          if (slash != -1) {
90              final String payload = path.substring(slash + 1);
91              final long n;
92              if (!payload.isEmpty()) {
93                  try {
94                      n = Long.parseLong(payload);
95                  } catch (final NumberFormatException ex) {
96                      throw new ProtocolException("Invalid request path: " + path);
97                  }
98              } else {
99                  // random length, but make sure at least something is sent
100                 n = 1 + (int)(Math.random() * 79.0);
101             }
102             response.setCode(HttpStatus.SC_OK);
103             response.setEntity(new RandomEntity(n));
104         } else {
105             throw new ProtocolException("Invalid request path: " + path);
106         }
107     }
108 
109     /**
110      * An entity that generates random data.
111      * This is an outgoing entity, it supports {@link #writeTo writeTo}
112      * but not {@link #getContent getContent}.
113      */
114     public static class RandomEntity extends AbstractHttpEntity {
115 
116         /** The range from which to generate random data. */
117         private final static byte[] RANGE = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789"
118                 .getBytes(StandardCharsets.US_ASCII);
119 
120         /** The length of the random data to generate. */
121         protected final long length;
122 
123 
124         /**
125          * Creates a new entity generating the given amount of data.
126          *
127          * @param len   the number of random bytes to generate,
128          *              0 to maxint
129          */
130         public RandomEntity(final long len) {
131             super((ContentType) null, null);
132             length = len;
133         }
134 
135         /**
136          * Tells that this entity is not streaming.
137          *
138          * @return      false
139          */
140         @Override
141         public final boolean isStreaming() {
142             return false;
143         }
144 
145         /**
146          * Tells that this entity is repeatable, in a way.
147          * Repetitions will generate different random data,
148          * unless perchance the same random data is generated twice.
149          *
150          * @return      {@code true}
151          */
152         @Override
153         public boolean isRepeatable() {
154             return true;
155         }
156 
157         /**
158          * Obtains the size of the random data.
159          *
160          * @return      the number of random bytes to generate
161          */
162         @Override
163         public long getContentLength() {
164             return length;
165         }
166 
167 
168         /**
169          * Not supported.
170          * This method throws an exception.
171          *
172          * @return      never anything
173          */
174         @Override
175         public InputStream getContent() {
176             throw new UnsupportedOperationException();
177         }
178 
179 
180         /**
181          * Generates the random content.
182          *
183          * @param out   where to write the content to
184          */
185         @Override
186         public void writeTo(final OutputStream out) throws IOException {
187 
188             final int blocksize = 2048;
189             int       remaining = (int) length; // range checked in constructor
190             final byte[]         data = new byte[Math.min(remaining, blocksize)];
191 
192             while (remaining > 0) {
193                 final int end = Math.min(remaining, data.length);
194 
195                 double value = 0.0;
196                 for (int i = 0; i < end; i++) {
197                     // we get 5 random characters out of one random value
198                     if (i%5 == 0) {
199                         value = Math.random();
200                     }
201                     value = value * RANGE.length;
202                     final int d = (int) value;
203                     value = value - d;
204                     data[i] = RANGE[d];
205                 }
206                 out.write(data, 0, end);
207                 out.flush();
208 
209                 remaining = remaining - end;
210             }
211             out.close();
212 
213         }
214 
215         @Override
216         public void close() throws IOException {
217         }
218 
219     }
220 
221 }