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 package org.apache.hc.client5.testing.sync;
28
29 import java.io.IOException;
30
31 import org.apache.hc.client5.http.UserTokenHandler;
32 import org.apache.hc.client5.http.classic.methods.HttpGet;
33 import org.apache.hc.client5.http.impl.classic.CloseableHttpClient;
34 import org.apache.hc.client5.http.protocol.HttpClientContext;
35 import org.apache.hc.client5.testing.sync.extension.TestClientResources;
36 import org.apache.hc.core5.http.ClassicHttpRequest;
37 import org.apache.hc.core5.http.ClassicHttpResponse;
38 import org.apache.hc.core5.http.EndpointDetails;
39 import org.apache.hc.core5.http.HttpException;
40 import org.apache.hc.core5.http.HttpHost;
41 import org.apache.hc.core5.http.HttpStatus;
42 import org.apache.hc.core5.http.URIScheme;
43 import org.apache.hc.core5.http.io.HttpRequestHandler;
44 import org.apache.hc.core5.http.io.entity.EntityUtils;
45 import org.apache.hc.core5.http.io.entity.StringEntity;
46 import org.apache.hc.core5.http.protocol.HttpContext;
47 import org.apache.hc.core5.testing.classic.ClassicTestServer;
48 import org.apache.hc.core5.util.Timeout;
49 import org.junit.jupiter.api.Assertions;
50 import org.junit.jupiter.api.Test;
51 import org.junit.jupiter.api.extension.RegisterExtension;
52
53
54
55
56 public class TestStatefulConnManagement {
57
58 public static final Timeout TIMEOUT = Timeout.ofMinutes(1);
59 public static final Timeout LONG_TIMEOUT = Timeout.ofMinutes(3);
60
61 @RegisterExtension
62 private TestClientResources testResources = new TestClientResources(URIScheme.HTTP, TIMEOUT);
63
64 private static class SimpleService implements HttpRequestHandler {
65
66 public SimpleService() {
67 super();
68 }
69
70 @Override
71 public void handle(
72 final ClassicHttpRequest request,
73 final ClassicHttpResponse response,
74 final HttpContext context) throws HttpException, IOException {
75 response.setCode(HttpStatus.SC_OK);
76 final StringEntity entity = new StringEntity("Whatever");
77 response.setEntity(entity);
78 }
79 }
80
81 @Test
82 public void testStatefulConnections() throws Exception {
83 final ClassicTestServer server = testResources.startServer(null, null, null);
84 server.registerHandler("*", new SimpleService());
85 final HttpHost target = testResources.targetHost();
86
87 final int workerCount = 5;
88 final int requestCount = 5;
89
90 final UserTokenHandler userTokenHandler = (route, context) -> {
91 final String id = (String) context.getAttribute("user");
92 return id;
93 };
94
95 final CloseableHttpClient client = testResources.startClient(
96 builder -> builder
97 .setMaxConnTotal(workerCount)
98 .setMaxConnPerRoute(workerCount),
99 builder -> builder
100 .setUserTokenHandler(userTokenHandler)
101 );
102
103 final HttpClientContext[] contexts = new HttpClientContext[workerCount];
104 final HttpWorker[] workers = new HttpWorker[workerCount];
105 for (int i = 0; i < contexts.length; i++) {
106 final HttpClientContext context = HttpClientContext.create();
107 contexts[i] = context;
108 workers[i] = new HttpWorker(
109 "user" + i,
110 context, requestCount, target, client);
111 }
112
113 for (final HttpWorker worker : workers) {
114 worker.start();
115 }
116 for (final HttpWorker worker : workers) {
117 worker.join(LONG_TIMEOUT.toMilliseconds());
118 }
119 for (final HttpWorker worker : workers) {
120 final Exception ex = worker.getException();
121 if (ex != null) {
122 throw ex;
123 }
124 Assertions.assertEquals(requestCount, worker.getCount());
125 }
126
127 for (final HttpContext context : contexts) {
128 final String state0 = (String) context.getAttribute("r0");
129 Assertions.assertNotNull(state0);
130 for (int r = 1; r < requestCount; r++) {
131 Assertions.assertEquals(state0, context.getAttribute("r" + r));
132 }
133 }
134
135 }
136
137 static class HttpWorker extends Thread {
138
139 private final String uid;
140 private final HttpClientContext context;
141 private final int requestCount;
142 private final HttpHost target;
143 private final CloseableHttpClient httpclient;
144
145 private volatile Exception exception;
146 private volatile int count;
147
148 public HttpWorker(
149 final String uid,
150 final HttpClientContext context,
151 final int requestCount,
152 final HttpHost target,
153 final CloseableHttpClient httpclient) {
154 super();
155 this.uid = uid;
156 this.context = context;
157 this.requestCount = requestCount;
158 this.target = target;
159 this.httpclient = httpclient;
160 this.count = 0;
161 }
162
163 public int getCount() {
164 return this.count;
165 }
166
167 public Exception getException() {
168 return this.exception;
169 }
170
171 @Override
172 public void run() {
173 try {
174 this.context.setAttribute("user", this.uid);
175 for (int r = 0; r < this.requestCount; r++) {
176 final HttpGet httpget = new HttpGet("/");
177 this.httpclient.execute(this.target, httpget, this.context, response -> {
178 EntityUtils.consume(response.getEntity());
179 return null;
180 });
181 this.count++;
182
183 final EndpointDetails endpointDetails = this.context.getEndpointDetails();
184 final String connuid = Integer.toHexString(System.identityHashCode(endpointDetails));
185 this.context.setAttribute("r" + r, connuid);
186 }
187
188 } catch (final Exception ex) {
189 this.exception = ex;
190 }
191 }
192
193 }
194
195 @Test
196 public void testRouteSpecificPoolRecylcing() throws Exception {
197 final ClassicTestServer server = testResources.startServer(null, null, null);
198 server.registerHandler("*", new SimpleService());
199 final HttpHost target = testResources.targetHost();
200
201
202
203
204
205 final int maxConn = 2;
206
207
208 final UserTokenHandler userTokenHandler = (route, context) -> context.getAttribute("user");
209
210 final CloseableHttpClient client = testResources.startClient(
211 builder -> builder
212 .setMaxConnTotal(maxConn)
213 .setMaxConnPerRoute(maxConn),
214 builder -> builder
215 .setUserTokenHandler(userTokenHandler)
216 );
217
218
219 final HttpContext context1 = HttpClientContext.create();
220 context1.setAttribute("user", "stuff");
221 client.execute(target, new HttpGet("/"), context1, response -> {
222 EntityUtils.consume(response.getEntity());
223 return null;
224 });
225
226
227
228
229
230 Thread.sleep(100);
231
232
233
234 final HttpContext context2 = HttpClientContext.create();
235 client.execute(new HttpHost("127.0.0.1", server.getPort()), new HttpGet("/"), context2, response -> {
236 EntityUtils.consume(response.getEntity());
237 return null;
238 });
239
240
241
242
243 Thread.sleep(100);
244
245
246
247
248
249
250 final HttpContext context3 = HttpClientContext.create();
251 client.execute(target, new HttpGet("/"), context3, response -> {
252 EntityUtils.consume(response.getEntity());
253 return null;
254 });
255
256
257
258
259
260 }
261
262 }