org.apache.http.NoHttpResponseException

Posted Over 8 years ago. Visible to the public.

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.

keepAliveTimeout

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.

João Paulo
Last edit
Over 8 years ago
João Paulo
Posted by João Paulo to ZeroGlosa (2015-09-01 12:03)