Подтвердить что ты не робот

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

Я получаю java.io.EOFException при использовании Spring шаблона REST на 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

Другая подобная стек:

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

Это все происходит на 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 Template" настроен следующим образом:

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.

4b9b3361

Ответ 1

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

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

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

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

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

2) Измените HTTP-клиент.
В Spring для Android (протестировано на 1.0.1.RELEASE, но может быть и в более ранних версиях) 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
Я не уверен, какое влияние это повлияет на производительность, но я думаю, что это более результативно, чем приложение, которое не работает.

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

Ответ 2

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

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

Когда я использую: System.setProperty( "http.keepAlive", "false" ); проблемы исчезает.

Но из моего понимания, живые связи значительно увеличиваются так что лучше не отключать их.

Мне также следует, что сохранить жизнь нужно отключить для старых версий, но моим устройством является Jelly Bean.

После применения ошибки исчезли.
Кажется, он не полностью связан с Spring, но проблема с JB.

Ответ 3

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

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

Ответ 4

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

restTemplate.postForObject......