capacitor-community / http

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

`Http.uploadFile()` has a request `content-length` of 0, resulting in a 0-byte file upload #191

Open ptmkenny opened 3 years ago

ptmkenny commented 3 years ago

Describe the support request How can I use this plugin to upload an image file with Http.post()?

I can't use Http.uploadFile() because my Drupal backend requires a CSRF token.

To Reproduce

In axios, I post a file like this:

    const arrayStr = await readFileBlob(fileBlob) as ArrayBuffer;
    axiosAuthConfig.post(
      fetchUrl, arrayStr, {
        headers: {
          'Content-Disposition': 'file; filename="mypic.jpg"',
          'Content-Type': 'application/octet-stream',
        },
      },
    )

Here's readFileBlob():

// https://stackoverflow.com/a/46568146
function readFileBlob(fileBlob: Blob) {
  return new Promise((resolve) => {
    const reader = new FileReader();
    reader.onload = () => {
      // console.log('reader RESULT', reader.result);
      resolve(reader.result);
    };
    reader.onabort = () => Promise.reject(Error('file reading was aborted'));
    reader.onerror = () => Promise.reject(Error('file reading has failed'));
    reader.readAsArrayBuffer(fileBlob);
  });
}

I tried to do the same thing with Http.post(). Here is the config:

const fetchPostFileOptions = (url: string, data: unknown): HttpOptions => ({
  method: 'POST',
  url,
  data,
  headers: {
    Accept: JSONAPI,
    Cache: 'no-cache',
    'Content-Disposition': 'file; filename="mypic.jpg"',
    'Content-Type': 'application/octet-stream',
  },
  webFetchExtra: {
    credentials: 'include',
  },
});

And then here is where I call Http:

    const options = fetchPostFileOptions(fetchUrl, arrayStr);

    const csrfToken = await getCsrfToken();
    if (typeof csrfToken === 'string' && options.headers) {
      options.headers['X-CSRF-Token'] = csrfToken;
    }
    return (Http.request(options));

The axios request posts successfully, and the Http request fails to post with an nginx 502 errror.

When I check Chrome Dev Tools, I see that the request headers are identical except that the content-length is only 2 for Http and 200,000+ for axios.

So, somehow the file is not getting sent properly with Http. What do I need to do to get it working, or is this not supported?

thomasvidas commented 2 years ago

Does using Http.uploadFile() function not fit your use case? If not, could you post a small, reproducible example so I could take a look at it?

ptmkenny commented 2 years ago

After some more attempts, I managed to get the same error with Http.uploadFile().

Here's my code:

const postAuthFileBlob = async (
  fetchUrl: string, fileBlob: Blob,
): Promise<any> => {
  const csrfToken = await getCsrfToken();
  if (typeof csrfToken !== 'string') {
    throw new Error('Could not get CSRF token!');
  }

  const options = {
    url: fetchUrl,
    name: 'profile_pic',
    blob: fileBlob,
    headers: {
      // Drupal's JSON:API requires the next two lines.
      'Content-Disposition': 'file; filename="profile_pic.jpg"',
      'Content-Type': 'application/octet-stream',
      // Drupal needs a csrfToken for auth users to POST.
      'X-CSRF-Token': csrfToken,
    },
    method: 'POST',
    webFetchExtra: {
      credentials: 'include' as RequestCredentials,
    },
  };

  return (Http.uploadFile(options).then((response) => (
    console.log('response', response);
  )).catch((error) => {
    console.log('postFileError', error, error.message);
  }));
};

This code results in a successful POST-- but, on the server, the file profile_pic.jpg is a 0-byte file.

The file does not appear to be reaching the server, because in Chrome Dev tools, I'm seeing content-length: 0 in the POST Request Headers. When I upload with axios using the code in the original post, the length is 200,000+.

I am guessing this is because when I supply blob to Http.uploadFile, it needs to be read into an array buffer for Drupal to accept it (as I do with axios in the function readFileBlob() in the original post).

I'm going to continue to debug this, but to make sure I'm going in the right direction, am I correct in assuming that if http.uploadFile() is working properly, the content-length in the Request Headers should always be much larger than 0?