I'm trying to post a test File to a spring rest servlet deployed on tomcat using Android. I'm developing on Android 4.1.2, but I have verified same problem on 4.0.3.
The problem is that the file upload requires a very long time (about 70 seconds for a 4MB file), also in local network. The time is equiparable using a 3g connection. I've excluded that it could be a server problem: executing the same call with curl it takes 1 / 2 seconds, and using apache as backend results are the same.
Using HttpClient works fine.
I'm using Spring Android RestClient 1.0.1.RELEASE and, given Android version and the fact that I'm not overriding default behaviour, it uses HttpUrlConnection instead of HttpClient to make http requests.
I have also implemented my custom ClientHttpRequestFactory in order to manipulate some details of SSL connection and I have defined my own implementation of ClientHttpRequestInterceptor in order to modify authentication header.
I have also set setBufferRequestBody(false) in order to avoid OutOfMemoryException on big files. But this property have no effects on time required.
MyClientHttpRequestFactory:
public class MyClientHttpRequestFactory extends SimpleClientHttpRequestFactory{
#Override
protected void prepareConnection(HttpURLConnection connection, String httpMethod) throws IOException {
super.prepareConnection(connection, httpMethod);
connection.setConnectTimeout(240 * 1000);
connection.setReadTimeout(240 * 1000);
if ("post".equals(httpMethod.toLowerCase())) {
setBufferRequestBody(false);
}else {
setBufferRequestBody(true);
}
}
#Override
protected HttpURLConnection openConnection(URL url, Proxy proxy) throws IOException {
final HttpURLConnection httpUrlConnection = super.openConnection(url, proxy);
if (url.getProtocol().toLowerCase().equals("https")
&&
settings.selfSignedCert().get())
{
try {
((HttpsURLConnection)httpUrlConnection).setSSLSocketFactory(getSSLSocketFactory());
((HttpsURLConnection)httpUrlConnection).setHostnameVerifier(new NullHostnameVerifier());
} catch (Exception e) {
MyLog.e(LOG_TAG, "OpenConnection", e);
}
}
return httpUrlConnection;
}
MyClientHttpRequestInterceptor:
public class MyClientHttpRequestInterceptor implements ClientHttpRequestInterceptor {
#Override
public ClientHttpResponse intercept(HttpRequest request, byte[] body, ClientHttpRequestExecution execution) throws IOException {
final HttpHeaders headers = request.getHeaders();
headers.setAuthorization(new HttpBasicAuthentication( settings.username().get(), settings.password().get()));
if (settings.enable_gzip().get()) {
headers.setAcceptEncoding(ContentCodingType.GZIP);
}
return execution.execute(request, body);
}
}
And here my Rest call:
List<ClientHttpRequestInterceptor> interceptors = Arrays.asList((ClientHttpRequestInterceptor)myClientHttpRequestInterceptor);
RestTemplate restTemplate = new RestTemplate();
restTemplate.getMessageConverters().add(new FormHttpMessageConverter());
restTemplate.getMessageConverters().add(new StringHttpMessageConverter());
restTemplate.getMessageConverters().add(new MappingJackson2HttpMessageConverter());
restTemplate.setInterceptors(interceptors);
MultiValueMap<String, Object> parts = new LinkedMultiValueMap<String, Object>();
parts.add("file", new FileSystemResource("/sdcard/test/4MB_file"));
HttpEntity<MultiValueMap> requestEntity = new HttpEntity<MultiValueMap>(parts);
restTemplate.exchange(myUrl, HttpMethod.POST, requestEntity, Integer.class).getBody();
}
Looking at Spring Android source code, the next lines of code my request is passing through are:
public ClientHttpRequest createRequest(URI uri, HttpMethod httpMethod) throws IOException {
HttpURLConnection connection = openConnection(uri.toURL(), this.proxy);
prepareConnection(connection, httpMethod.name());
if (this.bufferRequestBody) {
return new SimpleBufferingClientHttpRequest(connection);
} else {
return new SimpleStreamingClientHttpRequest(connection, this.chunkSize);
}
}
Because of this.bufferRequestBody is false, return new SimpleStreamingClientHttpRequest(connection, this.chunkSize); is executed (with chunkSize = 0)
SimpleStreamingClientHttpRequest(HttpURLConnection connection, int chunkSize) {
this.connection = connection;
this.chunkSize = chunkSize;
// Bugs with reusing connections in Android versions older than Froyo (2.2)
if (olderThanFroyo) {
System.setProperty("http.keepAlive", "false");
}
}
and then:
ClientHttpRequest delegate = requestFactory.createRequest(request.getURI(), request.getMethod());
delegate.getHeaders().putAll(request.getHeaders());
if (body.length > 0) {
FileCopyUtils.copy(body, delegate.getBody());
}
return delegate.execute();
From here is all android subsystem I think..
I have dumped tcp traffic and analyzed it:
POST /urlWherePost HTTP/1.1
Content-Type: multipart/form-data;boundary=nKwsP85ZyyzSDuAqozCTuZOSxwF1jLAtd0FECUPF
Authorization: Basic xxxxxxxxxxxxxxxxxxxxxxxxxx=
Accept-Encoding: gzip
User-Agent: Dalvik/1.6.0 (Linux; U; Android 4.1.2; sdk Build/MASTER)
Host: 192.168.168.225:8080
Connection: Keep-Alive
Content-Length: 4096225
--nKwsP85ZyyzSDuAqozCTuZOSxwF1jLAtd0FECUPF
Content-Disposition: form-data; name="file"; filename="4MB_file"
Content-Type: application/octet-stream
Content-Length: 4096000
I've tryed to re-create similar request with curl:
curl --verbose
-H "Connection: Keep-Alive"
-H "Content-Type: multipart/form-data"
-H "Accept-Encoding: gzip"
-H "Content-Disposition: form-data; name=\"file\"; filename=\"4MB_file\""
-H "Content-Type: application/octet-stream"
--user xxx:xxx
-X POST
--form file=#4MB_file
http://192.168.168.225:8080/urlWherePost
but with curl the post is ok.
Posting json data is not a problem (maybe small body size). But when I try to send "big" files the time increase.
Looking in DDMS shell, on Network Statistics I've also found that the network throughput is never over 250kb in TX. There seems to be a bootleneck, but how to investigate it? Where I can look, which parameter can I change?
Thank you for any suggestion!
Have you tried using the MultipartEntity method? I had the same problem when downloading big amounts of JSON data from the server, but I switched to this method and caught all the data that the server provided me.
HttpClient httpclient = new DefaultHttpClient();
HttpPost httppost = new HttpPost("http://myurl.com");
try {
MultipartEntity entity = new MultipartEntity();
entity.addPart("type", new StringBody("json"));
entity.addPart("data", new JSONObject(data));
httppost.setEntity(entity);
HttpResponse response = httpclient.execute(httppost);
} catch (ClientProtocolException e) {
} catch (IOException e) {
} catch (JSONException e){
}
To upload big files, You can use this library android-async-http
For simple and easy to use, I recommend this lib https://github.com/koush/ion.
I use it on my project and it works perfectly.
Related
I am working on a project which was working fine before Christmas but suddenly doesn't without any changes being made.
The project involves a C++ which listens on a particular port and listens to post requests in a REST API to process the data and store in a database.
It consists of an Android library which gathers information and then sends this as an HTTP POST to the rest API on the C++ app.
The C++ app prints out the HTTP response that was received straight from receiving it on the socket before any processing done. First the android app has to send an initalisation request to the C++ app, the C++ prints the request and shows post data was sent, and successfully initalises and sends a response back to android including a session cookie. I then re-use the HttpClient within Android to post the next request which contains a fair amount of data but this request doesn't work.
When stepping through the android library I can see the post values have been successfully set and are being used to perform the HTTP request, however, the C++ app only receives the HTTP headers, not any post data.
If in the second request, I replace the post data with only a couple of post fields, the C++ then sees the post data, so it looks like the DefaultHTTPClient in Android isn't sending the post data if the post data is quite large.
Below is how I am posting the data in Android
if (httpClient == null)
{
//AndroidHttpClient client = new AndroidHttpClient();
httpClient = new DefaultHttpClient();
}
HttpParams httpParams = httpClient.getParams();
HttpConnectionParams.setConnectionTimeout(httpParams, 3000);
HttpConnectionParams.setSoTimeout(httpParams, 3000);
HttpPost httpPost = new HttpPost(serverURL);
//httpPost.setHeader("Content-Type", "application/x-www-form-urlencoded");
httpPost.setHeader("Authorisation-Token", authorisationToken);
httpPost.setHeader("Connection", "close");
//httpPost.setHeader("Cookie", "SESSIONID=zd8d5n3kucysl4idug1911m7ye");
httpPost.setEntity(new UrlEncodedFormEntity(postData, "UTF-8"));
String headers = "";
responseHandler = new ResponseHandler<String>()
{
#Override
public String handleResponse(HttpResponse httpResponse) throws ClientProtocolException, IOException
{
return EntityUtils.toString(httpResponse.getEntity());
}
};
responseBody = httpClient.execute(httpPost, responseHandler);
Log.d("Response", responseBody);
//httpClient = null;
if (responseBody != null && !responseBody.isEmpty())
{
httpClient.getConnectionManager().closeIdleConnections(0, TimeUnit.NANOSECONDS);
JSONObject jsonObject = new JSONObject(responseBody);
return jsonObject;
}
else
{
httpClient.getConnectionManager().closeIdleConnections(0, TimeUnit.NANOSECONDS);
return null;
}
As an example the post data is being created as follows:
postData = new ArrayList<>();
postData.add(new BasicNameValuePair("Test", "Item 1"));
postData.add(new BasicNameValuePair("Test 2", "Item 2"));
and the above is passed in to the execute command of the ASyncTask.
Below is how I am receiving the data on the socket from C++
string LinuxSocket::receiveDataOnSocket(int *socket)
{
string receiveData = "";
char * temp = NULL;
int bytesReceived = 0;
do
{
bytesReceived = recv(*socket, this->buffer, this->bufferLength, 0);
if (bytesReceived < 0)
{
stringstream logstream;
logstream << "Failed to read data on socket. Error: " << strerror(bytesReceived);
this->bitsLibrary->writeToLog(logstream.str(), "LinuxSocket", "receiveDataOnSocket");
this->closeSocket(socket);
throw SocketException(strerror(bytesReceived));
}
//If we got here then we should be able to get some data
temp = new char[bytesReceived + 1];
strncpy(temp, this->buffer, bytesReceived);
temp[bytesReceived] = '\0';
receiveData.append(temp);
delete[] temp;
temp = NULL;
memset(this->buffer, 0, this->bufferLength);
} while (bytesReceived == this->bufferLength);
return receiveData;
}
The post data that I am sending is a follows:
POST /crash HTTP/1.1
User-Agent: My Android User Agent
Authorisation-Token: DlzSIkx4ro*OatHCV6epfWY0F
Connection: close
Content-Length: 1231
Content-Type: application/x-www-form-urlencoded
Host: 192.168.1.123:500
Cookie: SESSIONID=6rc5q1db0z8lupe5uij5ten3mw
Cookie2: $Version=1
Severity=Critical&DeviceID=36e85611db16c7fa&VersionName=1.5&DeviceType=Android&ROMBuild=sdk_gphone_x86-userdebug+8.0.0+OSR1.170901.056+4497355+dev-keys&KernelVersion=3.18.81%2B&DeviceBrand=google&DeviceModel=Android+SDK+built+for+x86&APILevel=26&ScreenResolution=1080+x+1776&Locale=English&MobileNetwork=Android&CrashType=Handled&ExceptionType=java.lang.Exception&Stacktrace=java.lang.Exception%3A+Standard+Exception+been+thrown%0A%09at+com.MyCompany.MyApp.MainActivity%242.onClick%28MainActivity.java%3A67%29%0A%09at+android.view.View.performClick%28View.java%3A6256%29%0A%09at+android.view.View%24PerformClick.run%28View.java%3A24701%29%0A%09at+android.os.Handler.handleCallback%28Handler.java%3A789%29%0A%09at+android.os.Handler.dispatchMessage%28Handler.java%3A98%29%0A%09at+android.os.Looper.loop%28Looper.java%3A164%29%0A%09at+android.app.ActivityThread.main%28ActivityThread.java%3A6541%29%0A%09at+java.lang.reflect.Method.invoke%28Native+Method%29%0A%09at+com.android.internal.os.Zygote%24MethodAndArgsCaller.run%28Zygote.java%3A240%29%0A%09at+com.android.internal.os.ZygoteInit.main%28ZygoteInit.java%3A767%29%0A&CustomProperty=%7B%22Test+Non+Json+Property%22%3A%22Here+is+my+value%22%7D&AppID=15869700
As an example the post data that successfully works is a follows:
POST /initialise HTTP/1.1
Authorisation-Token: DlzSIkx4ro*OatHCV6epfWY0F
Connection: close
Content-Length: 48
Content-Type: application/x-www-form-urlencoded
Host: 192.168.1.123:500
User-Agent: My Android User Agent
ApplicationID=15869700&DeviceID=36e85611db16c7fa
I've also captured the request from Android and sent it from a Rest API Test Client Insomnia.Rest and sent this to the Rest API and the C++ successfully sees all of the post data, so it looks like the problem is the Android library won't send post data if its of a certain size.
Is this the case, and how can I get round this?
I found the issue with this, there were two separate things that I've done to resolve this.
The issue with no post data at all being sent, I changed from the org.apache.DefaultHTTPClient to the OkHTTPClient. From looking at Google it looks like the apache version has been deprecated and the OkHTTPClient is the preferred HTTP client anyway. This then sent some post data but not everything. This brings me to the second fix (I don't think the issue was actually with the apache client, I think it was my C++ receiving data on the socket).
The second fix I did was to change my receiveDataOnSocket function in the C++ app.
When I've worked on socket to socket communication then normally if the buffer is full, then usually I can expect to receive more data, if the buffer is only partly full, then all data is sent and I return the received data.
It looks like HTTP doesn't necessarily send as much as the buffer on receiving socket can handle, I've therefore changed my receive data on socket to process the data as its being received to look for the Content-Length header, and the length of the body that I may have, then on each subsequent recv call, I increment the current body count by the receivedBytes count and if the body length is equal to the content length I stop receiving data on the socket and return the HTTP request.
I want to use the Video Cognitive Service in Android.
The sample that Microsoft provided is used in C#.
The video function is sending an URL to the server,
So I think it is possible using HTTP POST to send an URL in Android.
http://ppt.cc/V1piA
The problem I met is that I don't know the URL format in "application/octet-stream", and I didn't see the example on the Microsoft website.
Is it possible using HTTP POST in Android to upload a downloaded video to the server, and I can get the analysis result from the server?
If possible, what is the format of the HTTP POST to send request to the server?
Thanks.
You may try something like this to send image files for cognitive-services face detect.
Using org.apache.httpcomponents::httpclient :
#Test
public void testSendBinary() throws MalformedURLException {
File picfile = new File("app/sampledata/my_file.jpeg");
if (!picfile.exists()) throw new AssertionError();
HttpClient httpclient = HttpClients.createDefault();
try {
URIBuilder builder = new URIBuilder("https://westcentralus.api.cognitive.microsoft.com/face/v1.0/detect");
builder.setParameter("returnFaceId", "true");
builder.setParameter("returnFaceLandmarks", "false");
URI uri = builder.build();
HttpPost request = new HttpPost(uri);
request.setHeader("Content-Type", "application/octet-stream");
request.setHeader("Ocp-Apim-Subscription-Key", "***");
// Request body
request.setEntity(new FileEntity(picfile));
HttpResponse response = httpclient.execute(request);
HttpEntity entity = response.getEntity();
if (entity != null) {
System.out.println(EntityUtils.toString(entity));
}
} catch (Exception e) {
System.out.println(e.getMessage());
}
}
HTTP POST refers to the HTTP method 'POST', application/octet-stream refers to the media type - in this case a stream of application specific octets or bytes.
This is, unfortunately, very subjective as the mechanism for uploading content via HTTP action may be preferred one way or another. Suffice it to say, you will create an InputStream of your content, format a POST request using the mechanism of your choosing:
straight Java
HTTPClient
Making sure to set the content-type of the POST to application/octet-stream.
After performing the post, consult your API documentation for expected return types.
I am doing a REST client in Android to consume a webService hosted in Amazon EC2.
When a acess the URL in bowser, it's work fine. But when i tried to acess the webservice in the android app, i received a error 404.
URL urla = new URL(SERVICE_URL);
HttpGet method= new HttpGet(new URI(SERVICE_URL));
HttpClient client = new DefaultHttpClient();
HttpGet request = new HttpGet();
request.setURI(new URI(SERVICE_URL));
HttpResponse response = client.execute(method);
int responseCode = response.getStatusLine().getStatusCode();
When I provide the rule "All" Access In the amazon EC2, it's work fine, but i believe it's not a better way.
All Rule:
*Ports: All
Protocol: All
Source: All*
Does anyone know a better way to access the REST webservice hosted in the EC2 with a client android?
Try using AsyncHttpClient library instead thats simple and easy. Here is a sample code
AsyncHttpClient client;
client.get("http://ec2-xx-xxx-xx-xx.compute-1.amazonaws.com:8080/MyService1/jaxrs/helloworld",
new AsyncHttpResponseHandler() {
// When the response returned by REST has Http
// response code '200'
#Override
public void onSuccess(String response) {
try {
// JSON Object
tv1.setText(response);
} catch (Exception e) {
tv1.setText(e.getMessage());
}
}
});
Simple give the public DNS of your instance with port of server, then path to the function call of your service.
This is how I did it.
I'm using the below code to fetch some data from server: (it happens on button onclick)
#Override
protected ArrayList<Category> doInBackground(String... arg0) {
ArrayList<Category> result = new ArrayList<Category>();
JSONArray array = new JSONArray();
BufferedReader in = null;
try
{
HttpParams params = new BasicHttpParams();
params.setParameter(CoreProtocolPNames.PROTOCOL_VERSION, HttpVersion.HTTP_1_1);
HttpClient httpclient = new DefaultHttpClient(params);
HttpGet request = new HttpGet();
URI website = new URI(_URL);
request.setURI(website);
HttpResponse response = httpclient.execute(request);
in = new BufferedReader(new InputStreamReader(
response.getEntity().getContent()));
String data = in.readLine();
array = new JSONArray(data);
for(int i = 0;i < array.length();i++)
{
JSONObject object = array.getJSONObject(i);
Category category = new Category(object.getInt("Id"), object.getString("Name"));
result.add(category);
}
}
catch(Throwable t)
{
Log.e("Error getting categories", t.getMessage());
}
return result;
}
the problem is that 8 out of 10 tries,it throws the following exception:
org.apache.http.conn.HttpHostConnectException: Connection to http://kiagallery.ir refused
and most of the times it takes a long time to fetch the data, but sometimes it's fast as a lightning bolt, the data is not big, here's the data that's supposed to be fetched at the moment:
[{"Id":44,"Name":"Collection 101"},{"Id":45,"Name":"local 01"}]
so my question is, how come sometimes it can fetch the data at reasonable speed and sometimes it throws an exception, could be the network speed? because my workplace got a terrible network, I also tried at home and the result was better but the exception occurred still once in a while
Additional Info: I used curl to fetch it and it was fast, paste the url in my browser and it was fast.
if you suspect the server-side , you can just remove the android stack from the equation and test directly in curl, using switches -V and 'time' to measure as much stuff as you want.
when you have investigated server directly and are no longer suspicious of server-side, you return to your android stack.
example POST on REST endpoint:
curl -X POST -H "X-Parse-Application-Id: ovv" -H "X-Parse-REST-API-Key: heoj" -H "X-Parse-Session-Token: i9ds" -H "Content-Type: application/json" --data #media1.json https://api.parse.com/1/classes/MediaItem
Im writing an android app that connects to my own Jersey rest client. HTTP get commands work fine, but im having trouble with my POSTs where im trying to send something to the server. I get a 405 sent back, so it seems like the server cannot match the request up with the resource methods. Any thoughts? Test code below...
REST SERVER
#PUT
#Consumes(MultiPartMediaTypes.MULTIPART_MIXED)
public Response putResponse(MultiPart multiPart) {
System.out.println(multiPart.getBodyParts());
return null;
}
ANDROID CLIENT
HttpClient httpclient = new DefaultHttpClient();
HttpPost request = new HttpPost(URL + "responses");
request.addHeader("Content-Type", "multipart/mixed");
MultipartEntity entity = new MultipartEntity(
HttpMultipartMode.BROWSER_COMPATIBLE);
entity.addPart("Testpart1", new StringBody("<testxml></testxml>"));
entity.addPart("image1", new StringBody("imagedata1"));
request.setEntity(entity);
request.addHeader("deviceId", deviceId);
ResponseHandler<String> handler = new BasicResponseHandler();
try {
String result = httpclient.execute(request, handler);
Log.i("tag", result);
return result;
} catch (ClientProtocolException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
} finally {
httpclient.getConnectionManager().shutdown();
}
return null;
TCPMon Traffic shows the following
POST /Maintenance_Server/rest/responses HTTP/1.1
Content-Type: multipart/mixed
deviceId: xxxxx
Content-Length: 244
Host: 127.0.0.1:12345
Connection: Keep-Alive
User-Agent: Apache-HttpClient/UNAVAILABLE (java 1.4)
--jju2JFDOlzJ4LQo7YkrJYLuwDUHmB5b7
Content-Disposition: form-data; name="Testpart1"
<testxml></testxml>
--jju2JFDOlzJ4LQo7YkrJYLuwDUHmB5b7
Content-Disposition: form-data; name="image1"
imagedata1
--jju2JFDOlzJ4LQo7YkrJYLuwDUHmB5b7--
Thanks
Mark
You are sending HTTP POST, but on the server side you declare a handler for HTTP PUT only. So, it's not able to match POST to any method hence 405. Change the annotation on your resource method from #PUT to #POST or send HTTP PUT instead of POST by the client.