1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
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
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
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
154
155 public static class RandomBinAsyncEntityProducer extends AbstractBinAsyncEntityProducer {
156
157
158 private final static byte[] RANGE = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789"
159 .getBytes(StandardCharsets.US_ASCII);
160
161
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 }