I'm receiving java.io.EOFException
's when using Spring REST template on Android.
The stacktrace cause reads like this:
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
Another similar 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
This is all happening on Android 4.1.2, installed on my Xoom tablet.
The problem appears and disappears. It's not triggered by long requests either. The server part is running on a machine within the local network. When I try to run the API Calls through curl
, it works just fine.
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; }
The Rest Template is configured like this:
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);
The API call in question that crashed here is described in the Android annotations based interface:
@Post("/user/memberships") @Accept(MediaType.APPLICATION_JSON) CompanyApiResponse saveGroupMembership(UserGroupMembership membership) throws RestClientException;
Things I've tried:
I've been reading various forums posts, the EOF exception seems to appear if URLs are incorrect, which I double checked in this case.
Also of note, once the EOF Exception occurs, the call not even reaches the server side.
Where would be a good point to continue the search for a fix? Is this a Android 4.1 inconvenience?
While debugging this issue, I also found https://jira.springsource.org/browse/ANDROID-102 which prevented me from seeing the real error (EOF) before.
Update: Just found http://code.google.com/p/google-http-java-client/issues/detail?id=116 - it might be related.
The fix is also outlined in https://codereview.appspot.com/6225045/ - so it might've been merged for 4.1.
Signals that an end of file or end of stream has been reached unexpectedly during input. This exception is mainly used by data input streams to signal end of stream. Note that many other input operations return a special value on end of stream rather than throwing an exception.
RestTemplate provides a synchronous way of consuming Rest services, which means it will block the thread until it receives a response. RestTemplate is deprecated since Spring 5 which means it's not really that future proof.
Rest Template is used to create applications that consume RESTful Web Services. You can use the exchange() method to consume the web services for all HTTP methods. The code given below shows how to create Bean for Rest Template to auto wiring the Rest Template object.
This one bit me as well, running Jelly Bean 4.2. After researching, it seems that it's happening because of a combination of Keep-Alive being set and using the standard J2SE HTTP Client, which I believe is HttpURLConnection.
There are 2 solutions that I can confirm are correct.
1) Switch off Keep-Alive.
For me, the solution given in Sebastian's answer, System.setProperty("http.keepAlive", "false"); didn't work. I had to use
HttpHeaders headers = new HttpHeaders(); headers.set("Connection", "Close");
and send those headers in an HttpEntity in the RestTemplate.
As mentioned, this solution could have an impact on performance
2) Change the HTTP Client.
In Spring for Android (tested on 1.0.1.RELEASE, but could be in earlier releases too) the default HTTP Client for a RestTemplate instance is determined by the version of Android on the device. API 9 or newer uses HttpURLConnection, older uses HTTPClient. To explicitly set the client to the old one, use
restTemplate.setRequestFactory(new HttpComponentsClientHttpRequestFactory());
More info can be found here: http://static.springsource.org/spring-android/docs/1.0.1.RELEASE/reference/htmlsingle/#d4e34
I'm not sure what impact this will have on performance, but I guess it's more performant than an app that doesn't work.
Anyway, hope that helps someone. I just wasted a week wild-goose-chasing this one down.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With