klippa-app/nativescript-http

Does This Support Binary File Upload (Image) in Formdata Submit

gitalvininfo opened this issue · 16 comments

Make sure to check the demo app(s) for sample usage

Make sure to check the existing issues in this repository

If the demo apps cannot help and there is no issue for your problem, tell us about it

Please, ensure your title is less than 63 characters long and starts with a capital
letter.

Which platform(s) does your issue occur on?

  • Android 10
  • Device (Huawei Mate)

Please, provide the following version numbers that your issue occurs with:

TNS Version: 7.0.11

My package.json

"dependencies": {
"@angular/animations": "~10.1.0",
"@angular/common": "~10.1.0",
"@angular/compiler": "~10.1.0",
"@angular/core": "~10.1.0",
"@angular/forms": "~10.1.0",
"@angular/platform-browser": "~10.1.0",
"@angular/platform-browser-dynamic": "~10.1.0",
"@angular/router": "~10.1.0",
"@klippa/nativescript-http": "^2.0.1",
"@nativescript/angular": "~10.1.0",
"@nativescript/core": "~7.0.0",
"@nativescript/theme": "~2.3.0",
"@nstudio/nativescript-cardview": "^2.0.1",
"@nstudio/nativescript-floatingactionbutton": "^3.0.4",
"nativescript-carousel": "^7.0.1",
"nativescript-http-formdata": "^2.1.0",
"nativescript-imagepicker": "^7.1.0",
"nativescript-oauth2": "^3.0.2",
"nativescript-ui-calendar": "^7.0.2",
"nativescript-ui-chart": "^8.0.2",
"nativescript-ui-dataform": "^7.0.4",
"nativescript-ui-listview": "^9.0.4",
"nativescript-ui-sidedrawer": "~9.0.0",
"reflect-metadata": "~0.1.12",
"rxjs": "^6.6.0",
"zone.js": "~0.11.1"
},
"devDependencies": {
"@angular/cli": "^10.2.0",
"@angular/compiler-cli": "~10.1.0",
"@nativescript/android": "7.0.1",
"@nativescript/schematics": "^10.1.0",
"@nativescript/webpack": "~3.0.0",
"@ngtools/webpack": "~10.1.0",
"@schematics/angular": "^11.0.1",
"codelyzer": "~6.0.0",
"node-sass": "^4.14.1",
"tslint": "~6.1.3",
"typescript": "~4.0.3"
},

Is there any code involved?

My use case is very simple. Submit the image as binary file like when uploading image in web (I don't if this is supported already) in formdata.

So I already grab the image with no luck I cannot find enough resources to upload the image to my server as binary like the image below. I tried converting it to base64 format and I don't know if this is the right way to convert it to binary.

Capture

Here is my sample snippet when grabbing the image. I'm using nativescript-imagepicker.

let bitmapImage: any = image;
let stream = new java.io.ByteArrayOutputStream();
bitmapImage.compress(android.graphics.Bitmap.CompressFormat.PNG, 100, stream);
let byteArray = stream.toByteArray();
bitmapImage.recycle()
let image_data = byteArray;

Any help would be appreciated which would point my mistakes here. Thanks in advance.

Can you share the code that you use to post the form data?

Here it is. I've already managed to upload each property as formdata however the image is not uploaded to the server.

const form = new HTTPFormData();
form.append("transaction_id", "c788d266-98e1-4959-9dd5-d18745a949b6");
    form.append("ticker_id", "348");
    form.append("shares", `${share}`);
    form.append("entry_price", "1.3");
    form.append("entry_date", "2020-12-06");
    form.append("type", "ipo");
    form.append("buy_content", "new content");
    form.append(`buy_images[0]`, `${this.image_form_data}`);
Http.request({
      // url: "https://httpbin.org/post",
      url: "https://api.mytestingapp.com/trades/entry/ipo",
      method: "POST",
      content: form,
      headers: GlobalVar.HttpFormData
    }).then((response: HttpResponse) => {
      alert(JSON.stringify(response))
    }, (e) => {
    });

This is my header for the request.

public static HttpHeaderFormData(key) {
        return this.HttpFormData = {
            'Cache-Control': 'no-cache',
            'processData': 'false',
            Authorization: "Bearer " + GlobalVar.Key
        }
    }

I've read the documentation and at this line Im a bit confused.

By default this client behaves the same as the Core HTTP for FormData objects, meaning it will just encode it as key=value pairs and it does not support Blob/File objects. It will be posted as application/x-www-form-urlencoded unless you override it using a custom header.

So what it means I can override the default content-type and set my own which is multipart-formdata. Is it correct?

@gitalvininfo The following bit is important:

// formFile data can be a JavaScript ArrayBuffer but also native file objects like java.io.File and NSData.dataWithContentsOfFile.
const formFile = new HTTPFormDataEntry(new java.io.File(fileLocation), "test.png", "image/png");
form.append("file", formFile);

Sou you have to wrap your binary file with a HTTPFormDataEntry object.

@gitalvininfo The following bit is important:

// formFile data can be a JavaScript ArrayBuffer but also native file objects like java.io.File and NSData.dataWithContentsOfFile.
const formFile = new HTTPFormDataEntry(new java.io.File(fileLocation), "test.png", "image/png");
form.append("file", formFile);

Sou you have to wrap your binary file with a HTTPFormDataEntry object.

Yes I've seen this. I just want to clarify, the fileLocation, is this the path from where you uploaded the image or can I put there my base64 converted image?

Thanks again for pointing out.

That's when you want to use a file from the disk. You can also directly use a byte array in there, so the results from stream.toByteArray() for example.

Also to be clear: if you want to post base64, but your backend expects binary, you have to decode it before sending.

Hi @jerbob92!

Thanks for pointing out.

I've already manage to create a formdata and wrap the binary file (the image i uploaded) into HTTPFormDataEntry object and post them into a sample api endpoint in httpbin (POST) and I've seen the response of the server. They are submitted as form data and I've looked onto my buy_image property and it's value are something like a random very long characters which I believe a representation of my image.

However, when I change my httpbin endpoint to my actual dev endpoint, I can't see the uploaded image, only the other object properties are submitted.

I've compare it to my Angular Web Project and see that this is the request payload when submitting (FORM DATA) in web.

res

When I view the parsed (the FORM DATA value in web) this is the value of my image. Disregard the naming of buy_image[0] and buy_image[1]

101374084-943e6680-3862-11eb-91b4-87bb31f01371

This is the sample request in android where this.image_form_data is the result of stream.toByteArray() as what you have said earlier.

const formFile: any = new HTTPFormDataEntry(this.image_form_data);

Just a quick question, is the response of httpbin for my uploaded image (random long characters) is equivalent to the image above? Or should I tell something to the backend devs about this one?

Sorry, I know this is more of a support but I can't find any related source in stackoverflow with this library. Thanks in advance

@gitalvininfo can you perhaps send me a link to your httpbin to jerbob92@gmail.com so I can take a look?

@jerbob92 This is exactly the sample response coming from the server after I submit my formdata. (HTTPBIN).

Capture2

This is my headers.
headers

Thanks for your patience.

Can you try https://requestbin.com/? It's better in parsing multipart form data

I've try parsing the multipart form data in request https://requestbin.com/ but still the response looks like the same.

requestbin

I'm wondering if the random long characters is the same as the binary in web

Capture

It looks like it's trying to send the data as a string, and not as a file.
Can you post the code that you have right now (all of it). Easiest way for me to help you is to create a small sample project.
But it looks like you are not using the HTTPFormDataEntry class

private startSelection(context) {
    let that = this;

    context
      .authorize()
      .then(() => {
        that.imageAssets = [];
        that.imageSrc = null;
        return context.present();
      })

      .then((selection) => {
        let item = selection[0];

        that.imageSrc = that.isSingleMode && selection.length > 0 ? selection[0] : null;

        that.imageAssets = selection;
        let imagedata: any;
        item.getImageAsync(async (image, error) => {

          if (image) {
            if (image.ios) {
              // this.image_form_data = UIImagePNGRepresentation(image);
            } else {
              // let bitmapImage: android.graphics.Bitmap = image;
              let bitmapImage: any = image;
              let stream = new java.io.ByteArrayOutputStream();
              bitmapImage.compress(android.graphics.Bitmap.CompressFormat.PNG, 100, stream);
              let byteArray = stream.toByteArray();
              bitmapImage.recycle();
              imagedata = byteArray
              this.image_form_data = byteArray;
            }
          }
        })

      }).catch(function (e) {
        console.log(e);
      });
  }




  public submit() {
    const form = new HTTPFormData();

    form.append("transaction_id", "c788d266-98e1-4959-9dd5-d18745a949b6");
    form.append("ticker_id", "348");
    form.append("shares", "1");
    form.append("entry_price", "1.3");
    form.append("entry_date", "2020-12-06");
    form.append("type", "ipo");
    form.append("buy_content", "test content");
    
    const formFile: any = new HTTPFormDataEntry(this.image_form_data);

    for (var i = 0; i < 2; i++) {
      form.append(`buy_images[${i}]`, formFile);
    }


    Http.request({
      url: "https://httpbin.org/post",
      method: "POST",
      content: form,
    }).then((response: HttpResponse) => {
      alert(JSON.stringify(response))
    }, (e) => {
    });
  }

This is exactly the code I have right now.

On public startselection(), user selects the image and I've manage to display it.

On public submit(), user clicks on button submit, and then process the request.

To let your application see it as a file upload you have to set the mime type and filename like this:

    const formFile: any = new HTTPFormDataEntry(this.image_form_data, "buy_image.png", "image/png");

You made my day. Thank you so much for the patience. It's uploading now to my server. I've made a mistake when I remove the mime type and filename because what you said a day ago got me confused.

That's when you want to use a file from the disk. You can also directly use a byte array in there, so the results from stream.toByteArray() for example.

I thought mime type and filename is not needed and just replace the filelocation with my stream.toByteArray() result.

Thanks again. CLOSED