capacitor-community / http

Community plugin for native HTTP
MIT License
208 stars 136 forks source link

FormData is not being send #213

Open FernetB opened 2 years ago

FernetB commented 2 years ago

Describe the bug I'm trying to do a post with a img file in a FormData and the request sends nothing

Steps to reproduce

const form = new FormData();
const file  = new File([someBlob], "name")

form.append("file", file)

Http.request({ data: form, headers: { "Content-type": "multipart/form-data" }, ...etc})

/* nothing is send, the form is empty */

Expected behavior To send the formData

Screenshots image

maximov-dev commented 2 years ago

Hey @FernetB. Have you considered using Http.uploadFile?

FernetB commented 2 years ago

Yes, but not only doesn't work but also ask the user for permissions to the file system.

FernetB commented 2 years ago

@thomasvidas I'm doing something wrong ? With axios this works perfectly, but I really need to use this plugin for set-cookie reasons with capacitor

FernetB commented 2 years ago

On web: image On Native: image

thomasvidas commented 2 years ago

Currently on Holiday, but I'm planning on fixing this in the 2.x branch; which isn't released yet. Http.request doesn't work with multipart form data. If someone else wants to make a PR I can review/release but I don't have the bandwidth to work on this.

FernetB commented 2 years ago

Can I use uploadFile for this? I tried and failed but maybe I did something wrong, or is just made for another purposes?

christulsi commented 2 years ago

I am having the same issue, is it fixed as yet?

FernetB commented 2 years ago

Nop, I have to change to axios and use a clouflare worker to handle the set-cookie and cors problems

vamfixed commented 2 years ago

any updates?

maudvd commented 2 years ago

Any news ? Is there any way besides FormData to send multiple files at once ?

mightytyphoon commented 2 years ago

Hi ! This is really a critical serious issue. It should be easy to use a form and send images with POST.

I won't let you all down, as I have a fix, but this issue should be resolved ASAP in my honest opinion.

So the fix idea is to get your image as dataURI in Ionic and send it as a string. This gives lot of problems really, and should be a fix only for a short period of time.

So, using this capacitor Http plugin, you will need first to convert the image in input to a DataURI. You will need fileReader and you will probably encounter another problem of capacitor, not loading the filereader properly.

So on Ionic app (interface side) you will need :

the fn to get fileReader

export function getFileReader(): FileReader {
  const fileReader = new FileReader();
  const zoneOriginalInstance = (fileReader as any)["__zone_symbol__originalInstance"];
  return zoneOriginalInstance || fileReader;
};

the fn to get the dataURI from fileReader

export function readURL(file: any){
    return new Promise((res, rej) => {
        const reader = getFileReader();
        reader.onload = e => res(e.target.result);
        reader.onerror = e => rej(e);
        reader.readAsDataURL(file);
    });
  };

the function to submit your data

  async submit(){
    const img = this.imgInput.nativeElement.files.item(0); // get the file from the input
    const dataURI = await readURL(img); // get the dataURI with readURL function
    await Http.request({ // post the file
      method: 'post',
      url: 'your/api/url/to/post/file',
      data : {img: dataURI}
      headers: {'Content-Type': 'application/json'}
    });
  }

of course you will also need to get the imgInput reference, so add the @ViewChild in your .ts file

  @ViewChild('imgInput' , { static: true }) imgInput: ElementRef<HTMLInputElement>;

on html side you will need the input and a button

<input #imgInput name="img" type="file" accept="image/*">
<button (click)="submit()">submit image</button>

Then on api side, you will need to get the string from the body and to convert it to a base64 data that can be written as a file.

you will need a getDataURI fn :

export function getDataURI(base64: string): {type: string , ext: string , data: string}{
    let type = '';
    let ext = '';
    let data = '';
    for(let i = 0 ; i < 60 ; i++){
      if(base64[i] === ':') {
        for(let j = 1 ; j < 60 ; j++) {
          if(base64[i+j] !== '/') {
            type = type + base64[i+j];
          } else {
            break;
          }
        }
      }
      if(base64[i] === '/') {
        for(let j = 1 ; j < 60 ; j++) {
          if(base64[i+j] !== ';') {
            ext = ext + base64[i+j];
          } else {
            break;
          }
        }
      }
      if(base64[i] === ',') {
        data = base64.substr(i + 1);
        break;
      }
    }
    return {type , ext , data}
  }

then you will have to get the data and write them using Buffer (on nodejs app)

      const infos = getDataURI(body.img); // we get the img from {img: dataURI} POST in ionic interface
      writeFileSync(resolve('./imgFile.jpg')  , Buffer.from(infos.data , 'base64')

Now with this you will probably get payload too large error if your server can't handle big POST requests (statusCode: 413)

I am personally using loopback 4 + dokku (so nginx). If you get this error, you will need to make your payload bigger on your server and on your app.

in my case, for nginx you need to add bigger body size :

sudo nano /etc/nginx/nginx.conf
// change line tp your needs => client_max_body_size 2M;
// for example client_max_body_size 200M;
// then save file and reload nginx
nginx -s reload

and then on loopback 4, in index.ts :

app.bind(RestBindings.REQUEST_BODY_PARSER_OPTIONS).to({limit: '200Mb'})

So yeah... this works but it's kinda horrible to just upload a file... And this will get out of hand with many files, you will have to handle a sort of lazy uploading. You could also try to stream files, or use websockets to send chunks... but yeah, this shouldn't be normal usage of Http imo.

mightytyphoon commented 2 years ago

Currently on Holiday, but I'm planning on fixing this in the 2.x branch; which isn't released yet. Http.request doesn't work with multipart form data. If someone else wants to make a PR I can review/release but I don't have the bandwidth to work on this.

@thomasvidas Hi Thomas ! Could we have some infos about what could be wrong in the capacitor Http plugin not sending properly FormData with files using POST method ? So we can try to fix the problem and send a pull request ?

Thank you a lot.

FernetB commented 2 years ago

You can't send form data because it's not yet implemented. We need to wait until he or someone who knows Java and Swift adds it

El vie., 28 de enero de 2022 6:42 a. m., mightytyphoon < @.***> escribió:

Currently on Holiday, but I'm planning on fixing this in the 2.x branch; which isn't released yet. Http.request doesn't work with multipart form data. If someone else wants to make a PR I can review/release but I don't have the bandwidth to work on this.

@thomasvidas https://github.com/thomasvidas Hi Thomas ! Could we have some infos about what could be wrong in the capacitor Http plugin not sending properly FormData with files using POST method ? So we can try to fix the problem and send a pull request ?

Thank you a lot.

— Reply to this email directly, view it on GitHub https://github.com/capacitor-community/http/issues/213#issuecomment-1024045814, or unsubscribe https://github.com/notifications/unsubscribe-auth/ALJFPIBMCA3BQ7Z7LMXGPBTUYJQHRANCNFSM5K4V45UQ . Triage notifications on the go with GitHub Mobile for iOS https://apps.apple.com/app/apple-store/id1477376905?ct=notification-email&mt=8&pt=524675 or Android https://play.google.com/store/apps/details?id=com.github.android&referrer=utm_campaign%3Dnotification-email%26utm_medium%3Demail%26utm_source%3Dgithub.

You are receiving this because you were mentioned.Message ID: @.***>

mightytyphoon commented 2 years ago

@FernetB I could give it a go. (I need to put the project I am working on in production in about 3 months, and I don't want to use my fix to send files in prod)

I bet for Android I need to do something with this file : https://github.com/capacitor-community/http/blob/main/android/src/main/java/com/getcapacitor/plugin/http/FormUploader.java and for iOS : https://github.com/capacitor-community/http/blob/main/ios/Plugin/HttpRequestHandler.swift

What I want is just someone to explain the possible problem(s) to try to fix it, this will help me to not lose too much time searching for the logic in the code.

I am into typescript and C++ everyday, but I am pretty language agnostic, so I could be able to understand how to send a formData using Java & Swift.

Here is a possible Java implementation : https://stackoverflow.com/questions/16797468/how-to-send-a-multipart-form-data-post-in-android-with-volley And here is a possible Swift implementation : https://stackoverflow.com/questions/43492089/how-to-send-form-data-in-post-request-in-swift-3

mightytyphoon commented 2 years ago

Edit : Hi, because this repo is unfinished and seems already dead, I finally abandoned @capacitor/http and ended using Axios instead to be able to use http only cookies and post forms.

Bye.

EmilMatei commented 2 years ago

Hi, I finally ended using Axios to use http only cookies and post forms.

Bye.

When I've read this the first time (the "ended using axios" part) I thought it's something like "I'm done with axios, enough is enough". I understood it wrong and axios is the solution. I'm leaving this comment for other non native English speakers that may get confused as well.

maudvd commented 2 years ago

Will there be a fix for the FormData ? Axios is not a solution for some of us and using FormData seems like an essential part of a HTTP plugin.

mightytyphoon commented 2 years ago

@modvd if you want to use cookies for persisting sessions and also formData, you don't have any other solution, check this repo, it's dead anyway. There is no more feedbacks on PR, only one guy has the hand on the repo and seems dead, so I don't think you should keep using this anyway. Either make your own or use Axios.

What I advise you to do is to create a http service inside your Ionic/Capacitor project, that will normalize the http requests. Meaning if one day this repo gets back on its feet, or you find something better than axios, you can only change one file to implement a new way of making the http requests in your app.

import { Injectable } from '@angular/core';
import { AxiosRequestConfig , default as http } from 'axios';

@Injectable({
  providedIn: 'root'
})
export class HttpService {

  constructor() { }

  public async get(url: string , config: AxiosRequestConfig){
    return await http.get(url , config);
  }

  public async post(url: string , body: any , config: AxiosRequestConfig){
    return await http.post(url , body , config);
  }

  public async delete(url: string , config: AxiosRequestConfig){
    return await http.delete(url , config);
  }

  public async head(){}
  public async put(){}

}

Of course this can be pumped up by adding typings, as well as Observable.

riderx commented 2 years ago

Just got my whole app stop to function because i use, this lib as custom fetcher for Supabase, and they made the storage API use the custom fetcher last week. i will have a check and do a PR maybe next week if i have time Edit i didn't found a way to fix the module, and it's going to get deprecated in v4 in the meantime i found a solution for my use case in Supabase

export const useSupabase = () => {
  const options: SupabaseClientOptions = {
    fetch: (requestInfo, requestInit) => {
      const url = requestInfo.toString()
      if (requestInit?.method === 'POST' && url.includes('/storage/')) {
        return fetch(requestInfo, requestInit)
      }
      return Http.request({
        url,
        method: requestInit?.method,
        headers: requestInit?.headers as any || {},
        data: requestInit?.body,
      })
        .then((data) => {
          const type = data.headers['content-type']
          const res = type.includes('application/vnd.pgrst.object+json') ? data.data : JSON.stringify(data.data)
          const resp = new Response(res, {
            status: data.status,
            headers: data.headers,
          })
          return resp
        })
    },
  }
  return createClient(supabaseUrl, supabaseAnonKey, options)
}
nhh commented 2 years ago

+1

MattSynaptic commented 2 years ago

This is also a big problem for us as well. Has there been no progress yet?

@FernetB or @mightytyphoon Is there a Capacitor plugin that you used to implement Axios or just the normal JS lib?

mightytyphoon commented 2 years ago

This is also a big problem for us as well. Has there been no progress yet?

@FernetB or @mightytyphoon Is there a Capacitor plugin that you used to implement Axios or just the normal JS lib?

Hi, I created a http service file that handles all http requests (patch, post, get, etc...) with parameters, for now I have just 3 apps using this, so I don't need to create a package for easy management but I would advise to do so. This way if one day you need to change http and use this plugin, in case it's patched, you can easily change only the file once and this will work on all your projects by a simple update.

And yes, axios, you simply need to install it in your project with npm. It works seemlessly, don't forget to use withCredentials=true to send cookies during authenticated requests.

maudvd commented 2 years ago

Axios was not a good solution for me in my Ionic app. I ended up going back to the cordova http plugin "cordova-plugin-advanced-http" which works great with FormData.

mightytyphoon commented 2 years ago

Axios was not a good solution for me in my Ionic app. I ended up going back to the cordova http plugin "cordova-plugin-advanced-http" which works great with FormData.

Could you explain why, please ?

maudvd commented 2 years ago

Axios was not a good solution for me in my Ionic app. I ended up going back to the cordova http plugin "cordova-plugin-advanced-http" which works great with FormData.

Could you explain why, please ?

Because of the CORS restrictions when developing and running the app on a device.

mightytyphoon commented 2 years ago

Axios was not a good solution for me in my Ionic app. I ended up going back to the cordova http plugin "cordova-plugin-advanced-http" which works great with FormData.

Could you explain why, please ?

Because of the CORS restrictions when developing and running the app on a device.

Oh yeah right, I had this problem also, you have to manage them yourself on server side. Can be annoying indeed but it works.

rsushant commented 2 years ago

I am facing same issue with this plugin. I tried implementing @ionic-native/http/ngx or cordova-plugin-advanced-http, project builds successfully. But in execution post methods doesn't get called or throws any exception.

Please fix capacitor-comminuty/http asap!!

mightytyphoon commented 2 years ago

I am facing same issue with this plugin. I tried implementing @ionic-native/http/ngx or cordova-plugin-advanced-http, project builds successfully. But in execution post methods doesn't get called or throws any exception.

Please fix capacitor-comminuty/http asap!!

Would love to make a PR and fix this, (to do so you just need to code the formData functionality both under swift of iOS and java for android, then create the interface for execution under js/typescript), but repo master isn't responding at all nor looking at PRs...

So imo best way is to use axios, which works great, but as pointed out by @modvd you will need to manage CORS server side.

riderx commented 2 years ago

@mightytyphoon @rsushant the repo admin is working on 100% compatible solution to integrate into Capacitor core as mentioned in the README : https://github.com/capacitor-community/http#maintence-mode Be patient better is coming :) There issue to follow also https://github.com/ionic-team/capacitor/issues/5145

mightytyphoon commented 2 years ago

@mightytyphoon @rsushant the repo admin is working on 100% compatible solution to integrate into Capacitor core as mentioned in the README : https://github.com/capacitor-community/http#maintence-mode

Be patient better is coming :)

Yeah sure, the guy came like 4 months ago, just to put one line in The readme and ignore all messages and community's PRs...

brilliantinsane commented 2 years ago

Still nothing?