2012-11-01 25 views
36

Otrzymuję java.io.EOFException przy korzystaniu z szablonu Spring REST na Androida.Użycie szablonu spoczynku sprężyny powoduje EOFException

StackTrace przyczyna brzmi tak:

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 

Inny podobny StackTrace:

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 

To wszystko dzieje się na Androidzie 4.1.2, zainstalowany na moim tablecie Xoom.

Problem pojawia się i znika. Nie jest również wyzwalany przez długie żądania. Część serwera działa na komputerze w sieci lokalnej. Kiedy próbuję uruchomić wywołania API przez curl, działa dobrze.

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; 
} 

Reszta Szablon jest skonfigurowany tak:

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); 

Wywołanie API w pytaniu, który rozbił tutaj jest opisany w Android interfejs oparty adnotacji:

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

Rzeczy próbowałem:

  • Usunięto LoggingInterceptor
  • Called wszystkie wywołania API według CURL
  • Usunięto wezwanie BufferingClientHttpRequestFactory - Pomógł trochę, ale błąd nadal występuje.
  • Przetestowano go na Androidzie 2.3 - błąd nie może być powielana

Czytałem różnych forach posty, wyjątek EOF wydaje się pojawiać, czy adresy są nieprawidłowe, które podwójnie sprawdzane w tym przypadku .

Należy również zauważyć, że po wystąpieniu wyjątku EOF połączenie nie dociera nawet do strony serwera.

Gdzie jest dobry punkt do kontynuowania poszukiwania poprawki? Czy to jest niedogodność związana z Androidem 4.1?

Podczas debugowania tego problemu, znalazłem również https://jira.springsource.org/browse/ANDROID-102, który uniemożliwił mi zobaczenie rzeczywistego błędu (EOF) przed.


Aktualizacja: Wystarczy znaleźć http://code.google.com/p/google-http-java-client/issues/detail?id=116 - może to być związane.

Poprawka jest również opisana w https://codereview.appspot.com/6225045/ - więc mogła zostać scalona w wersji 4.1.

+0

Uwaga: Android-102 jest rozwiązany w https://github.com/ ened/spring-android –

+0

Jak mogę to naprawić w moim projekcie? Pobrałem migawkę od 26 listopada, ale nadal jej nie ma ... – tolgap

Odpowiedz

59

To też mnie trochę, działa Jelly Bean 4.2. Po przeprowadzeniu badań wydaje się, że dzieje się tak z powodu połączenia funkcji Keep-Alive z ustawieniami i przy użyciu standardowego klienta HTTP J2SE, który, jak sądzę, jest połączeniem HttpURLConnection.

Istnieją 2 rozwiązania, które mogę potwierdzić, są poprawne.

1) Wyłącz tryb Keep-Alive.
Dla mnie rozwiązanie podane w odpowiedzi Sebastiana, System.setProperty ("http.keepAlive", "false"); nie działa. Musiałem użyć

i wysłać te nagłówki w HttpEntity w RestTemplate.
Jak wspomniano, to rozwiązanie może mieć wpływ na wydajność

2) Zmień klienta HTTP.
Wiosną na Androida (testowane w wersji 1.0.1.RELEASE, ale może być również we wcześniejszych wersjach) domyślny klient HTTP dla instancji RestTemplate jest określony przez wersję Androida na urządzeniu. API 9 lub nowszy używa HttpURLConnection, starszy używa HTTPClient. Aby jawnie ustawić klienta do starego, wykorzystać

restTemplate.setRequestFactory(new HttpComponentsClientHttpRequestFactory()); 

Więcej informacji można znaleźć tutaj: http://static.springsource.org/spring-android/docs/1.0.1.RELEASE/reference/htmlsingle/#d4e34
Nie jestem pewien, jaki wpływ będzie to miało na wydajność, ale myślę, że to bardziej wydajnych niż app to nie działa.

W każdym razie, mam nadzieję, że ktoś pomaga. Właśnie zmarnowałem tydzień gówno-goniącego za tym.

+0

Wygląda na bardzo dobre wyniki. Dziękujemy za szczegółowe informacje.Czy jest jakiś pomysł, dlaczego keepAlive = false nie zadziałało? Prawdopodobnie zmieniło się to w 4.2, jak sądzę. –

+0

Bez obaw. Skaczę przy każdej szansie, że mogę oddać tę społeczność ratującą życie. Nie jestem pewien, dlaczego ustawienie keepAlive = false nie działa. Nie spowodowało błędu, ale nie zmieniło nagłówków w skontrolowanych pakietach. Waliłem głową o ścianę przez tak długi czas, że nie widziałem żadnego pożytku z próby ustalenia, co dzieje się w tej części. – jaseelder

+0

Dziękuję ... Otrzymujemy losowo błędy ECONNRESET ... rzeczy działają, a następnie przestają działać ... Dodałem poniższy kod, aby ustawić przechwytywacz dla RestTemplate, aby zmienić nagłówki. Po stronie serwera potwierdzono, że właściwość Keep-Alive znajdowała się w nagłówku, mimo że próbowałem używać System.setProperty ("http.keepAlive", "false") .. które również NIE DZIAŁA. Tylko ta opcja wydawała się działać. – lepert

11

http://code.google.com/p/google-http-java-client/issues/detail?id=116 zawiera obejście w najnowszym komentarzu:

defenetly to jakoś związane z połączeniami keepalive.

Kiedy używam: System.setProperty ("http.keepAlive", "false"); problemy znika.

Ale z mojego zrozumienia utrzymywanie połączeń na żywo znacznie wzrosły wydajność, więc lepiej nie wyłączać ich.

Jestem również pewny, że utrzymanie przy życiu powinien być wyłączony dla starych wersji, ale moim urządzeniem jest Jelly Bean.

Po zastosowaniu błąd zniknął.
Wydaje się, że nie jest to w pełni związane ze sprężyną, ale z problemem JB.

0

Niedawno w obliczu tego problemu i będzie w stanie Rozwiązaliśmy ten problem po ustawieniu nagłówki z poniższego fragmentu kodu:

headers.set("Accept-Language", "en-US,en;q=0.8"); 
-1
RestTemplate restTemplate = new RestTemplate(); 
      ((SimpleClientHttpRequestFactory)restTemplate.getRequestFactory()).setOutputStreaming(false); 

restTemplate.postForObject......