I would like to know if there are known issues on Android with HttpUrlConnection and POST requests. We are experiencing intermittent EOFExceptions when making POST requests from an Android client. Retrying the same request will eventually work. Here is a sample stack trace:
java.io.EOFException
at libcore.io.Streams.readAsciiLine(Streams.java:203)
at libcore.net.http.HttpEngine.readResponseHeaders(HttpEngine.java:579)
at libcore.net.http.HttpEngine.readResponse(HttpEngine.java:827)
at libcore.net.http.HttpURLConnectionImpl.getResponse(HttpURLConnectionImpl.java:283)
at libcore.net.http.HttpURLConnectionImpl.getResponseCode(HttpURLConnectionImpl.java:497)
at libcore.net.http.HttpsURLConnectionImpl.getResponseCode(HttpsURLConnectionImpl.java:134)
There are many similar bug reports and posts to stack overflow but I cannot understand if there really is an issue and if so, what versions of Android are affected and what the proposed fix/work around is.
Here are some of the similar reports I am referring to:
Android HttpsUrlConnection eofexception
Android HttpURLConnection throwing EOFException
EOFException and FileNotFoundException in HttpURLConnection getInputStream()
https://code.google.com/p/google-http-java-client/issues/detail?id=213
https://code.google.com/p/android/issues/detail?id=29509
https://code.google.com/p/google-http-java-client/issues/detail?id=230
https://code.google.com/p/android/issues/detail?id=41576
Here is a potential Android framework fix
https://android.googlesource.com/platform/libcore/+/19aa40c81c48ff98ccc7272f2a3c41479b806376
I do know there was an issue with poisoned connections in the connection pool in pre-Froyo but these issues are occurring on new ICS+ devices exclusively. If there were a problem on later devices I would expect some kind of official Android documentation of the issue.
Our conclusion is that there is an issue in the Android platform. Our workaround was to catch the EOFException and retry the request N number of times. Below is the pseudo code:
private static final int MAX_RETRIES = 3;
private ResponseType fetchResult(RequestType request) {
return fetchResult(request, 0);
}
private ResponseType fetchResult(RequestType request, int reentryCount) {
try {
// attempt to execute request
} catch (EOFException e) {
if (reentryCount < MAX_RETRIES) {
fetchResult(request, reentryCount + 1);
}
}
// continue processing response
}
HttpURLConnection library internally maintains a pool of Connections. So, whenever a request is send, it first checks if there is an existing connection already present in the pool, based on which it decides to create a new one.
These connections are nothing but sockets, and this library by default does not closes these sockets. It may sometimes happen that a connection (socket) which is not currently being used and is present in the pool is no longer usable as the Server may choose to terminate the connection after some time. Now, since the connection even though is closed by the server, the library does not knows about it and assumes the connection/socket to be still connected. Thus it sends the new request using this stale connection and hence we get EOFException.
The best way to handle this is to check the Response Headers after each request you send. The server always sends a "Connection: Close" before terminating a connection (HTTP 1.1). So, you can use getHeaderField() and check for "Connection" field. Another thing to note is that server ONLY sends this connection field when it is about to terminate the connection. So, you need to code around this with the possibility of getting a "null" in the normal case (when server is not closing the connection)
This workaround tends to be reliable and performant:
static final int MAX_CONNECTIONS = 5;
T send(..., int failures) throws IOException {
HttpURLConnection connection = null;
try {
// initialize connection...
if (failures > 0 && failures <= MAX_CONNECTIONS) {
connection.setRequestProperty("Connection", "close");
}
// return response (T) from connection...
} catch (EOFException e) {
if (failures <= MAX_CONNECTIONS) {
disconnect(connection);
connection = null;
return send(..., failures + 1);
}
throw e;
} finally {
disconnect(connection);
}
}
void disconnect(HttpURLConnection connection) {
if (connection != null) {
connection.disconnect();
}
}
This implementation relies on the fact that the default number of connections that can be opened with a server is 5 (Froyo - KitKat). This means that up to 5 stale connections may exist, each of which will have to be closed.
After each failed attempt, the Connection:close request property will cause the underlying HTTP engine to close the socket when connection.disconnect() is called. By retrying up to 6 times (max connections + 1), we ensure that the last attempt will always be given a new socket.
The request may experience additional latency if no connections are alive, but that is certainly better than an EOFException. In that case, the final send attempt won't immediately close the freshly opened connection. That's the only practical optimization that can be made.
Instead of relying on the magic default value of 5, you may be able to configure the system property yourself. Keep in mind that this property is accessed by a static initializer block in KitKat's ConnectionPool.java, and it works like this in older Android versions too. As a result, the property may be used before you have a chance to set it.
static final int MAX_CONNECTIONS = 5;
static {
System.setProperty("http.maxConnections", String.valueOf(MAX_CONNECTIONS));
}
Yes. There is a problem in the Android platform, specifically, in Android libcore with version 4.1-4.3.
The problem is introduced in this commit: https://android.googlesource.com/platform/libcore/+/b2b02ac6cd42a69463fd172531aa1f9b9bb887a8
Android 4.4 switched http lib to "okhttp" which doesn't have this problem.
Problem explained as follow:
On android 4.1-4.3, when you are using URLConnection/HttpURLConnection to POST with "ChunkedStreamingMode" or "FixedLengthStreamingMode" set, URLConnection/HttpURLConnection will not do silent retry if the reused connection is stale. You should retry POST at most "http.maxConnections+1" times in your code, just as previous answers suggest.
I suspect it might be the server that is at fault here, and the HttpURLConnection is not as forgiving as other implementations. That was the cause of my EOFException. I suspect in my case this would not be intermittent (fixed it before testing the N retry workaround), so the answers above relate to other issues and be a correct solution in those cases.
My server was using python SimpleHTTPServer and I was wrongly assuming all I needed to do to indicate success was the following:
self.send_response(200)
That sends the initial response header line, a server and a date header, but leaves the stream in the state where you are able to send additional headers too. HTTP requires an additional new line after headers to indicate they are finished. It appears if this new line isn't present when you attempt to get the result body InputStream or response code etc with HttpURLConnection then it throws the EOFException (which is actually reasonable, thinking about it). Some HTTP clients did accept the short response and reported the success result code which lead to me perhaps unfairly pointing the finger at HttpURLConnection.
I changed my server to do this instead:
self.send_response(200)
self.send_header("Content-Length", "0")
self.end_headers()
No more EOFException with that code.
This worked for me.
public ResponseObject sendPOST(String urlPrefix, JSONObject payload) throws JSONException {
String line;
StringBuffer jsonString = new StringBuffer();
ResponseObject response = new ResponseObject();
try {
URL url = new URL(POST_URL);
HttpURLConnection connection = (HttpURLConnection) url.openConnection();
connection.setDoInput(true);
connection.setDoOutput(true);
connection.setReadTimeout(10000);
connection.setConnectTimeout(15000);
connection.setRequestMethod("POST");
connection.setRequestProperty("Accept", "application/json");
connection.setRequestProperty("Content-Type", "application/json; charset=UTF-8");
OutputStream os = connection.getOutputStream();
os.write(payload.toString().getBytes("UTF-8"));
os.close();
BufferedReader br = new BufferedReader(new InputStreamReader(connection.getInputStream()));
while ((line = br.readLine()) != null) {
jsonString.append(line);
}
response.setResponseMessage(connection.getResponseMessage());
response.setResponseReturnCode(connection.getResponseCode());
br.close();
connection.disconnect();
} catch (Exception e) {
Log.w("Exception ",e);
return response;
}
String json = jsonString.toString();
response.setResponseJsonString(json);
return response;
}
connection.addRequestProperty("Accept-Encoding", "gzip");
is the answer
Related
I am posting some data using POST API call with the following function:
conn = (HttpURLConnection) url.openConnection();
conn.setConnectTimeout(40000);
conn.setReadTimeout(40000);
conn.setDoOutput(true);
conn.setUseCaches(false);
conn.setDefaultUseCaches(false);
conn.setRequestProperty("Connection", "Keep-Alive");
conn.addRequestProperty("Cache-Control", "max-age=0");
conn.setRequestMethod("POST");
conn.setRequestProperty("Content-Type", "application/json");
conn.setRequestProperty(JsonKeys.DATE_KEY, getTimeStamp());
if (params != null) {
// Write serialized JSON data to output stream.
OutputStreamWriter wr = new OutputStreamWriter(conn.getOutputStream());
wr.write(params.toString());
wr.close();
}
int status = conn.getResponseCode();
StringBuilder sb = new StringBuilder();
try {
if (status == HttpURLConnection.HTTP_OK || status == HTTP_CREATED) {
InputStream is = conn.getInputStream();
int ch;
while ((ch = is.read()) != -1) {
sb.append((char) ch);
}
is.close();
return new JSONObject(sb.toString());
} else {
throw new IOException("We encountered some problem with the request. Please try again later");
} catch (JSONException jex) {
MessageUtils.showLog(jex);
} finally {
if (conn != null) {
MessageUtils.showLog("connection disconnected");
conn.disconnect();
}
}
This POST request needs to wait for at least 40 seconds for the response from the server. This code works perfectly fine on most of the devices but on some devices the request ends abruptly after 10 seconds.I get a 502 response (like after 10 seconds). The server log has no trace of a response being sent (confirmed by my back-end developer). The error stream for 502 that I receive in my front end contains this:
<HEAD><TITLE>Server Hangup</TITLE></HEAD>
I have no idea as to why this is behaving differently for different mobile devices or different Android APIs. Can somebody help me with an insight to this problem or a solution? I have been stuck on this problem for hours and cant find an explanation/solution to it.
This is a stack trace we are receiving in the apache access log:
[Wed May 01 13:54:54.142877 2019] [proxy:error] [pid 23000:tid 140687508961231] (111)Connection refused: AH00957: HTTP: attempt to connect to 127.0.0.1:9000 (localhost) failed
[Wed May 01 13:54:54.142995 2019] [proxy:error] [pid 23000:tid 140687508961231] AH00959: ap_proxy_connect_backend disabling worker for (localhost) for 60s
[Wed May 01 13:54:54.143001 2019] [proxy_http:error] [pid 23000:tid 140687508961231] [client 160.32.32.202:256560] AH01114: HTTP: failed to make connection to backend: localhost
The proxy configuration in our server is something like this:
<Location "/v2/">
ProxyPass http://0.0.0.0:9000/ retry=0 timeout=500
ProxyPassReverse http://0.0.0.0:9000
</Location>
ProxyPreserveHost On
ProxyRequests Off
<Proxy *>
Order deny,allow
Allow from all
</Proxy>
502 is actually a 'Bad Gateway'. This is definitely a server issue. You have some apache module that checks for stable connection and that module detects that your connection is not ok.
From the updated question it is visible that the property 'retry' on your proxy is 0. Here is the meaning from documentation:
https://httpd.apache.org/docs/2.4/mod/mod_proxy.html#proxypass
retry
Connection pool worker retry timeout in seconds. If the connection pool worker to the backend server is in the error state, Apache httpd will not forward any requests to that server until the timeout expires. This enables to shut down the backend server for maintenance and bring it back online later. A value of 0 means always retry workers in an error state with no timeout.
So definitely the problem is in different request and not in different android. Please check your requests. The requests you send might be different and server can detect connection as dead letting you think that problem is in android. You can also play with retry parameter and set it to default retry=60.
I am facing a strange issue, and I am not able to debug it out. I have implemented a logic for uploading stream of data and am using Volley for the same, I have customized a logic little bit in HurlStack, addBodyIfExists api,so that body of type "application/octet-stream" can be handled.
My logic is to post progress to user, so that UI can be updated indicating user progress in upload, below my logic for same.
int toRead = length; // File length
byte[] data = new byte[4096];
connection.setDoOutput(true);
if(length != -1) {
connection.setFixedLengthStreamingMode(length);
} else {
connection.setChunkedStreamingMode(4096);
}
OutputStream os;
int i;
int count;
os = connection.getOutputStream();
int progress= 0;
try {
for(i = 0; (count= is.read(data)) > 0; ++i) { // is, is not null and contains a valid input stream
os.write(data, 0, count); // at this line am getting unexpected end of stream
progress+= count;
if(i % 20 == 0) {
rs.deliverProgress(progress, 0L);
progress= 0;
}
}
os.flush();
} finally {
if(is != null) {
is.close();
}
if(os != null) {
os.close();
}
}
on executing above code am getting this, although I have verified, output stream is not null, neither do input stream, it fails in first iteration of read loop itself, am seeing it has read 4096 bytes and then trying to write the same.
java.net.ProtocolException: unexpected end of stream
at com.android.okhttp.internal.http.HttpConnection$FixedLengthSink.close(HttpConnection.java:326)
at com.android.okio.RealBufferedSink.close(RealBufferedSink.java:174)
at com.android.okio.RealBufferedSink$1.close(RealBufferedSink.java:142)
any help in debugging above will he highly appreciated.
This may help you :
That exception is thrown by FixedLengthInputStream when the expected number of bytes (usually set in the content-length header of the response) is larger than the actual data in the response.
Check that the content-length header is correct. (If you're supplying your own value for the content length, make sure it is correct.)
It would help to see your code that sets up the input stream.
Already Fixed it, please add "Accept-Encoding", "identity" in header, then the server-side will get command that it will not modify the response, then send back to Clients.
If you have checked everywhere in your code and tried every solution in stackoverflow and github but the issue still occurs, and you have only tested your code on emulator, then, you should try to run your code on your real device instead. Maybe it will work, or maybe it won't, but if you feel desperate, just have a try, seriously. I was astonished when I happened to find that my code ran with bugs on emulator everytime but successfully on my mobile phone. Besides, the code also ran sucessfully on others' android emulators. So I guess there is something wrong in my android studio configuration that I can't find out. I have no idea why this happen, just like we don't know why "Clean Project/Invalidate caches" sometimes works better than any solution.
It is a little strange that your data length might be unknown.
Is it a media file? Or a live stream?
Anyway, I tried to upload my live stream data. And it happened in the same error.
I added this setting to the Connection and solved my problem.
Transfer-Encoding : chunked
("setChunkedStreamingMode" didn't work. I still don't know why.)
This happens for me on android emulator and doesn't happen on my physical android device.
I was doing GET request to flask server running on 0.0.0.0 on my laptop from the android app.
To fix it on the emulator, add the servers ip address in the emulators proxy.
see How to set up Android emulator proxy settings
The exact problem i had was unexpected end of stream retrofit
Using Robolectric 2.3-SNAPSHOT, I want to test an object that'll execute a request in the background. In order to isolate it, I'm trying to mock the HttpResponse returned, without success after some hours invested.
I've created a project that anyone can clone. Simly run ./gradlew check https://github.com/Maragues/RobolectricDummyProject (git clone https://github.com/Maragues/RobolectricDummyProject.git)
I've tried
Robolectric.setDefaultHttpResponse(200, "my_mocked_word");
MockWebServer (https://code.google.com/p/mockwebserver/)
But the tests fail because they query the real URL
private static final String MOCKED_WORD = "MOCKED";
#Test
public void mockedRequestUsingMockServer() throws Exception {
mMockWebServer.enqueue(new MockResponse().setBody(MOCKED_WORD));
mMockWebServer.play();
Robolectric.getFakeHttpLayer().interceptHttpRequests(false);
Robolectric.getFakeHttpLayer().interceptResponseContent(false);
String result = request.loadDataFromNetwork();
assertEquals(MOCKED_WORD, result);
}
#Test
public void mockedRequestUsingRobolectric() throws Exception {
Robolectric.setDefaultHttpResponse(200, MOCKED_WORD);
String result = request.loadDataFromNetwork();
assertEquals(MOCKED_WORD, result);
}
The code executing the request
public String loadDataFromNetwork() throws Exception {
// With Uri.Builder class we can build our url is a safe manner
Uri.Builder uriBuilder = Uri.parse("http://robospice-sample.appspot.com/reverse").buildUpon();
uriBuilder.appendQueryParameter("word", word);
String url = uriBuilder.build().toString();
HttpURLConnection urlConnection = (HttpURLConnection) new URL(url)
.openConnection();
String result = IOUtils.toString(urlConnection.getInputStream());
urlConnection.disconnect();
return result;
}
Possibly related questions
Can't capture HTTP request with robolectric (I've tried that without success. Perhaps I'm missing something)
Anyone had success mocking HttpRequests with Robolectric? (I'm not using eclipse)
You're disabling Roboelectric's HTTP layer, so you're using the real HTTP layer. This means that there's no clever magic happening under the hood of your test: when you send an HTTP request, it's really going out onto the internet (as you are seeing).
MockWebServer doesn't stop this. It just sets up a web server locally, that your test can connect to.
So to resolve this problem, you need to stop attempting to connect to a real server, and instead, connect to the mock server. To do this, yo need to inject/set the URL in the request.
#Test
public void mockedRequestUsingMockServer() throws Exception {
mMockWebServer = new MockWebServer();
mMockWebServer.play();
mMockWebServer.enqueue(new MockResponse().setResponseCode(200).setBody(MOCKED_WORD));
request.myUrl = mMockWebServer.getUrl("/");
String result = request.loadDataFromNetwork();
assertEquals(MOCKED_WORD, result);
mMockWebServer.shutdown();
}
It turns out that Robolectric's FakeHttpLayer only works with Apache's HttpClient, which is highly discouraged on versions greater than Froyo. Extracted from Robolectric's Google Group
That being said, the usage of HttpUrlConnection will cause you trouble. I'd try to use Android's implementation of HttpClient where possible, since Robolectric intercepts all calls to that library and lets you set up canned responses to your HTTP calls. We're looking at doing the same for HttpUrlConnection, though it's not clear when that'll happen.
Apart from that, a unit test should not need to mock the HTTP layer. My approach was wrong from the beginning.
You can try this(ref:https://github.com/square/okhttp/tree/master/mockwebserver).
// Create a MockWebServer. These are lean enough that you can create a new
// instance for every unit test.
MockWebServer server = new MockWebServer();
// Schedule some responses.
server.enqueue(new MockResponse().setBody("it's all cool"));
// Start the server.
server.play();
// Ask the server for its URL. You'll need this to make HTTP requests.
//Http is my own http executor.
Http.Response response = http.get(server.getUrl("/"));
then, you can compare the response to server.enqueue(new MockResponse().setBody("it's all cool"));
MockWebServer is a part of okhttp https://github.com/square/okhttp/tree/master/mockwebserver. the URLConnectionImpl in android 4.4 have been changed from defaultHttpClient to Okhttp.
I am developing an android app, and i need to download some simple XML-Files with authorization from an url. I already got something working in java on my local computer [Code at the end]. When i try to use this function in my android app, LogCat throws thousands of errors. I included Internet permission, i will attach the errors to my post.
So, heres my download function. i hardcoded the base64 string, because it will not change with the time...:
public String getXmlFromUri(String url)
{
String strFileContents = new String("");
try{
String base64 = "Basic " + "hardcodedBase64String";
URL pURL = new URL(url);
URLConnection conn = pURL.openConnection();
conn.setRequestProperty("Authorization", base64);
BufferedReader in=new BufferedReader(new InputStreamReader(conn.getInputStream()));
String data = new String("");
while((data = in.readLine()) != null)
{
strFileContents += data;
}
in.close();
}catch(IOException e) {}
if(strFileContents == "") strFileContents = "An error occured or the downloaded file was empty!";
return strFileContents;
}
My logcat is very long, so i uploaded it at pastebin: http://pastebin.com/DhFra9SG
The function isnt finished yes, and I am currently only using it on the website where i need authorization.
To conclude: It works on windows, and it does not work on android!
it
Here it is:
FATAL EXCEPTION: android.os.NetworkOnMainThreadException
It crashes because you are trying to perform a network operation on UI (main) thread.
Network operations are considered to be long running operations and as a consequence they could block the UI. Thus, starting with API Level 11 if you attempt to perform a network operation on main thread, this exception is thrown.
Long running operations should run in a separate thread, for example you may use the AsyncTask to get the XML file from the server.
Based in your last piece of code and logcat the issue comes from the call of the Console method.
Caused by: android.view.ViewRootImpl$CalledFromWrongThreadException:
Only the original thread that created a view hierarchy can touch its
views.
Actually, you can not change/add some View object from the doInBackground method. If you want to change the UI, it must be from the onPreExecute(), onPostExecute(Result) and onProgressUpdate(Progress...) methods.
Check the AsyncTask reference, and more specifically The 4 steps section.
BR
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:
Removed LoggingInterceptor
Called all API calls by CURL
Removed call BufferingClientHttpRequestFactory - Helped a little but the error still occurs.
Tested it on Android 2.3 - the error cannot be reproduced
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.
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.
http://code.google.com/p/google-http-java-client/issues/detail?id=116 contains a workaround in the latest comment:
This is defenetly somehow connected with keepAlive connections.
When I use: System.setProperty("http.keepAlive", "false"); problems
disappears.
But from my understanding keep alive connections are greatly increase
performance so it is better not to disable them.
Im also awere that keep alive should be disabled for old versions, but
my device is Jelly Bean.
Once applied the error disappeared.
Seems it's not entirely related to Spring, but a JB problem.
Recently I faced this issue and will able to resolved this issue after setting headers with following piece of code :
headers.set("Accept-Language", "en-US,en;q=0.8");
RestTemplate restTemplate = new RestTemplate();
((SimpleClientHttpRequestFactory)restTemplate.getRequestFactory()).setOutputStreaming(false);
restTemplate.postForObject......