3

I am trying to send an image to the server using HttpUrlConnection, because it's recommended by Google. I decided to convert the image into Base64 string and send it to the server where I decoded it into .jpg file. But this method is only feasible with small-sized thumbnails and I cannot send a full-sized images.

Here is the android client code:

 public static void postData(Bitmap imageToSend) {



        try

        {
            URL url = new URL("http://");
            HttpURLConnection conn = (HttpURLConnection) url.openConnection();
            conn.setRequestMethod("POST");

            conn.setDoInput(true);
            conn.setDoOutput(true);

            conn.setRequestProperty("Connection", "Keep-Alive");
            conn.setRequestProperty("Cache-Control", "no-cache");

            conn.setReadTimeout(35000);
            conn.setConnectTimeout(35000);


            ByteArrayOutputStream bos = new ByteArrayOutputStream();

            // writes a compress version of Bitmap to a specified outputstream
            imageToSend.compress(Bitmap.CompressFormat.JPEG, 100, bos);

            byte[] byteArray = bos.toByteArray();
            String imageEncoded = Base64.encodeToString(byteArray, Base64.DEFAULT);

            List<NameValuePair> params = new ArrayList<NameValuePair>();
            params.add(new BasicNameValuePair("image", imageEncoded));


            OutputStream os = conn.getOutputStream();
            BufferedWriter writer = new BufferedWriter(
                    new OutputStreamWriter(os, "UTF-8"));

            // getQuery is function for creating a URL encoded string
            writer.write(getQuery(params));

            System.out.println("Response Code: " + conn.getResponseCode());

            InputStream in = new BufferedInputStream(conn.getInputStream());
            Log.d("sdfs", "sfsd");
            BufferedReader responseStreamReader = new BufferedReader(new InputStreamReader(in));
            String line = "";
            StringBuilder stringBuilder = new StringBuilder();
            while ((line = responseStreamReader.readLine()) != null)
                stringBuilder.append(line).append("\n");
            responseStreamReader.close();

            String response = stringBuilder.toString();
            System.out.println(response);

            bos.flush();
            bos.close();
            in.close();
            conn.disconnect();

        }

        catch(MalformedURLException e) {
            e.printStackTrace();
        }

        catch(IOException e) {
            e.printStackTrace();
        }


}

Node.js server code:

function base64_decode(base64str,file) {
   var bitmap = new Buffer(base64str,'base64');
   //writing into an image file
   fs.writeFile(file, bitmap);
   //write a text file
    console.log('File created from base64 encoded string');
}

app.post("/", function (req,res,next) {
        //requesting the value of the image key which is urlencoded base 64 string
        var image = req.body.image;
        console.log(req.body.image);
        base64_decode(image,'newImage.jpg');

        res.writeHead(200, {'Content-Type':'text/plain'});
        res.write('the image is saved');
        res.end();
        if (req.url != "/")
           next();
 

I cannot use the same method for the full-sized images, because of the BufferedWriter size limits - the base64 encoded string is too long for it.

Another method is using HttpPost and MultipartEntity, but both are deprecated in API22, and I did not know how to handle request on the server side. In the other examples some wrappers were used, like two hyphens, boundaries, crlf, but I could not find why.

I need an example with HttpUrlConnection

Any help is appreciated, because I'm newbie to Android and node.js

Azik
  • 63
  • 1
  • 6
  • If you want to avoid messing around with `HttpURLConnection`, you could have a look at [DavidWebb](http://hgoebl.github.io/DavidWebb/). And if you're using Express, there is a test-api-server with [some sample code](https://github.com/hgoebl/DavidWebb/blob/master/src/test/api-test-server/routes/upload.js#L41-L86) showing how upload might be done. Generally I wouldn't recommend using Base64, you can upload binary in all cases. Are you open for this library and Express? – hgoebl Jul 07 '15 at 10:48
  • I use Express anyway. And thank you for the libraries, I will consider using it if I cannot figure out HttpUrlConnection issues. However, I see in documentation that it does not support multi-part upload – Azik Jul 07 '15 at 11:42

2 Answers2

2

I'd recommend uploading binary data. You could place image meta-data (like name, type, user-id, ...) as url parameters or custom http-headers (X-...).

Android client code (not tested!):

 public static void postData(Bitmap imageToSend) {
        try
        {
            URL url = new URL("http://myserver/myapp/upload-image");
            HttpURLConnection conn = (HttpURLConnection) url.openConnection();
            conn.setRequestMethod("POST");

            conn.setDoInput(true);
            conn.setDoOutput(true);

            conn.setRequestProperty("Connection", "Keep-Alive");
            conn.setRequestProperty("Cache-Control", "no-cache");

            conn.setReadTimeout(35000);
            conn.setConnectTimeout(35000);

            // directly let .compress write binary image data
            // to the output-stream
            OutputStream os = conn.getOutputStream();
            imageToSend.compress(Bitmap.CompressFormat.JPEG, 100, os);
            os.flush();
            os.close();

            System.out.println("Response Code: " + conn.getResponseCode());

            InputStream in = new BufferedInputStream(conn.getInputStream());
            Log.d("sdfs", "sfsd");
            BufferedReader responseStreamReader = new BufferedReader(new InputStreamReader(in));
            String line = "";
            StringBuilder stringBuilder = new StringBuilder();
            while ((line = responseStreamReader.readLine()) != null)
                stringBuilder.append(line).append("\n");
            responseStreamReader.close();

            String response = stringBuilder.toString();
            System.out.println(response);

            conn.disconnect();
        }
        catch(MalformedURLException e) {
            e.printStackTrace();
        }
        catch(IOException e) {
            e.printStackTrace();
        }
}

Node.js, Express code:

function rawBody(req, res, next) {
    var chunks = [];

    req.on('data', function(chunk) {
        chunks.push(chunk);
    });

    req.on('end', function() {
        var buffer = Buffer.concat(chunks);

        req.bodyLength = buffer.length;
        req.rawBody = buffer;
        next();
    });

    req.on('error', function (err) {
        console.log(err);
        res.status(500);
    });
}

app.post('/upload-image', rawBody, function (req, res) {

    if (req.rawBody && req.bodyLength > 0) {

        // TODO save image (req.rawBody) somewhere

        // send some content as JSON
        res.send(200, {status: 'OK'});
    } else {
        res.send(500);
    }

});

I'll try to explain the node.js part: The function rawBody acts as Express middleware. When a POST request is made, this function gets called with the request object. It registers listeners for data, end and error events. The data events append all incoming chunks of data to a buffer. When end fires, the property rawBody is created in the request object and contains the binary data (your image blob). rawBody() then transfers control to the next handler which can now save the blob to your database or filesystem.

When dealing with really big data blobs, this kind of processing is not the best way. It would be better to stream the data to a file or to the database to save memory.

hgoebl
  • 12,637
  • 9
  • 49
  • 72
  • This is it! Thank you very much!!! You saved me a lot of time and efforts! I did not completely understand the node.js part though, I'm gonna research it or if you can explain it in short I would be very grateful! – Azik Jul 07 '15 at 13:16
1

I don't have enough points yet, so thats why I have to write a whole answer instead of a comment.

I want to add a few things to hgoebl's answer, which is great and saved me a lot of time! Thanks!

Before you flush out the stream you have to add this lines, since without them I didn't managed it to get it work.

urlConnection.setRequestProperty("Content-Type", "multipart/form-data");
urlConnection.setFixedLengthStreamingMode(1024);

If you want to read directly from your USB/SD Storage you don't have to decode your Bitmap and encode it to a compressed format. just do like

// ... set request header 

FileInputStream fis = new FileInputStream(filePath);

OutputStream os = new BufferedOutputStream(urlConnection.getOutputStream());
OutputStreamWriter out = new OutputStreamWriter(os);

byte[] buffer = new byte[1024];
int read;
while ((read = fis.read(buffer)) != -1) {
    os.write(buffer, 0, read);
}

... // flush and close

with this you should be able to upload any kind of binary data.

Daveloper
  • 509
  • 4
  • 9