Использование шаблона Spring Rest вызывает исключение EOFException

Я получаю java.io.EOFExceptionпри использовании шаблона весеннего отдыха на Android.

причина stacktrace выглядит так:

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

еще один подобный 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.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.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.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

все это происходит на Android 4.1.2, установленном на моем планшете Xoom.

проблема появляется и исчезает. Это не вызвано длинными запросами. Серверная часть выполняется на компьютере в локальной сети. Когда я пытаюсь запустить вызовы API через curl, он работает просто отлично.

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

шаблон Rest настроен следующим образом:

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

вызов API, о котором идет речь, который разбился здесь, описан в интерфейсе на основе аннотаций Android:

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

вещи, которые я пробовал:

  • Удален LoggingInterceptor
  • назвал всех Вызовы API по CURL
  • удален вызов BufferingClientHttpRequestFactory -помогло немного, но ошибка все еще происходит.
  • протестировал его на Android 2.3 -ошибка не может быть воспроизведена

Я читал различные сообщения на форумах, исключение EOF, похоже, появляется, если URL-адреса неверны, что я дважды проверил в этом случае.

также обратите внимание, что после возникновения исключения EOF вызов даже не достигает сторона сервера.

где было бы хорошо продолжить поиск исправления? Это Android 4.1 неудобства?

при отладке этой проблемы я также нашел https://jira.springsource.org/browse/ANDROID-102 Что помешало мне увидеть реальную ошибку (EOF) раньше.


обновление: нашел http://code.google.com/p/google-http-java-client/issues/detail?id=116 - это может быть связанный.

исправление также описано в https://codereview.appspot.com/6225045/ - таким образом, он мог быть объединен для 4.1.

4 ответов


Это один бит меня, а также, работает Jelly Bean 4.2. После исследования кажется, что это происходит из-за комбинации Keep-Alive set и используя стандартный HTTP-клиент J2SE, который я считаю HttpURLConnection.

есть 2 решения, которые я могу подтвердить, являются правильными.

1) выключите Keep-Alive.
Для меня решение, данное в ответе Себастьяна,система.методов-setproperty("http-данных.keepAlive", "false"); не работает. Мне пришлось использовать

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

и отправьте эти заголовки в HttpEntity в RestTemplate.
Как уже упоминалось, это решение может повлиять на производительность

2) Измените HTTP-клиент.
Весной для Android (протестировано на 1.0.1.Выпуск, но может быть и в более ранних выпусках) HTTP-клиент по умолчанию для экземпляра RestTemplate определяется версией Android на устройстве. API 9 или новее использует HttpURLConnection, более старый использует HTTPClient. Явно задать клиент к старому, используйте

restTemplate.setRequestFactory(new HttpComponentsClientHttpRequestFactory());

больше информации можно найти здесь: http://static.springsource.org/spring-android/docs/1.0.1.RELEASE/reference/htmlsingle/#d4e34
Я не уверен, какое влияние это окажет на производительность, но я думаю, что это более эффективно, чем приложение, которое не работает.

в любом случае, надеюсь, что это поможет кому-то. Я просто потратил неделю, гоняясь за этим.


http://code.google.com/p/google-http-java-client/issues/detail?id=116 содержит обходной путь в последнем комментарии:

Это как-то связано с keepAlive-соединениями.

когда я использую:


недавно я столкнулся с этой проблемой и смогу решить эту проблему после установки заголовков со следующим фрагментом кода:

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

RestTemplate restTemplate = new RestTemplate();
            ((SimpleClientHttpRequestFactory)restTemplate.getRequestFactory()).setOutputStreaming(false);

restTemplate.postForObject......