Я столкнулся с головоломкой, которую пытаюсь понять. Я реализовал очень простой пример сервиса REST, который работает на сервере Wildfly. У меня есть отдельная многопоточная тестовая программа Java, которая использует клиентскую библиотеку Apache http для доступа к службе. Клиентская программа имеет два независимых потока, одновременно обращающихся к службе REST. Когда тестовая программа запускается без установления httpSession на сервере, сервер обрабатывает запросы одновременно (как показано в журнале сервера). Если потоки запускаются после установления httpSession, запросы обрабатываются сервером последовательным образом (как показано во втором журнале сервера). Обратите внимание, что оба потока совместно используют один сеанс.
Это по замыслу? Что мне здесь не хватает? Заранее спасибо.
Вот основы моей среды:
- Информация о сервере Wildfly: Wildfly-17.0.0. Окончательный запуск на Windows 10 с использованием автономной конфигурации высокой доступности, указанной в standalone- га. xml
- Информация о клиенте: Apache http клиент 4.5.5 (управляемая версия).
Вот журналы сервера, представляющие оба случаи:
Server log when no http session is established.
Requests run concurrently when there is no session.
13:39:13,384 INFO (default task-5) Getting widget 3
13:39:13,384 INFO (default task-6) Getting widget 4
13:39:13,384 INFO (default task-6) DELAYING widget 4
13:39:13,384 INFO (default task-5) Returning widget 3
13:39:13,404 INFO (default task-6) DELAY COMPLETE widget 4
13:39:13,404 INFO (default task-6) Returning widget 4
13:39:13,408 INFO (default task-6) Getting widget 3
13:39:13,408 INFO (default task-6) Returning widget 3
13:39:13,409 INFO (default task-5) Getting widget 4
13:39:13,409 INFO (default task-5) DELAYING widget 4
13:39:13,413 INFO (default task-6) Getting widget 3
13:39:13,413 INFO (default task-6) Returning widget 3
13:39:13,419 INFO (default task-6) Getting widget 3
13:39:13,419 INFO (default task-6) Returning widget 3
13:39:13,425 INFO (default task-6) Getting widget 3
13:39:13,425 INFO (default task-6) Returning widget 3
13:39:13,430 INFO (default task-6) Getting widget 3
13:39:13,430 INFO (default task-6) Returning widget 3
13:39:13,436 INFO (default task-5) DELAY COMPLETE widget 4
13:39:13,436 INFO (default task-5) Returning widget 4
13:39:13,436 INFO (default task-6) Getting widget 3
13:39:13,436 INFO (default task-6) Returning widget 3
13:39:13,441 INFO (default task-6) Getting widget 4
13:39:13,441 INFO (default task-5) Getting widget 3
13:39:13,442 INFO (default task-6) DELAYING widget 4
13:39:13,442 INFO (default task-5) Returning widget 3
13:39:13,447 INFO (default task-5) Getting widget 3
13:39:13,447 INFO (default task-5) Returning widget 3
...
Server log after having established a http session.
Requests run sequentially after having established a session.
13:41:08,761 INFO (default task-5) Session established PA7xR3HCYMR3A2S6iX-4Ziw1dGVNejPktseYMMdm
13:41:08,808 INFO (default task-5) Getting widget 4
13:41:08,808 INFO (default task-5) DELAYING widget 4
13:41:08,829 INFO (default task-5) DELAY COMPLETE widget 4
13:41:08,829 INFO (default task-5) Returning widget 4
13:41:08,833 INFO (default task-6) Getting widget 3
13:41:08,833 INFO (default task-6) Returning widget 3
13:41:08,837 INFO (default task-5) Getting widget 4
13:41:08,837 INFO (default task-5) DELAYING widget 4
13:41:08,857 INFO (default task-5) DELAY COMPLETE widget 4
13:41:08,857 INFO (default task-5) Returning widget 4
13:41:08,860 INFO (default task-6) Getting widget 3
13:41:08,860 INFO (default task-6) Returning widget 3
13:41:08,863 INFO (default task-5) Getting widget 4
13:41:08,864 INFO (default task-5) DELAYING widget 4
13:41:08,884 INFO (default task-5) DELAY COMPLETE widget 4
13:41:08,884 INFO (default task-5) Returning widget 4
13:41:08,887 INFO (default task-6) Getting widget 3
13:41:08,887 INFO (default task-6) Returning widget 3
13:41:08,890 INFO (default task-5) Getting widget 4
13:41:08,890 INFO (default task-5) DELAYING widget 4
13:41:08,911 INFO (default task-5) DELAY COMPLETE widget 4
13:41:08,911 INFO (default task-5) Returning widget 4
13:41:08,914 INFO (default task-6) Getting widget 3
13:41:08,915 INFO (default task-6) Returning widget 3
...
Это реализация службы REST
@Path("concurrencyTest")
@Stateless
public class ConcurrencyTestWs extends BaseWs {
private static final Logger log = LoggerFactory
.getLogger(ConcurrencyTestWs.class);
@SuppressWarnings("static-method")
@GET
@Path("/widget")
public Response getWidget(@Context HttpServletRequest request,
@QueryParam(value = "widgetId") Long widgetId) {
try {
log.info(String.format("Getting widget %d", widgetId));
if ((widgetId % 2) == 0) {
log.info(String.format("DELAYING widget %d", widgetId));
try {
Thread.sleep(20);
} catch (Exception e) {
System.err.println(e.getMessage());
}
log.info(String.format("DELAY COMPLETE widget %d", widgetId));
}
log.info(String.format("Returning widget %d", widgetId));
return Response.ok(String.format("Widget %d retrieved", widgetId))
.build();
} catch (Exception e) {
return RestUtilities.getInternalError(request, "Widget", widgetId, e);
}
}
@SuppressWarnings("static-method")
@POST
@Path("/establishSession")
public Response establishSession(@Context HttpServletRequest request) {
try {
HttpSession session = request.getSession(true);
log.info(String.format("Session established %s", session.getId()));
Response response = Response.ok().build();
return response;
} catch (Exception e) {
return RestUtilities.getInternalError(request, e);
}
}
@SuppressWarnings("static-method")
@POST
@Path("/terminateSession")
public Response terminateSession(@Context HttpServletRequest request) {
try {
HttpSession session = request.getSession(false);
log.info(String.format("Terminating session %s",
(session == null) ? "null" : session.getId()));
Response response = Response.ok().build();
return response;
} catch (Exception e) {
return RestUtilities.getInternalError(request, e);
}
}
}
Вот исходный код клиента
public class WidgetFetchTest {
private final static String restServiceUrl = "http://dtp002.novodynamics.com:8081/TEST/REST/v1.0/concurrencyTest";
private final CloseableHttpClient httpClient;
private final CookieStore cookieStore;
public WidgetFetchTest() {
this.cookieStore = new BasicCookieStore();
RequestConfig requestConfig = RequestConfig.custom()
.setConnectTimeout(60000).setConnectionRequestTimeout(60000)
.setSocketTimeout(60000).setCookieSpec(CookieSpecs.STANDARD).build();
@SuppressWarnings("resource")
PoolingHttpClientConnectionManager connectionManager = new PoolingHttpClientConnectionManager();
connectionManager.setMaxTotal(100);
connectionManager.setDefaultMaxPerRoute(40);
this.httpClient = HttpClients.custom()
.setDefaultRequestConfig(requestConfig)
.setDefaultCookieStore(this.cookieStore)
.setConnectionManager(connectionManager).build();
}
public void run() {
establishSession(this.httpClient);
DocFetchRunnable r1 = new DocFetchRunnable("4", this.httpClient);
DocFetchRunnable r2 = new DocFetchRunnable("3", this.httpClient);
ExecutorService exec = Executors.newFixedThreadPool(2);
exec.execute(r1);
exec.execute(r2);
try {
Thread.sleep(60000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
protected static void establishSession(CloseableHttpClient client) {
try {
URIBuilder ub = new URIBuilder(
String.format("%s%s", restServiceUrl, "/establishSession"));
String url = ub.toString();
HttpContext ctx = HttpClientContext.create();
try (CloseableHttpResponse response = client.execute(new HttpPost(url),
ctx)) {
// intentionally empty.
} catch (Exception e) {
e.printStackTrace();
}
} catch (URISyntaxException e) {
e.printStackTrace();
}
}
protected static void getWidget(CloseableHttpClient client, String widgetId) {
try {
URIBuilder ub = new URIBuilder(
String.format("%s%s", restServiceUrl, "/widget"));
ub.addParameter("widgetId", widgetId);
String url = ub.toString();
HttpContext ctx = HttpClientContext.create();
try (CloseableHttpResponse response = client.execute(new HttpGet(url),
ctx)) {
// intentionally empty.
} catch (Exception e) {
e.printStackTrace();
}
} catch (URISyntaxException e) {
e.printStackTrace();
}
}
private class DocFetchRunnable implements Runnable {
private String widgetId;
CloseableHttpClient client;
public DocFetchRunnable(String widgetId, CloseableHttpClient client) {
this.widgetId = widgetId;
this.client = client;
}
@Override
public void run() {
for (int i = 0; i < 50; i++) {
System.out.println(
String.format("Fetching widget %s - %d", this.widgetId, i));
System.out.flush();
getWidget(this.client, this.widgetId);
System.out.println(
String.format("Widget fetched %s - %d", this.widgetId, i));
System.out.flush();
}
}
}
public static void main(String[] args) {
WidgetFetchTest dft = new WidgetFetchTest();
dft.run();
System.exit(0);
}
}