My upload code as below:
String end = "\r\n";
String twoHyphens = "--";
String boundary = "*****";
try {
URL url = new URL(ActionUrl);
HttpURLConnection con = (HttpURLConnection) url.openConnection();
con.setDoInput(true);
con.setDoOutput(true);
con.setUseCaches(false);
con.setRequestMethod("POST");
con.setRequestProperty("Connection", "Keep-Alive");
con.setRequestProperty("Accept", "text/*");
con.setRequestProperty("Content-Type", "multipart/form-data; boundary=" + boundary);
DataOutputStream ds = new DataOutputStream(con.getOutputStream());
ds.writeBytes(twoHyphens + boundary + end);
ds.writeBytes("Content-Disposition: form-data;" + "name=\"folder\"" + end + end);
ds.write(SavePath.getBytes("UTF-8"));
ds.writeBytes(end);
ds.writeBytes(twoHyphens + boundary + end);
ds.writeBytes("Content-Disposition: form-data;" + "name=\"Filedata\"; filename=\"");
ds.write(FileName.getBytes("UTF-8"));
ds.writeBytes("\"" + end);
ds.writeBytes(end);
FileInputStream fStream = new FileInputStream(uploadFilepath+""+FileName);
int bufferSize = 1024;
byte[] buffer = new byte[bufferSize];
int length = -1;
int pro = 0;
while((length = fStream.read(buffer)) != -1) {
ds.write(buffer, 0, length);
}
ds.writeBytes(end);
ds.writeBytes(twoHyphens + boundary + twoHyphens + end);
fStream.close();
ds.flush();
InputStream is = con.getInputStream();
int ch;
StringBuffer b = new StringBuffer();
while((ch = is.read()) != -1) {
b.append((char)ch);
}
ds.close();
}
catch(Exception e) {
e.printStackTrace();
}
It can works while upload a smaller file.
But while more 16 mb, it will upload fail and show the OutOfMemory error.
I think it cause by put all data in buffer.
So I want to make it to send data while buffer save 1024 bytes.
But I no idea to do that.
May anyone help me to do it?
brian, you should add
con.setChunkedStreamingMode(0);
before
DataOutputStream ds = new DataOutputStream(con.getOutputStream());
if your server could support chunked mode, or add
con.setFixedLengthStreamingMode(packet_size);
where packet_size = upload_file_size + header_size.
You should confirm at what point your error occurs. I suspect that it's during reading the response. In this case, it seems that server may be responding with a lot of data that you place in the StringBuffer. Do you actually need to consume the entire response and keep it in memory? If it's a file, save it rather than keeping in memory.
I did some more research and here is one other possibility. Android JVM by default has 16mb max heap. See this for some details.
On the other hand, if your server does not actually consume the data, most of it will reside on the client. So if you have more than max heap of data the client will fail.
So I suspect that your server just does not read the data from the stream.
The following class (which is a snippet of relevant parts of your code) illustrates the problem. Run it on any JVM like following:
java -Xmx16m -cp . Test
and it will produce OOM very quickly. In fact, much earlier than expected.
import java.io.*;
import java.net.*;
public class Test {
public static void main(String argv[]) throws Exception {
new Thread() {
public void run() {
try {
new ServerSocket(12000).accept();
} catch (Throwable t) {
t.printStackTrace();
}
}
}.start();
URL url = new URL("http://localhost:12000/");
HttpURLConnection con = (HttpURLConnection) url.openConnection();
con.setDoInput(true);
con.setDoOutput(true);
con.setUseCaches(false);
DataOutputStream ds = new DataOutputStream(con.getOutputStream());
int bufferSize = 1024;
byte[] buffer = new byte[bufferSize];
for (int i=0;i<100000;i++) {
ds.write(buffer, 0, 1024);
System.out.println("Written chunk " + i);
}
ds.flush();
}
}
Related
I'm developing an app by enables the user to take a photo and send it to a Keras model for prediction. This model is already deployed in a Google App Engine Service with a Python script that uses Flask for receiving via POST request the image and calling the model to make the prediction. Here's the Python code:
import numpy as np
import flask
import io
import logging
import tensorflow as tf
from keras.preprocessing.image import img_to_array
from keras.applications import imagenet_utils
from keras.models import load_model
from PIL import Image
# initialize our Flask application and the Keras model
app = flask.Flask(__name__)
app.config['PROPAGATE_EXCEPTIONS'] = True
model = None
def recortar(image):
# Function that centers and crop image. Please, asume that it works properly. Return is a numpy array.
return image
#app.route("/predict", methods=["POST"])
def predict():
model = load_model('modelo_1.h5')
graph = tf.get_default_graph()
data = {"success": False}
if flask.request.method == "POST":
if flask.request.files.get("image"):
# read the image in PIL format
image = flask.request.files["image"].read()
image = Image.open(io.BytesIO(image))
image = recortar(image)
app.logger.info('Tamaño: '+str(image.size))
image = img_to_array(image)
image = np.expand_dims(image, axis=0)
with graph.as_default():
preds = model.predict(image)
data['predictions'] = str(np.squeeze(preds).tolist())
data["success"] = True
return flask.jsonify(data)
else:
return "No se ha obtenido la imagen"
else:
return "El HTTP request no era POST"
# if this is the main thread of execution first load the model and
# then start the server
if __name__ == "__main__":
print(("* Loading Keras model and Flask starting server..."
"please wait until server has fully started"))
app.debug = True
app.run()
Sending the image via curl works perfectly: as expected, I obtain a JSON response from the server containing the prediction. Here's the CURL command and the server response:
>> curl -X POST -F image=#nevus.jpg 'https://example.com/predict'
{"predictions":"[0.7404708862304688, 0.25952914357185364]","success":true}
Then I try to repeat the same process, but through an Android app, but I get a 500 error as response. When checking the logs on Stackdriver Error Reporting, I see the following stacktrace:AttributeError:
'NoneType' object has no attribute 'size'
at predict (/home/vmagent/app/main.py:73)
at dispatch_request (/env/lib/python3.6/site-packages/flask/app.py:1799)
at full_dispatch_request (/env/lib/python3.6/site-packages/flask/app.py:1813)
at reraise (/env/lib/python3.6/site-packages/flask/_compat.py:35)
at handle_user_exception (/env/lib/python3.6/site-packages/flask/app.py:1718)
at full_dispatch_request (/env/lib/python3.6/site-packages/flask/app.py:1815)
at wsgi_app (/env/lib/python3.6/site-packages/flask/app.py:2292)
at reraise (/env/lib/python3.6/site-packages/flask/_compat.py:35)
at handle_exception (/env/lib/python3.6/site-packages/flask/app.py:1741)
at wsgi_app (/env/lib/python3.6/site-packages/flask/app.py:2295)
at __call__ (/env/lib/python3.6/site-packages/flask/app.py:2309)
at handle_request (/env/lib/python3.6/site-packages/gunicorn/workers/sync.py:176)
at handle (/env/lib/python3.6/site-packages/gunicorn/workers/sync.py:135)
This error refers to image object, so I assume that, as the code was working properly before, the error must be in the way I send the image through the HTTP request. Recall that the image is taken when a user click a button, because this button sends an intent for taking the photo. When the photo is taken, the user can click a send button, whose code I post bellow. Note that orientedBitmap corresponds to the photo taken in bitmap format.
btn_enviarfoto.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View v) {
Log.d(TAG, "Botón \"enviar\" pulsado. Codificando imagen.");
ByteArrayOutputStream stream = new ByteArrayOutputStream();
orientedBitmap.compress(Bitmap.CompressFormat.PNG, 100, stream);
byte[] byteArray = stream.toByteArray();
orientedBitmap.recycle();
uploadToServer(byteArray);
}
});
uploadToServer just calls the execute methong of AsynchTask class as shown bellow:
private void uploadToServer(byte[] data) {
Bitmap bitmapOrg = BitmapFactory.decodeByteArray(data, 0, data.length);
Log.d(TAG, "Imagen codificada. Enviando al servidor.");
ObtenerPrediccionTask task = new ObtenerPrediccionTask();
task.execute(bitmapOrg);
}
And finally and most important, this is the code for the ObtenerPrediccionTask class:
public class ObtenerPrediccionTask extends AsyncTask<Bitmap, Void, String> {
#Override
protected String doInBackground(Bitmap... imagen) {
ByteArrayOutputStream bao = new ByteArrayOutputStream();
HttpURLConnection connection = null;
DataOutputStream outputStream = null;
String probabilidad_melanoma = "";
JsonReader jsonReader = null;
try {
for (int i = 0; i < imagen.length; i++) {
Bitmap imagen2 = imagen[i];
imagen2.compress(Bitmap.CompressFormat.JPEG, 90, bao);
byte[] ba = bao.toByteArray();
InputStream fileInputStream = new ByteArrayInputStream(ba);
URL url = new URL("https://example.com/predict"); // not the real URL
String lineEnd = "\r\n";
String twoHyphens = "--";
String boundary = "xxxxxxxx";
String str = twoHyphens + boundary + lineEnd;
connection = (HttpURLConnection) url.openConnection();
// Allow Inputs & Outputs
connection.setDoInput(true);
connection.setDoOutput(true);
connection.setUseCaches(false);
// Enable POST method
connection.setRequestMethod("POST");
connection.setRequestProperty("Connection", "Keep-Alive");
connection.setRequestProperty("Content-Type", "multipart/form-data;boundary=" + boundary);
outputStream = new DataOutputStream(connection.getOutputStream());
outputStream.writeBytes(twoHyphens + boundary + lineEnd);
outputStream.writeBytes("Content-Disposition: form-data; name=\"" +
"image" + "\";filename=\"" +
"foto.jpg" + "\"" + lineEnd);
outputStream.writeBytes(lineEnd);
int bytesAvailable = fileInputStream.available();
int bufferSize = Math.min(bytesAvailable, 1024);
byte[] buffer = new byte[bufferSize];
// Read file
int bytesRead = fileInputStream.read(buffer, 0, bufferSize);
while (bytesRead > 0) {
outputStream.write(buffer, 0, bufferSize);
bytesAvailable = fileInputStream.available();
bufferSize = Math.min(bytesAvailable, 1024);
bytesRead = fileInputStream.read(buffer, 0, bufferSize);
}
outputStream.writeBytes(lineEnd);
outputStream.writeBytes(twoHyphens + boundary + twoHyphens + lineEnd);
// Responses from the server (code and message)
int responseCode = connection.getResponseCode();
connection.getResponseMessage();
fileInputStream.close();
outputStream.flush();
outputStream.close();
if (responseCode == HttpURLConnection.HTTP_OK) {
InputStream responseStream = new
BufferedInputStream(connection.getInputStream());
BufferedReader responseStreamReader =
new BufferedReader(new InputStreamReader(responseStream));
String line = "";
StringBuilder stringBuilder = new StringBuilder();
while ((line = responseStreamReader.readLine()) != null) {
stringBuilder.append(line).append("\n");
}
responseStreamReader.close();
String response = stringBuilder.toString();
Log.d(TAG, "Imagen recibida por el servidor y pasada al modelo. Esta es la respuesta: " + response);
jsonReader = new JsonReader(new StringReader(response));
probabilidad_melanoma = readJson(jsonReader);
} else {
Log.d(TAG, Integer.toString(responseCode));
}
}
return probabilidad_melanoma;
} catch (MalformedURLException malformedURLException) {
Log.e(TAG, malformedURLException.toString());
return null;
} catch (IOException io) {
Log.e(TAG, io.toString());
return null;
} finally {
if (connection != null) {
connection.disconnect();
}
}
}
protected void onPostExecute(String probabilidad_melanoma) {
if (probabilidad_melanoma != null) {
Log.d(TAG, "Probabilidad melanoma: " + probabilidad_melanoma);
} else {
Log.w(TAG, "La respuesta ha sido nula");
}
}
}
readJson function is also working properly, so don't get bothered by it.
This last chunk of code is the result of an extensive search in SO of a way to properly send an image, but as nothing has worked yet, I've run out of ideas. What's the problem with my code?
The crash traceback indicates that at this line image is None:
app.logger.info('Tamaño: '+str(image.size))
Which means that recortar() returns None, despite your comments:
# Function that centers and crop image. Please, asume that it works properly. Return is a numpy array.
So your the error must be in the way I send the image through the HTTP request assumption might be wrong. Before spending time on that I'd first add checks to ensure that recortar() works properly.
can you explain why android client parse multiparform data to non ascii chat. while file upload working good using postman
here is my app.js code
var multipart = require('connect-multiparty');
var apiRoutes = require('./routes/apiRoutes');
app.set('views', path.join(__dirname, 'views'));
app.use(logger('dev'));
app.use(bodyParser.json({limit: '50mb'}));
app.use(bodyParser.urlencoded([{extended: false},{ uploadDir:path.join(__dirname, 'uploads') }, {parameterLimit:100000}, {limit: '50mb'}]));
app.use(cookieParser());
app.use(express.static(path.join(__dirname, 'public')));
app.use(express.static(path.join(__dirname, 'uploads')));
app.use(multipart());
app.use(apiRoutes);
and apiRoutes point my upload function contain simple print req param.using postman it working good
console.log("mediaChat called", req.body, req.files);
response
mediaChat called { apiKey: '123' } { media:
{ fieldName: 'media',
originalFilename: 'default.png',
path: '/tmp/KFnwsKGp-f4woTaBH6aPR-qa.png',
headers:
{ 'content-disposition': 'form-data; name="media"; filename="default.png"',
'content-type': 'image/png' },
size: 716,
name: 'default.png',
type: 'image/png' } }
here is my android client code (Note this code working file with php $_FILE but not working with express)
com.info.acruss.wave;
import android.os.AsyncTask;
import android.util.Log;
import java.io.BufferedReader;
import java.io.DataOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.InputStreamReader;
import java.net.HttpURLConnection;
import java.net.MalformedURLException;
import java.net.URL;
/**
* Created by Naitik on 9/20/2016.
*/
public class UploadImage extends AsyncTask<Void, Void, String> {
String CallingURL;
URL url;
OnTaskCompleted myListener;
private static final String TAG = "UploadImage";
int timeoutMilli = 60000;
String sourceFileUri;
String ApiKey,Type;
public UploadImage(String sourceFileUri, String URL,String apiKey, String type,
OnTaskCompleted listener) {
Log.e("Uploading", "API:" + URL);
this.sourceFileUri = sourceFileUri;
this.CallingURL = URL;
this.myListener = listener;
this.ApiKey=apiKey;
this.Type=type;
try {
url = new URL(CallingURL);
Log.e(TAG, "Url : " + CallingURL);
} catch (MalformedURLException e) {
e.printStackTrace();
}
}
#Override
protected String doInBackground(Void... params) {
String fileName = sourceFileUri;
HttpURLConnection conn = null;
DataOutputStream dos = null;
String lineEnd = "\r\n";
String twoHyphens = "--";
String boundary = "*****";
int bytesRead, bytesAvailable, bufferSize;
byte[] buffer;
int maxBufferSize = 10 * 1024 * 1024;
File sourceFile = new File(sourceFileUri);
if (!sourceFile.isFile()) {
Log.e("UploadImage", "Source File Does not exist";
return null;
}
String serverResponseMessage = "";
try {
// open a URL connection to the Servlet
FileInputStream fileInputStream = new FileInputStream(sourceFile);
URL url = new URL(CallingURL);
// Open a HTTP connection to the URL
conn = (HttpURLConnection) url.openConnection();
conn.setDoInput(true); // Allow Inputs
conn.setDoOutput(true); // Allow Outputs
conn.setUseCaches(false); // Don't use a Cached Copy
conn.setRequestMethod("POST";
conn.setReadTimeout(timeoutMilli);
conn.setConnectTimeout(timeoutMilli);
conn.setRequestProperty("Connection", "Keep-Alive";
conn.setRequestProperty("ENCTYPE", "multipart/form-data";
conn.setRequestProperty("Content-Type", "multipart/form-data;boundary=" + boundary);
conn.setRequestProperty("media", fileName);
conn.setRequestProperty("apiKey",ApiKey);
conn.setRequestProperty("media_type",Type);
conn.setRequestProperty("media", fileName);
dos = new DataOutputStream(conn.getOutputStream());
dos.writeBytes(twoHyphens + boundary + lineEnd);
dos.writeBytes("Content-Disposition: form-data; name='media';filename='" + fileName + "'" + lineEnd);
dos.writeBytes(lineEnd);
// create a buffer of maximum size
bytesAvailable = fileInputStream.available();
bufferSize = Math.min(bytesAvailable, maxBufferSize);
buffer = new byte[bufferSize];
// read file and write it into form...
bytesRead = fileInputStream.read(buffer, 0, bufferSize);
while (bytesRead > 0) {
dos.write(buffer, 0, bufferSize);
bytesAvailable = fileInputStream.available();
bufferSize = Math.min(bytesAvailable, maxBufferSize);
bytesRead = fileInputStream.read(buffer, 0, bufferSize);
}
// send multipart form data necesssary after file data...
dos.writeBytes(lineEnd);
dos.writeBytes(twoHyphens + boundary + twoHyphens + lineEnd);
// Responses from the server (code and message)
int serverResponseCode = conn.getResponseCode();
BufferedReader br = new BufferedReader(new InputStreamReader(conn.getInputStream()));
String line = "";
StringBuilder responseOutput = new StringBuilder();
while ((line = br.readLine()) != null) {
responseOutput.append(line);
}
br.close();
serverResponseMessage = responseOutput.toString();//output.toString();
Log.e("uploadFile", "HTTP Response is : " + serverResponseMessage);
if (serverResponseCode == 200) {
//status code 200
//status ok
}
//close the streams //
fileInputStream.close();
dos.flush();
dos.close();
} catch (MalformedURLException ex) {
ex.printStackTrace();
Log.e("Upload file to server", "error: " + ex.getMessage(), ex);
} catch (Exception e) {
Log.e("Upload file to server", "error: " + e.getMessage(), e);
e.printStackTrace();
}
return serverResponseMessage;
}
#Override
protected void onPostExecute(String result) {
super.onPostExecute(result);
Log.e("Result", "" + result);
if (myListener != null)
if (result != null) {
myListener.onFileUploadComplete(result);
} else {
myListener.onFileUploadComplete("";
}
}
public interface OnTaskCompleted {
void onFileUploadComplete(String result);
}
}
using android this show wired response as below
mediaChat called { null: '����\u0000\u0010JFIF\u0000\u0001\u0001\u0000\u0000\u0001\u0000\u0001\u0000\u0000��\u0000C\u0000\u0010\u000b\f\u000e\f\n\u0010\u000e\r\u000e\u0012\u0011\u0010\u0013\u0018(\u001a\u0018\u0016\u0016\u00181#%\u001d(:3=Mqypdx\\egc��\u0000C\u0001\u0011\u0012\u0012\u0018\u0015\u0018/\u001a\u001a/cB8Bccccccccccc....
�\u001f.[���_�\u0014)M���XIjX��7�`=�/�8`��ïDʚ\u0018�D���#�V#q~m�q10L�' }
i also tried multer and other multipart handler but noting works.
please help me to out from this hell
It seems the server response is encoded as UTF-8. To properly decode and read you could try
BufferedReader br = new BufferedReader(new InputStreamReader(conn.getInputStream(), "UTF-8"));
Specifying the encoding part to the InputStreamReader will decode and read the stream using specified encoding scheme, which in this case is UTF-8. Check the javadocs for details.
You need to allocate buffer again based on the new size of bufferSize, I have revised the code below:
// create a buffer of maximum size
bytesAvailable = fileInputStream.available();
bufferSize = Math.min(bytesAvailable, maxBufferSize);
buffer = new byte[bufferSize];
// read file and write it into form...
bytesRead = fileInputStream.read(buffer, 0, bufferSize);
while (bytesRead > 0) {
dos.write(buffer, 0, bufferSize);
bytesAvailable = fileInputStream.available();
bufferSize = Math.min(bytesAvailable, maxBufferSize);
buffer = null;
buffer = new byte[bufferSize];
bytesRead = fileInputStream.read(buffer, 0, bufferSize);
}
Multipart form submission takes away a lot of the ambiguity that percent-encoding had: the server now can explicitly ask for certain encodings, and the client can explicitly tell the server during the form submission what encoding the fields are in.
There are two ways you go with this functionality: leave it unset and have the browser send in the same encoding as the page, or set it to UTF-8 and then do another conversion server-side. Each method has deficiencies, especially the former.
If you tell the browser to send the form in the same encoding as the page, you still have the trouble of what to do with characters that are outside of the character encoding's range. The behavior, once again, varies: Firefox 2.0 converts them to character entity references while Internet Explorer 7.0 mangles them beyond intelligibility. For serious internationalization purposes, this is not an option.
The other possibility is to set Accept-Encoding to UTF-8, which begs the question: Why aren't you using UTF-8 for everything then? This route is more palatable, but there's a notable caveat: your data will come in as UTF-8, so you will have to explicitly convert it into your favored local character encoding.
A Unicode-based encoding such as UTF-8 can support many languages and can accommodate pages and forms in any mixture of those languages. Its use also eliminates the need for server-side logic to individually determine the character encoding for each page served or each incoming form submission. This significantly reduces the complexity of dealing with a multilingual site or application.
A Unicode encoding also allows many more languages to be mixed on a single page than any other choice of encoding.
have look on Why UTF-X
We have a hybrid app where most of the code is handled in Javascript (including logging in via Google Sign In and some uploading to Google Drive). On iOS, we have other Google Drive uploading code, but I can't figure out how to accomplish the same thing in Android. I'm trying avoid having the user log into the web portion and then again for Android.
The iOS code that is being used to upload is...
let mutableURLRequest = NSMutableURLRequest(URL: NSURL(string: "https://www.googleapis.com/upload/drive/v3/files?uploadType=multipart")!)
mutableURLRequest.HTTPMethod = "POST"
let boundaryConstant = generateBoundaryString()
let contentType = "multipart/related; boundary="+boundaryConstant
// Set the headers
mutableURLRequest.addValue("Bearer \(accessToken)", forHTTPHeaderField: "Authorization")
mutableURLRequest.addValue("Keep-Alive", forHTTPHeaderField: "Connection")
mutableURLRequest.addValue(contentType, forHTTPHeaderField: "Content-Type")
mutableURLRequest.addValue("\(requestData.length)", forHTTPHeaderField: "Content-Length")
// create upload data to send
let uploadData = NSMutableData()
uploadData.appendData("\r\n--\(boundaryConstant)\r\n".dataUsingEncoding(NSUTF8StringEncoding)!)
let date = NSDate()
let calendar = NSCalendar.currentCalendar()
let components = calendar.components([.Day, .Month, .Year], fromDate: date)
let fileName = "\(components.year)-\(components.month)-\(components.day).\(ext)"
// Add parameters
let params = [
"name": fileName,
"mimeType": mimeType,
"parents": ["\(slnFldrId)"],
"convert": true
]
// Add the file meta data (JSON format)
uploadData.appendData("Content-Type: application/json; charset=UTF-8\r\n\r\n".dataUsingEncoding(NSUTF8StringEncoding)!)
uploadData.appendData("\(getJsonString(params))\r\n\r\n".dataUsingEncoding(NSUTF8StringEncoding)!)
uploadData.appendData("\r\n--\(boundaryConstant)\r\n".dataUsingEncoding(NSUTF8StringEncoding)!)
// Add the file
uploadData.appendData("Content-Type: \(mimeType)\r\n\r\n".dataUsingEncoding(NSUTF8StringEncoding)!)
uploadData.appendData(requestData)
uploadData.appendData("\r\n--\(boundaryConstant)--\r\n".dataUsingEncoding(NSUTF8StringEncoding)!)
mutableURLRequest.HTTPBody = uploadData
let configuration = NSURLSessionConfiguration.defaultSessionConfiguration()
let session = NSURLSession(configuration: configuration, delegate: self, delegateQueue: NSOperationQueue.mainQueue())
let task = session.uploadTaskWithStreamedRequest(mutableURLRequest)
task.resume()
...So it's just using the REST endpoint and works nicely. For Android, I've tried using the HttpClient with HttpPost and setting headers, etc., but I always get a 400 error (Bad request).
String ext = fileName.substring(fileName.lastIndexOf(".") + 1);
String mimeType = mimeTypeFromTypeId(typeId);
String boundary = getBoundary();
String tail = "\r\n-"+ boundary +"--\r\n";
HttpParams httpParams = new BasicHttpParams();
HttpConnectionParams.setConnectionTimeout(httpParams, 30000);
HttpConnectionParams.setSoTimeout(httpParams, 30000);
HttpClient httpClient = new DefaultHttpClient(httpParams);
HttpPost httpPost = new HttpPost("https://www.googleapis.com/upload/drive/v3/files?uploadType=multipart");
httpPost.addHeader("Authorization", "Bearer "+ Common.accessToken);
httpPost.addHeader("Content-Length", ""+ file.length());
String json = "{\"name\":\"Android upload."+ ext +"\",\"mimeType\":\""+ mimeType +"\",\"parents\":[\""+ Common.slnFldrId +"\"],\"convert\":true}";
MultipartEntityBuilder entityBuilder = MultipartEntityBuilder.create();
entityBuilder.setBoundary(boundary);
entityBuilder.setCharset(MIME.UTF8_CHARSET);
entityBuilder.setMode(HttpMultipartMode.BROWSER_COMPATIBLE);
entityBuilder.addTextBody("", json, ContentType.APPLICATION_JSON);
entityBuilder.addBinaryBody("", file, ContentType.create(mimeType), "Android upload."+ ext);
httpPost.setEntity(entityBuilder.build());
httpPost.setHeader(HTTP.CONTENT_TYPE, "multipart-related; boundary="+ boundary);
try
{
return httpClient.execute(httpPost);
}
catch(ConnectTimeoutException e)
{
throw e;
}
...I think the headers are causing the bad request error. Even though I'm setting the Content-Type header to multipart/related, it is always converted to multipart/form-data (I think by the MultipartEntityBuilder).
As far as I can tell, the Google Client libraries all require an authorization process (in that I can't set the accessToken that I already have from the web login into them), which is why I'm not using them.
Any help or suggestions would be greatly appreciated.
The solution I found is based off the article at http://www.codejava.net/java-se/networking/upload-files-by-sending-multipart-request-programmatically
Here is the code with some changes to make it work correctly with the Google Drive API.
URL url = null;
HttpURLConnection connection = null;
try
{
// Set up some constants and "global" variables
String BOUNDARY = getBoundary();
String LINE_FEED = "\r\n";
String UTF8 = "UTF-8";
String fileName = file.getName();
String ext = fileName.substring(fileName.lastIndexOf(".") + 1);
String mimeType = mimeTypeFromTypeId(typeId);
// Use the calendar to give the file a name
Calendar cal = Calendar.getInstance();
int cYear = cal.get(Calendar.YEAR);//calender year starts from 1900 so you must add 1900 to the value recevie.i.e., 1990+112 = 2012
int cMonth = cal.get(Calendar.MONTH);//this is april so you will receive 3 instead of 4.
int cDay = cal.get(Calendar.DAY_OF_MONTH);
int cHour = cal.get(Calendar.HOUR_OF_DAY);
int cMin = cal.get(Calendar.MINUTE);
int cSec = cal.get(Calendar.SECOND);
String name = cYear +"-"+ cMonth +"-"+ cDay +"-"+ cHour +"-"+ cMin +"-"+ cSec +"."+ ext;
// JSON meta-data part
String json = "{\"name\":\""+ name +"\",\"mimeType\":\""+ mimeType +"\",\"parents\":[\""+ Common.slnFldrId +"\"],\"convert\":true}";
String META_PART1 = "--"+ BOUNDARY + LINE_FEED
+ "Content-Type: application/json; charset="+ UTF8 + LINE_FEED + LINE_FEED
+ json + LINE_FEED + LINE_FEED;
// File meta-data part
String META_PART2 = "--"+ BOUNDARY + LINE_FEED
+ "Content-Type: "+ mimeType + LINE_FEED + LINE_FEED;
// Tail
String TAIL = LINE_FEED +"--"+ BOUNDARY +"--";
long contentLength = META_PART1.length() + META_PART2.length() + file.length() + TAIL.length();
// Set up the HttpUrlConnection
url = new URL("https://www.googleapis.com/upload/drive/v3/files?uploadType=multipart");
connection = (HttpURLConnection) url.openConnection();
connection.setReadTimeout(10000);
connection.setConnectTimeout(15000);
connection.setDoInput(true);
connection.setDoOutput(true);
connection.setInstanceFollowRedirects(false);
connection.setRequestProperty("Authorization", "Bearer "+ Common.accessToken);
connection.setRequestProperty("Content-Type", "multipart/related; boundary="+ BOUNDARY);
connection.setRequestProperty("Content-Length", ""+ contentLength);
connection.connect();
// Get the connection's output stream
OutputStream outputStream = connection.getOutputStream();
// Write the META_PART1 (JSON) and flush
outputStream.write(META_PART1.getBytes(UTF8));
outputStream.flush();
// Write the META_PART2 (file's contentType) and flush
outputStream.write(META_PART2.getBytes(UTF8));
outputStream.flush();
// Write the FILE
int totalRead = 0;
int bytesRead = -1;
byte[] buffer = new byte[4096];
FileInputStream inputStream = new FileInputStream(file);
while ((bytesRead = inputStream.read(buffer)) != -1)
{
outputStream.write(buffer, 0, bytesRead);
outputStream.flush();
totalRead += bytesRead;
float progress = totalRead / (float)contentLength;
delegate.onUploadProgress(progress);
}
// Flush the last of the file data and close the inputStream
outputStream.flush();
inputStream.close();
// Flush the TAIL and close the outputStream
outputStream.write(TAIL.getBytes(UTF8));
outputStream.flush();
outputStream.close();
// Get the server response
int responseCode = connection.getResponseCode();
if(responseCode == HTTP_SUCCESS)
{
BufferedReader reader = new BufferedReader(new InputStreamReader(connection.getInputStream()));
String line = "";
StringBuilder builder = new StringBuilder();
while ((line = reader.readLine()) != null)
{
builder.append(line);
}
// Send the completed message to the delegate
delegate.onUploadComplete(builder.toString(), typeId);
}
else
{
// Send the error message to the delegate
delegate.onUploadError(file);
}
}catch(IOException e)
{
// Send the error message to the delegate
delegate.onUploadError(file);
}finally
{
if(connection != null)
{
connection.disconnect();
}
}
I've read many many posts about sending an image to the server from an Android app and Content-type-wise, they are divided in three categories:
a) they dont set the content-type at all and probably somehow their code works
b) they are using deprecated methods
c) they are using completely different approach from the one I've selected.
I would like to send the file over to the server and store it in a folder.
My code is a total patchwork, a butchery job that I managed to come up with after reading lots of posts and articles, here is it:
public void uploadImageToServer(String imagePath) throws Exception {
try {
// set the http handlers
httpClient = new DefaultHttpClient();
localContext = new BasicHttpContext(); // why do I need this?
postRequest = new HttpPost("http://asd.com/asd.php");
//postRequest.addHeader("Content-type", "image/jpeg"); // - this didnt work
// deal with the file
ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
bitmap = BitmapFactory.decodeFile(imagePath);
bitmap.compress(CompressFormat.JPEG, 75, byteArrayOutputStream);
byte[] byteData = byteArrayOutputStream.toByteArray();
//String strData = Base64.encodeToString(data, Base64.DEFAULT); // I have no idea why Im doing this
ByteArrayBody byteArrayBody = new ByteArrayBody(byteData, "image"); // second parameter is the name of the image (//TODO HOW DO I MAKE IT USE THE IMAGE FILENAME?)
// send the package
multipartEntity = MultipartEntityBuilder.create();
multipartEntity.setMode(HttpMultipartMode.BROWSER_COMPATIBLE);
multipartEntity.addPart("uploaded_file", byteArrayBody);
postRequest.setEntity(multipartEntity.build());
// get the response. we will deal with it in onPostExecute.
response = httpClient.execute(postRequest, localContext);
bitmap.recycle();
} catch (Exception e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
And the error is:
FATAL EXCEPTION: AsyncTask #1
java.lang.RuntimeException: An error occured while executing doInBackground()
android.os.AsyncTask$3.done(AsyncTask.java:200)
java.util.concurrent.FutureTask$Sync.innerSetException(FutureTask.java:274)
java.util.concurrent.FutureTask.setException(FutureTask.java:125)
java.util.concurrent.FutureTask$Sync.innerRun(FutureTask.java:308)
java.util.concurrent.FutureTask.run(FutureTask.java:138)
java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1088)
java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:581)
java.lang.Thread.run(Thread.java:1019)
Caused by: java.lang.NoClassDefFoundError: org.apache.http.entity.ContentType
org.apache.http.entity.mime.content.ByteArrayBody.<init>(ByteArrayBody.java:67)
org.apache.http.entity.mime.content.ByteArrayBody.<init>(ByteArrayBody.java:87)
If you are using a library, you need to put it into /libs folder.
EDIT:
download httpmime, httpcore and httpclient library from http://hc.apache.org/downloads.cgi
Use this code to upload image file
HttpClient client = new DefaultHttpClient();
HttpPost postMethod = new HttpPost("http://localhost/Upload/index.php");
File file = new File(filePath);
MultipartEntity entity = new MultipartEntity();
FileBody contentFile = new FileBody(file);
entity.addPart("userfile",contentFile);
StringBody contentString = new StringBody("This is contentString");
entity.addPart("contentString",contentString);
postMethod.setEntity(entity);
client.execute(postMethod);
and in PHP use this code to receive
$uploads_dir = '/Library/WebServer/Documents/Upload/upload/'.$_FILES['userfile']['name'];
if(is_uploaded_file($_FILES['userfile']['tmp_name'])) {
echo $_POST["contentString"]."\n";
echo "File path = ".$uploads_dir;
move_uploaded_file ($_FILES['userfile'] ['tmp_name'], $uploads_dir);
} else {
echo "\n Upload Error";
echo "filename '". $_FILES['userfile']['tmp_name'] . "'.";
print_r($_FILES);
}
Use this code:--
public class HttpMultipartUpload {
String lineEnd = "\r\n";
String twoHyphens = "--";
String boundary = "AaB03x87yxdkjnxvi7";
public String upload(URL url, File file, String fileParameterName,
HashMap<String, String> parameters) throws IOException {
HttpURLConnection conn = null;
DataOutputStream dos = null;
DataInputStream dis = null;
FileInputStream fileInputStream = null;
byte[] buffer;
int maxBufferSize = 20 * 1024;
try {
// ------------------ CLIENT REQUEST
fileInputStream = new FileInputStream(file);
// open a URL connection to the Servlet
// Open a HTTP connection to the URL
conn = (HttpURLConnection) url.openConnection();
// Allow Inputs
conn.setDoInput(true);
// Allow Outputs
conn.setDoOutput(true);
// Don't use a cached copy.
conn.setUseCaches(false);
// Use a post method.
conn.setRequestMethod("POST");
conn.setRequestProperty("Content-Type",
"multipart/form-data;boundary=" + boundary);
dos = new DataOutputStream(conn.getOutputStream());
dos.writeBytes(twoHyphens + boundary + lineEnd);
dos.writeBytes("Content-Disposition: form-data; name=\""
+ fileParameterName + "\"; filename=\"" + mFileName
+ ".jpg" + "\"" + lineEnd);
dos.writeBytes("Content-Type:image/jpg" + lineEnd);
dos.writeBytes(lineEnd);
// create a buffer of maximum size
buffer = new byte[Math.min((int) file.length(), maxBufferSize)];
int length;
// read file and write it into form...
while ((length = fileInputStream.read(buffer)) != -1) {
dos.write(buffer, 0, length);
}
for (String name : parameters.keySet()) {
dos.writeBytes(lineEnd);
dos.writeBytes(twoHyphens + boundary + lineEnd);
dos.writeBytes("Content-Disposition: form-data; name=\""
+ name + "\"" + lineEnd);
dos.writeBytes(lineEnd);
dos.writeBytes(parameters.get(name));
}
// send multipart form data necessary after file data...
dos.writeBytes(lineEnd);
dos.writeBytes(twoHyphens + boundary + twoHyphens + lineEnd);
dos.flush();
} finally {
if (fileInputStream != null)
fileInputStream.close();
if (dos != null)
dos.close();
}
// ------------------ read the SERVER RESPONSE
try {
dis = new DataInputStream(conn.getInputStream());
StringBuilder response = new StringBuilder();
String line;
while ((line = dis.readLine()) != null) {
response.append(line).append('\n');
}
System.out.println("Upload file responce:"
+ response.toString());
return response.toString();
} finally {
if (dis != null)
dis.close();
}
}
}
If someone just cannot figure out what is going on with headers, take a look at this article http://develop-for-android.blogspot.com/2014/01/using-volley-in-your-application.html It just saved me the day.
Read the source of the file http. Check this solution:
Call new MultipartEntity:
MultipartEntity entity = new MultipartEntity(HttpMultipartMode.BROWSER_COMPATIBLE, null, Charset.forName("UTF-8"));
Add request header
heads.put("Content-Type", "image/png;charset=utf-8");
I am developing an Android app which enables the user to upload a file to services like Twitpic and others.
The POST upload is done without any external libraries and works just fine. My only problem is, that I can't grab any progress because all the uploading is done when I receive the response, not while writing the bytes into the outputstream.
Here is what I do:
URL url = new URL(urlString);
HttpURLConnection conn = (HttpURLConnection) url.openConnection();
conn.setDoInput(true);
conn.setDoOutput(true);
conn.setUseCaches(false);
conn.setRequestMethod("POST");
conn.setRequestProperty("Connection", "Keep-Alive");
conn.setRequestProperty("Content-Type", "multipart/form-data;boundary=" + boundary);
DataOutputStream dos = new DataOutputStream(conn.getOutputStream());
Then I write the form data to dos, which is not so important here, now. After that I write the file data itself (reading from "in", which is the InputStream of the data I want to send):
while ((bytesAvailable = in.available()) > 0)
{
bufferSize = Math.min(bytesAvailable, maxBufferSize);
byte[] buffer = new byte[bufferSize];
bytesRead = in.read(buffer, 0, bufferSize);
dos.write(buffer, 0, bytesRead);
}
After that, I send the multipart form data to indicate the end of the file. Then, I close the streams:
in.close();
dos.flush();
dos.close();
This all works perfectly fine, no problem so far. My problem is, however, that the whole process up to this point takes about one or two seconds no matter how large the file is.
The upload itself seems to happen when I read the response:
DataInputStream inStream = new DataInputStream(conn.getInputStream());
This takes several seconds or minutes, depending on how large the file is and how fast the internet connection.
My questions now are:
1) Why doesn't the uplaod happen when I write the bytes to "dos"?
2) How can I grab a progress in order to show a progress dialog during the upload when it all happens at once?
/EDIT:
1) I set the Content-Length in the header which changes the problem a bit, but does not in any
way solve it:
Now the whole content is uploaded after the last byte is written into the stream. So, this doesn't change the situation that you can't grab the progress, because again, the data is written at once.
2) I tried MultipartEntity in Apache HttpClient v4. There you don't have an OutputStream at all, because all the data is written when you perform the request. So again, there is no way to grab a progress.
Is there anyone out there who has any other idea how to grab a process in a multipart/form upload?
I have tried quite a lot in the last days and I think I have the answer to the initial question:
It's not possible to grab a progress using HttpURLConnection because there is a bug / unusual behavior in Android:
http://code.google.com/p/android/issues/detail?id=3164#c6
It will be fixed post Froyo, which is not something one can wait for...
So, the only other option I found is to use the Apache HttpClient. The answer linked by papleu is correct, but it refers to general Java. The classes that are used there are not available in Android anymore (HttpClient 3.1 was part of the SDK, once). So, what you can do is add the libraries from HttpClient 4 (specifically apache-mime4j-0.6.jar and httpmime-4.0.1.jar) and use a combination of the first answer (by Tuler) and the last answer (by Hamy):
import java.io.FilterOutputStream;
import java.io.IOException;
import java.io.OutputStream;
import java.nio.charset.Charset;
import org.apache.http.entity.mime.HttpMultipartMode;
import org.apache.http.entity.mime.MultipartEntity;
public class CountingMultipartEntity extends MultipartEntity {
private final ProgressListener listener;
public CountingMultipartEntity(final ProgressListener listener) {
super();
this.listener = listener;
}
public CountingMultipartEntity(final HttpMultipartMode mode, final ProgressListener listener) {
super(mode);
this.listener = listener;
}
public CountingMultipartEntity(HttpMultipartMode mode, final String boundary,
final Charset charset, final ProgressListener listener) {
super(mode, boundary, charset);
this.listener = listener;
}
#Override
public void writeTo(final OutputStream outstream) throws IOException {
super.writeTo(new CountingOutputStream(outstream, this.listener));
}
public static interface ProgressListener {
void transferred(long num);
}
public static class CountingOutputStream extends FilterOutputStream {
private final ProgressListener listener;
private long transferred;
public CountingOutputStream(final OutputStream out,
final ProgressListener listener) {
super(out);
this.listener = listener;
this.transferred = 0;
}
public void write(byte[] b, int off, int len) throws IOException {
out.write(b, off, len);
this.transferred += len;
this.listener.transferred(this.transferred);
}
public void write(int b) throws IOException {
out.write(b);
this.transferred++;
this.listener.transferred(this.transferred);
}
}
}
This works with HttpClient 4, but the downside is that my apk now has a size of 235 kb (it was 90 kb when I used the multipart upload described in my question) and, even worse, an installed app size of 735 kb (about 170 kb before).
That's really awful. Only to get a progress during upload the app size is now more than 4 times as big as it was before.
Try this out. It works.
connection = (HttpURLConnection) url_stripped.openConnection();
connection.setRequestMethod("PUT");
String boundary = "---------------------------boundary";
String tail = "\r\n--" + boundary + "--\r\n";
connection.addRequestProperty("Content-Type", "image/jpeg");
connection.setRequestProperty("Connection", "Keep-Alive");
connection.setRequestProperty("Content-Length", ""
+ file.length());
connection.setDoOutput(true);
String metadataPart = "--"
+ boundary
+ "\r\n"
+ "Content-Disposition: form-data; name=\"metadata\"\r\n\r\n"
+ "" + "\r\n";
String fileHeader1 = "--"
+ boundary
+ "\r\n"
+ "Content-Disposition: form-data; name=\"uploadfile\"; filename=\""
+ fileName + "\"\r\n"
+ "Content-Type: application/octet-stream\r\n"
+ "Content-Transfer-Encoding: binary\r\n";
long fileLength = file.length() + tail.length();
String fileHeader2 = "Content-length: " + fileLength + "\r\n";
String fileHeader = fileHeader1 + fileHeader2 + "\r\n";
String stringData = metadataPart + fileHeader;
long requestLength = stringData.length() + fileLength;
connection.setRequestProperty("Content-length", ""
+ requestLength);
connection.setFixedLengthStreamingMode((int) requestLength);
connection.connect();
DataOutputStream out = new DataOutputStream(
connection.getOutputStream());
out.writeBytes(stringData);
out.flush();
int progress = 0;
int bytesRead = 0;
byte buf[] = new byte[1024];
BufferedInputStream bufInput = new BufferedInputStream(
new FileInputStream(file));
while ((bytesRead = bufInput.read(buf)) != -1) {
// write output
out.write(buf, 0, bytesRead);
out.flush();
progress += bytesRead;
// update progress bar
publishProgress(progress);
}
// Write closing boundary and close stream
out.writeBytes(tail);
out.flush();
out.close();
// Get server response
BufferedReader reader = new BufferedReader(
new InputStreamReader(connection.getInputStream()));
String line = "";
StringBuilder builder = new StringBuilder();
while ((line = reader.readLine()) != null) {
builder.append(line);
}
Reference: http://delimitry.blogspot.in/2011/08/android-upload-progress.html
It is possible to grab the progress from the DefaultHttpClient. The fact that it stays in the line
response = defaultHttpClient.execute( httpput, context );
until complete data is sent, does not necessarily mean that you cannot grab the progress. Namely, HttpContext changes while executing this line, so if you approach it via different Thread, you may observe changes in that. What you need is ConnAdapter. It has HttpConnectionMetrics embedded
final AbstractClientConnAdapter connAdapter = (AbstractClientConnAdapter) context.getAttribute(ExecutionContext.HTTP_CONNECTION);
final HttpConnectionMetrics metrics = connAdapter.getMetrics();
int bytesSent = (int)metrics.getSentBytesCount();
If you check this periodically, you will observe the progress. Be sure to correctly handle threads, and all try/catch guarded. Especially, handle the case when Context is not valid any more (IllegalStateException), which occurs when Put is canceled or completed.
Use WatchedInputStream instead.
It nice, great, and easy to use.
use WatchedInputStream to wrap your inputstream.
watchedStream.setListener
watchedStream.setListener(new WatchedInputStream.Listener() {
public void notify(int read) {
// do something to update UI.
}
}