非同期サーブレットが必要ですが、外部トークンジェネレータへの非同期HTTPコールも必要です。トークン要求ごとに1つのスレッドをまだ作成している場合は、サーブレットからExecutorServiceにスレッドプールを使用してリクエストを渡すことで、何も得られません。あるスレッドが複数のHTTP要求を処理できるように、スレッドをHTTP要求から切り離す必要があります。これは、Apache Asynch HttpClientまたはAsync Http Clientのような非同期HTTPクライアントで実現できます。
まずあなたはこのサーブレットは、Apache HttpClientを非同期を使用して非同期HTTP呼び出しを行い、この1
public class ProxyService extends HttpServlet {
private CloseableHttpAsyncClient httpClient;
@Override
public void init() throws ServletException {
httpClient = HttpAsyncClients.custom().
setMaxConnTotal(Integer.parseInt(getInitParameter("maxtotalconnections"))).
setMaxConnPerRoute(Integer.parseInt(getInitParameter("maxconnectionsperroute"))).
build();
httpClient.start();
}
@Override
public void destroy() {
try {
httpClient.close();
} catch (IOException e) { }
}
@Override
public void doGet(HttpServletRequest request, HttpServletResponse response) {
AsyncContext asyncCtx = request.startAsync(request, response);
asyncCtx.setTimeout(ExternalServiceMock.TIMEOUT_SECONDS * ExternalServiceMock.K);
ResponseListener listener = new ResponseListener();
asyncCtx.addListener(listener);
Future<String> result = httpClient.execute(HttpAsyncMethods.createGet(getInitParameter("serviceurl")), new ResponseConsumer(asyncCtx), null);
}
}
のような非同期サーブレットを作成する必要があります。 RFC 2616の仕様に従って、HttpAsyncClientはデフォルトで同じホストへの最大2つの同時接続のみを許可するため、ルートごとの最大接続数を設定することができます。そして、HttpAsyncClient configurationに示すように構成できる他の多くのオプションがあります。 HttpAsyncClientは作成するのに費用がかかります。したがって、各GET操作でインスタンスを作成する必要はありません。
1つのリスナーがAsyncContextにフックされます。このリスナーは、上記の例でタイムアウトを処理するためにのみ使用されます。
public class ResponseListener implements AsyncListener {
@Override
public void onStartAsync(AsyncEvent event) throws IOException {
}
@Override
public void onComplete(AsyncEvent event) throws IOException {
}
@Override
public void onError(AsyncEvent event) throws IOException {
event.getAsyncContext().getResponse().getWriter().print("error:");
}
@Override
public void onTimeout(AsyncEvent event) throws IOException {
event.getAsyncContext().getResponse().getWriter().print("timeout:");
}
}
次に、HTTPクライアントのコンシューマーが必要です。このコンシューマは、complete()
を呼び出して、Future<String>
を呼び出し元ProxyService
サーブレットに返すステップとして、buildResult()
がHttpClientによって内部的に実行されるときに、AsyncContextに通知します。
public class ResponseConsumer extends AsyncCharConsumer<String> {
private int responseCode;
private StringBuilder responseBuffer;
private AsyncContext asyncCtx;
public ResponseConsumer(AsyncContext asyncCtx) {
this.responseBuffer = new StringBuilder();
this.asyncCtx = asyncCtx;
}
@Override
protected void releaseResources() { }
@Override
protected String buildResult(final HttpContext context) {
try {
PrintWriter responseWriter = asyncCtx.getResponse().getWriter();
switch (responseCode) {
case javax.servlet.http.HttpServletResponse.SC_OK:
responseWriter.print("success:" + responseBuffer.toString());
break;
default:
responseWriter.print("error:" + responseBuffer.toString());
}
} catch (IOException e) { }
asyncCtx.complete();
return responseBuffer.toString();
}
@Override
protected void onCharReceived(CharBuffer buffer, IOControl ioc) throws IOException {
while (buffer.hasRemaining())
responseBuffer.append(buffer.get());
}
@Override
protected void onResponseReceived(HttpResponse response) throws HttpException, IOException {
responseCode = response.getStatusLine().getStatusCode();
}
}
ProxyServiceのサーブレットのweb.xml構成であってもよい
<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns="http://java.sun.com/xml/ns/javaee"
xmlns:web="http://java.sun.com/xml/ns/javaee/web-app_3_0.xsd"
xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_3_0.xsd"
id="WebApp_ID" version="3.0" metadata-complete="true">
<display-name>asyncservlet-demo</display-name>
<servlet>
<servlet-name>External Service Mock</servlet-name>
<servlet-class>ExternalServiceMock</servlet-class>
<load-on-startup>1</load-on-startup>
</servlet>
<servlet>
<servlet-name>Proxy Service</servlet-name>
<servlet-class>ProxyService</servlet-class>
<load-on-startup>1</load-on-startup>
<async-supported>true</async-supported>
<init-param>
<param-name>maxtotalconnections</param-name>
<param-value>200</param-value>
</init-param>
<init-param>
<param-name>maxconnectionsperroute</param-name>
<param-value>4</param-value>
</init-param>
<init-param>
<param-name>serviceurl</param-name>
<param-value>http://127.0.0.1:8080/asyncservlet/externalservicemock</param-value>
</init-param>
</servlet>
<servlet-mapping>
<servlet-name>External Service Mock</servlet-name>
<url-pattern>/externalservicemock</url-pattern>
</servlet-mapping>
<servlet-mapping>
<servlet-name>Proxy Service</servlet-name>
<url-pattern>/proxyservice</url-pattern>
</servlet-mapping>
</web-app>
秒の遅れを持つトークン生成のためのモックサーブレットのようなものがあります。
public class ExternalServiceMock extends HttpServlet{
public static final int TIMEOUT_SECONDS = 13;
public static final long K = 1000l;
public void doGet(HttpServletRequest request, HttpServletResponse response) throws IOException {
Random rnd = new Random();
try {
Thread.sleep(rnd.nextInt(TIMEOUT_SECONDS) * K);
} catch (InterruptedException e) { }
final byte[] token = String.format("%10d", Math.abs(rnd.nextLong())).getBytes(ISO_8859_1);
response.setContentType("text/plain");
response.setCharacterEncoding(ISO_8859_1.name());
response.setContentLength(token.length);
response.getOutputStream().write(token);
}
}
することができますfully working example at GitHubを取得します。
トークン生成プロセス中に、毎秒着信要求の約80%が待機する必要があります。* "、着信要求の80%がアプリケーションへの着信要求か、トークン生成のためにサードパーティシステムに送信したリクエスト。私はあなたがあなたの完全な答えでこの全体を明確にする必要があると思います。私が言ったように、誰もが明白ではないかもしれないので、解明して、解決策を得るチャンスを増やすかもしれないからです。 – hagrawal
@hagrawal着信要求の80%がサードパーティを待っています。 –