Eu tinha um cenário em que precisava verificar de tempos em tempos, se uma determinada tarefa havia concluído, numa implementação como a do código a seguir:
int qtdSegundosEspera = 20
while (tarefaEmExecucao) {
registreEventoTarefaEmExecucao()
sleep(qtdSegundosEspera * 1000)
}
em que o método registreEventoTarefaEmExecucao faz uma requisição http para outra aplicação, informando que a tarefa ainda está em execução.
Daí num determinado momento (random) eu tinha a seguinte mensagem de erro:
The target server failed to respond
ou
Connection reset
Com esta stacktrace:
Caused by: org.apache.http.NoHttpResponseException: The target server failed to respond
at org.apache.http.impl.conn.DefaultHttpResponseParser.parseHead(DefaultHttpResponseParser.java:95)
at org.apache.http.impl.conn.DefaultHttpResponseParser.parseHead(DefaultHttpResponseParser.java:62)
at org.apache.http.impl.io.AbstractMessageParser.parse(AbstractMessageParser.java:254)
at org.apache.http.impl.AbstractHttpClientConnection.receiveResponseHeader(AbstractHttpClientConnection.java:289)
at org.apache.http.impl.conn.DefaultClientConnection.receiveResponseHeader(DefaultClientConnection.java:252)
at org.apache.http.impl.conn.AbstractClientConnAdapter.receiveResponseHeader(AbstractClientConnAdapter.java:219)
at org.apache.http.protocol.HttpRequestExecutor.doReceiveResponse(HttpRequestExecutor.java:300)
at org.apache.http.protocol.HttpRequestExecutor.execute(HttpRequestExecutor.java:127)
at org.apache.http.impl.client.DefaultRequestDirector.tryExecute(DefaultRequestDirector.java:712)
at org.apache.http.impl.client.DefaultRequestDirector.execute(DefaultRequestDirector.java:517)
at org.apache.http.impl.client.AbstractHttpClient.execute(AbstractHttpClient.java:906)
at org.apache.http.impl.client.AbstractHttpClient.execute(AbstractHttpClient.java:805)
at groovyx.net.http.HTTPBuilder.doRequest(HTTPBuilder.java:458)
at groovyx.net.http.AsyncHTTPBuilder.doRequestSuper(AsyncHTTPBuilder.java:146)
at groovyx.net.http.AsyncHTTPBuilder.access$000(AsyncHTTPBuilder.java:59)
at groovyx.net.http.AsyncHTTPBuilder$1.call(AsyncHTTPBuilder.java:131)
at java.util.concurrent.FutureTask.run(FutureTask.java:262)
at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1145)
at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:615)
at java.lang.Thread.run(Thread.java:745)
O problema estava na combinação da configuração do meu tomcat com a implementação.
Abaixo a configuração do tomcat:
<Connector port="8081" protocol="HTTP/1.1"
connectionTimeout="20000"
URIEncoding="UTF-8"
redirectPort="8443"
compression="on"
compressionMinSize="2048"
noCompressionUserAgents="gozilla, traviata"
compressableMimeType="text/html,text/xml,text/json,application/json" />
O problema estava ligado ao fato de eu dar um sleep de 20 segundos e o connectionTimeout ser, também de 20 segundos, pois o protocolo HTTP tem um conceito de
conexões persistentes
Show archive.org snapshot
em que ele define que a conexão estabelecida ficará aberta por X segundos para que possa ser reutilizada, sem a necessidade de abrir uma nova.
No tomcat, a configuração que define o tempo que a conexão fica aberta, esperando para que seja reutilizada é a keepAliveTimeout, que não estava definida na minha configuração, mas como pode-se observar na definição das
configurações do tomcat
Show archive.org snapshot
, caso esta propriedade não seja definida, ela utiliza a mesma definição de connectionTimeout, portanto meu keepAliveTimeout era de 20 segundos.
Voltemos agora ao meu loop que verificava se a tarefa estava concluída, para daí avisar outra aplicação.
O tempo de espera para fazer esta verificação era de 20 segundos
int qtdSegundosEspera = 20
Meu problema consistia na coincidência de o keepAliveTimeout e o tempo de espera assumirem o mesmo valor, pois quando meu cliente fazia o primeiro aviso de que a tarefa ainda não estava concluída, o servidor respondia e, no Header da response vinha Keep-Alive: 20 segundos, pois esta era a definição do meu tomcat; sendo assim, se meu cliente for realizar uma nova requisição dentro destes 20s, ele utiliza a mesma conexão que foi aberta na primeira requisição, que era o que acontecia, mas quando a requisição chegava no servidor, já haviam passado os 20s e a conexão havia sido fechada, pois deu o prazo, daí... The target server failed to respond
Encontrei três possíveis soluções para o caso:
1ª - Mudar a configuração do Tomcat, para que o keepAliveTimeout não fosse mais coincidente com meu tempo de espera
<Connector port="8081" protocol="HTTP/1.1"
connectionTimeout="20000"
URIEncoding="UTF-8"
redirectPort="8443"
compression="on"
compressionMinSize="2048"
keepAliveTimeout="10000"
noCompressionUserAgents="gozilla, traviata"
compressableMimeType="text/html,text/xml,text/json,application/json" />
2ª - Alterar o tempo de espera
int qtdSegundosEspera = 30
while (tarefaEmExecucao) {
registreEventoTarefaEmExecucao()
sleep(qtdSegundosEspera * 1000)
}
3ª - Fechar, explicitamente, as conexões expiradas e as conexões inativas, antes de fazer a requisição Show archive.org snapshot
Object doRequest(RequestConfigDelegate delegate) {
this.getClient().getConnectionManager().closeExpiredConnections();
this.getClient().getConnectionManager().closeIdleConnections(0, TimeUnit.SECONDS);
return doRequest(delegate);
}
Preferindo a última, por não ficar dependente de nenhuma configuração na hora de realizar as requisições.