web-dev-qa-db-fra.com

L'utilisation du modèle Spring Rest provoque une exception EOF

Je reçois le Java.io.EOFException lorsque j'utilise le modèle Spring REST sur Android.

La cause stacktrace se lit comme ceci:

Caused by: Java.io.EOFException
at libcore.io.Streams.readAsciiLine(Streams.Java:203)
at libcore.net.http.HttpEngine.readResponseHeaders(HttpEngine.Java:560)
at libcore.net.http.HttpEngine.readResponse(HttpEngine.Java:813)
at libcore.net.http.HttpURLConnectionImpl.getResponse(HttpURLConnectionImpl.Java:274)
at libcore.net.http.HttpURLConnectionImpl.getResponseCode(HttpURLConnectionImpl.Java:486)
at org.springframework.http.client.SimpleClientHttpResponse.getRawStatusCode(SimpleClientHttpResponse.Java:49)
at org.springframework.http.client.SimpleClientHttpResponse.getStatusCode(SimpleClientHttpResponse.Java:55)
at org.springframework.http.client.BufferingClientHttpResponseWrapper.getStatusCode(BufferingClientHttpResponseWrapper.Java:47)
at com.company.util.LoggingClientHttpRequestInterceptor.intercept(LoggingClientHttpRequestInterceptor.Java:33)
at org.springframework.http.client.InterceptingClientHttpRequest$RequestExecution.execute(InterceptingClientHttpRequest.Java:81)
at com.company.api.interceptor.AuthTokenInterceptor.intercept(AuthTokenInterceptor.Java:51)
at org.springframework.http.client.InterceptingClientHttpRequest$RequestExecution.execute(InterceptingClientHttpRequest.Java:81)
at org.springframework.http.client.InterceptingClientHttpRequest.executeInternal(InterceptingClientHttpRequest.Java:67)
at org.springframework.http.client.AbstractBufferingClientHttpRequest.executeInternal(AbstractBufferingClientHttpRequest.Java:46)
at org.springframework.http.client.AbstractClientHttpRequest.execute(AbstractClientHttpRequest.Java:63)
at org.springframework.web.client.RestTemplate.doExecute(RestTemplate.Java:475)
... 14 more

Un autre stacktrace similaire:

org.springframework.web.client.ResourceAccessException: I/O error: null; nested exception is Java.io.EOFException
at org.springframework.web.client.RestTemplate.doExecute(RestTemplate.Java:490)
at org.springframework.web.client.RestTemplate.execute(RestTemplate.Java:438)
at org.springframework.web.client.RestTemplate.exchange(RestTemplate.Java:414)
at com.company.api.ApiClient_.logLoginAttempt(ApiClient_.Java:299)
at com.company.security.CompanyAuthenticationService$2.onCreateCall(CompanyAuthenticationService.Java:206)
at com.company.api.SafeApiCall.doInBackground(SafeApiCall.Java:49)
at com.company.api.SafeApiCall.doInBackground(SafeApiCall.Java:22)
at Android.os.AsyncTask$2.call(AsyncTask.Java:287)
at Java.util.concurrent.FutureTask$Sync.innerRun(FutureTask.Java:305)
at Java.util.concurrent.FutureTask.run(FutureTask.Java:137)
at Android.os.AsyncTask$SerialExecutor$1.run(AsyncTask.Java:230)
at Java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.Java:1076)
at Java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.Java:569)
at Java.lang.Thread.run(Thread.Java:856)
Caused by: Java.io.EOFException
at libcore.io.Streams.readAsciiLine(Streams.Java:203)
at libcore.net.http.HttpEngine.readResponseHeaders(HttpEngine.Java:560)
at libcore.net.http.HttpEngine.readResponse(HttpEngine.Java:813)
at libcore.net.http.HttpURLConnectionImpl.getResponse(HttpURLConnectionImpl.Java:274)
at libcore.net.http.HttpURLConnectionImpl.getResponseCode(HttpURLConnectionImpl.Java:486)
at org.springframework.http.client.SimpleClientHttpResponse.getRawStatusCode(SimpleClientHttpResponse.Java:49)
at org.springframework.http.client.SimpleClientHttpResponse.getStatusCode(SimpleClientHttpResponse.Java:55)
at org.springframework.http.client.BufferingClientHttpResponseWrapper.getStatusCode(BufferingClientHttpResponseWrapper.Java:47)
at org.springframework.web.client.DefaultResponseErrorHandler.hasError(DefaultResponseErrorHandler.Java:46)
at org.springframework.web.client.RestTemplate.doExecute(RestTemplate.Java:476)
... 13 more

Tout cela se passe sur Android 4.1.2, installé sur ma tablette Xoom.

Le problème apparaît et disparaît. Ce n'est pas déclenché par de longues demandes non plus. La partie serveur s'exécute sur une machine du réseau local. Lorsque j'essaie d'exécuter les appels d'API via curl, cela fonctionne parfaitement.

AuthTokenInterceptor:

@Override
public ClientHttpResponse intercept(HttpRequest request, byte[] data, ClientHttpRequestExecution execution) throws IOException {
    HttpHeaders headers = request.getHeaders();
    if (!StringUtils.isEmpty(mAuthToken)) {
        headers.add((mIsOAuth ? "Authorization" : "authToken"), (mIsOAuth ? "Bearer " : "") + mAuthToken);
    }
    return execution.execute(request, data);
}

LoggingClientHttpRequestInterceptor:

/** {@inheritDoc} */
@Override
public ClientHttpResponse intercept(HttpRequest httpRequest, byte[] bytes, ClientHttpRequestExecution clientHttpRequestExecution) throws IOException {
    Log.d(TAG, "To     : " + httpRequest.getURI());
    Log.d(TAG, "Method : " + httpRequest.getMethod().name());
    Log.d(TAG, "Data   : " + new String(bytes));

    for (Object key : httpRequest.getHeaders().keySet()) {
        Log.d(TAG, "Header <" + key + ">: " + httpRequest.getHeaders().get(key));
    }

    final ClientHttpResponse response = clientHttpRequestExecution.execute(httpRequest, bytes);

    if (response != null) {
        Log.d(TAG, "Response: " + response.getStatusCode());
        if (response.getBody() != null) {
            Log.d(TAG, "Response: " + convertStreamToString(response.getBody()));
        }
    } else {
        Log.d(TAG, "Response: " + response);
    }

    return response;
}

Le modèle de repos est configuré comme ceci:

final RestTemplate template = new RestTemplate(false);
template.getMessageConverters().add(new MappingJacksonHttpMessageConverter());
template.setRequestFactory(new BufferingClientHttpRequestFactory(template.getRequestFactory()));
ApiUtils.addAuthTokenHeaderToRestTemplate(template, mAuthToken, false);
ApiUtils.addRequestLoggingToRestTemplate(template);

L'appel d'API en question qui s'est écrasé ici est décrit dans l'interface basée sur les annotations Android:

@Post("/user/memberships")
@Accept(MediaType.APPLICATION_JSON)
CompanyApiResponse saveGroupMembership(UserGroupMembership membership) throws RestClientException;

Choses que j'ai essayées:

  • LoggingInterceptor supprimé
  • Appelé tous les appels d'API par CURL
  • Appel supprimé BufferingClientHttpRequestFactory - Aide un peu, mais l'erreur se produit toujours.
  • Testé sur Android 2.3 - l'erreur ne peut pas être reproduite

J'ai lu divers messages sur les forums. L'exception EOF semble apparaître si les URL sont incorrectes, ce que j'ai vérifié dans ce cas.

À noter également, une fois que l'exception EOF se produit, l'appel n'atteint même pas le côté serveur.

Où serait un bon point pour continuer la recherche d'une solution? Est-ce un inconvénient pour Android 4.1?

Lors du débogage de ce problème, j’ai également trouvé https://jira.springsource.org/browse/Android-102 qui m’avait empêché de voir la véritable erreur (EOF) auparavant.


Mise à jour: Vient de trouver http://code.google.com/p/google-http-Java-client/issues/detail?id=116 - il pourrait être lié.

Le correctif est également décrit dans https://codereview.appspot.com/6225045/ - de sorte qu'il a peut-être été fusionné pour la version 4.1.

36
Sebastian Roth

Celui-ci m'a mordu aussi, avec Jelly Bean 4.2. Après des recherches, il semble que cela se produise à cause d'une combinaison de Keep-Alive étant définie et à l'aide du client HTTP J2SE standard, que je crois être HttpURLConnection.

Il y a 2 solutions que je peux confirmer sont correctes.

1) Éteignez Keep-Alive.
Pour moi, la solution donnée dans la réponse de Sebastian, System.setProperty ("http.keepAlive", "false"); n'a pas fonctionné. Je devais utiliser

HttpHeaders headers = new HttpHeaders();
headers.set("Connection", "Close");

et envoyez ces en-têtes dans une HttpEntity dans RestTemplate.
Comme mentionné, cette solution pourrait avoir un impact sur les performances

2) Changer le client HTTP.
Dans Spring pour Android (testé sur la version 1.0.1.RELEASE, mais pourrait également figurer dans les versions précédentes), le client HTTP par défaut pour une instance RestTemplate est déterminé par la version d'Android sur le périphérique. API 9 ou plus récente utilise HttpURLConnection, ancienne utilise HTTPClient. Pour définir explicitement le client sur l’ancien, utilisez

restTemplate.setRequestFactory(new HttpComponentsClientHttpRequestFactory());

Plus d'informations peuvent être trouvées ici: http://static.springsource.org/spring-Android/docs/1.0.1.RELEASE/reference/htmlsingle/#d4e34
Je ne suis pas sûr de l'impact que cela aura sur les performances, mais j'imagine que c'est plus performant qu'une application qui ne fonctionne pas.

Quoi qu'il en soit, espérons que cela aide quelqu'un. Je viens de perdre une semaine à la chasse à l'oie sauvage.

59
jaseelder

http://code.google.com/p/google-http-Java-client/issues/detail?id=116 contient une solution de contournement dans le dernier commentaire:

Ceci est en quelque sorte lié aux connexions keepAlive.

Quand j'utilise: System.setProperty ("http.keepAlive", "false"); problèmes disparaît.

Mais, à ma connaissance, les connexions sont toujours plus nombreuses Il est donc préférable de ne pas les désactiver.

Je pense également que Keep Alive devrait être désactivé pour les anciennes versions, mais mon appareil est Jelly Bean.

Une fois appliqué, l'erreur a disparu.
Semble que ce n'est pas entièrement lié au printemps, mais un problème de JB.

11
Sebastian Roth

Récemment, j'ai rencontré ce problème et je pourrai le résoudre après avoir défini les en-têtes avec le morceau de code suivant:

headers.set("Accept-Language", "en-US,en;q=0.8");
0
Bajarang Agarwal