org.apache.http.NoHttpResponseException

Posted . 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.

Profile picture of João Paulo
João Paulo
Last edit
João Paulo
Posted by João Paulo to ZeroGlosa (2015-09-01 12:03)