New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Question: how to a POST with form-data correctly #30

Closed
dgsunesen opened this Issue Aug 12, 2015 · 8 comments

Comments

Projects
None yet
10 participants
@dgsunesen
Copy link

dgsunesen commented Aug 12, 2015

Hi,

So my question is how I would implement the following (with request) using isomorphic-fetch

var options = {
    url: authUrlRoot + 'auth',
    followRedirect: false,
    form: {
      client_id: config.APP_KEY,
      redirect_uri: config.REDIRECT_URL,
      response_type: "code"
    }
  };

  request.post(options, function(error, httpResponse, body){
    // Do something here..
  });

I've tried something like

var FormData = require('form-data');
var form = new FormData();
form.append('client_id', config.APP_KEY);
form.append('redirect_url', config.REDIRECT_URL);
form.append('response_type', 'code');
fetch(authUrlRoot + 'auth', { method: 'POST', body: form })
    .then(function(response) {
        if (response.status >= 400) {
            throw new Error("Bad response from server");
        }
        return response.json();
    })
    .then(function(data) {
        console.log(data);
    });

But that gives me "Bad response from server" - status code is 400 Bad request. I might be totally blind, but I cannot seem to figure out what is missing?

@tracker1

This comment has been minimized.

Copy link

tracker1 commented Aug 13, 2015

The spec says you pass the FormData instance as the body...

fetch("/login", {
  method: "POST",
  body: form  //just pass the instance
})

You may need additional parameters, depending on your use...

fetch(url, {
  credentials: 'include', //pass cookies, for authentication
  method: 'post',
  headers: {
  'Accept': 'application/json, application/xml, text/plain, text/html, *.*',
  'Content-Type': 'application/x-www-form-urlencoded; charset=utf-8'
  },
  body: form
});

Or you can manually encode your data, example below will take an object... (NOTE: ES6 syntax)

//post-as-form.js

// npm module - safe deep cloning
import clone from 'safe-clone-deep'

export {postAsForm as default};

async function postAsForm(url, data) {
  var response;
  try {
    //safe clone of data
    data = clone(data);

    //array that holds the items that get added into the form
    var form = [];

    //special handling for object/array data, arrays will use "model" as the container ns
    addItemsToForm(form, typeof data == 'object' ? [] : [options.name || 'model'], data);

    //you'll need to await on response.text/blob/json etc
    var response = await fetch(url, {
          credentials: 'include', //pass cookies, for authentication
          method: 'post',
          headers: {
          'Accept': 'application/json, application/xml, text/play, text/html, *.*',
          'Content-Type': 'application/x-www-form-urlencoded; charset=utf-8'
          },
          body: form.join('&')
      });

    //return plain text as-is, don't parse it here
    return {
      response: response,
      text: await response.text()
    }
  } catch(err) {
    //console.error("Error from server:", err && err.stack || err);
    err.response = response;
    throw err;
  }
}

function addItemsToForm(form, names, obj) {
  if (obj === undefined || obj === "" || obj === null) return addItemToForm(form, names, "");

  if (
    typeof obj == "string" 
    || typeof obj == "number" 
    || obj === true 
    || obj === false
  ) return addItemToForm(form, names, obj);

  if (obj instanceof Date) return addItemToForm(form, names, obj.toJSON());

  // array or otherwise array-like
  if (obj instanceof Array) {
    return obj.forEach((v,i) => {
      names.push(`[${i}]`);
      addItemsToForm(form, names, v);
      names.pop();
    });
  }

  if (typeof obj === "object") {
    return Object.keys(obj).forEach((k)=>{
      names.push(k);
      addItemsToForm(form, names, obj[k]);
      names.pop();
    });
  }
}

function addItemToForm(form, names, value) {
  var name = encodeURIComponent(names.join('.').replace(/\.\[/g, '['));
  value = encodeURIComponent(value.toString());
  form.push(`${name}=${value}`);
}
@SamNil

This comment has been minimized.

Copy link

SamNil commented Dec 2, 2015

Would you please explain why we clone first ?

@wechta

This comment has been minimized.

Copy link

wechta commented Jan 10, 2016

Hi, im having strange problem, my code is like this:

export function signDocument(uri, jobid, securitycode, docid, signature) {
  var form = new FormData()
  form.append('signature', 1)

  return fetch(uri+jobid+'/documents/'+docid+'/signNextSigPlaceholder?securitycode='+securitycode, {
      method: 'POST',
      headers: {
        'Accept': 'application/json, application/xml, text/plain, text/html, *.*',
        'Content-Type': 'application/x-www-form-urlencoded; charset=utf-8'
      },
      body: form,
    })
    .then(function(response) {
      return checkStatus(response)
    });
}

But on the Api side, i don get signature. but if i use jquery like this:

$.ajax({
          url: postUrl+'/documents/'+ this.state.currentDocument.docId+'/signNextSigPlaceholder?securitycode='+code,
          dataType: 'json',
          method: 'POST',
          contentType:"application/x-www-form-urlencoded",
          data: { signature:podpis },
         cache: false,
          success: function(data) {
            ....
            });
    });

Then i get signature in API.. Event if i try it with Advanced REST client chrome extension, and i pass signature as RAW or FORM, i get the signature.

What could be wrong?

@stevensanborn

This comment has been minimized.

Copy link

stevensanborn commented Aug 2, 2016

So what helped me when i was trying to replicate a request that I got working not using fetch was to look at the request in chrome dev tools in networking. Look and see whats different when you click on the same request URL .ALl the request and form data should match up. My realization was that I needed to send over cookies as well because my API was giving me a 403 ( fexth does not automatically add this like XMLHttpRequest does). I added credentials: 'include', or credentials: 'same-origin', to the fetch command and that added the missing cookies .

Also i had to encode as mentioned above where I did not use FormData and just went with

body:"var1= encodeURIComponent(val1)&var2=encodeURIComponent(val2)..."

IDK if this is your issue but i was in a similar postion where i had a working old school style request I wanted in fetch

@xxd3vin

This comment has been minimized.

Copy link

xxd3vin commented Sep 1, 2016

url-search-params not work in Chrome 44:

    import URLSearchParams from 'url-search-params';
    const searchParams = new URLSearchParams();
    searchParams.set('userid', billUser);
    return fetch(`/sscpub/sckmsg/usermsghistory`, {
      credentials: 'include', //pass cookies, for authentication
      method: 'POST',
      headers: {
        'Accept': 'application/json'
      },
      body: searchParams
    })

So I use the method provided by stevensanborn , it works perfect.

    let billUser = '123';
    let bodyStr = "userid=" + encodeURIComponent(billUser);
    return fetch(`/sscpub/sckmsg/usermsghistory`, {
      credentials: 'include', //pass cookies, for authentication
      method: 'POST',
      headers: {
        'Accept': 'application/json',
        'Content-Type': 'application/x-www-form-urlencoded; charset=utf-8'
      },
      body: bodyStr
    })
@designreact

This comment has been minimized.

Copy link

designreact commented Nov 16, 2016

I've been struggling with this too.

The issue seems fairly random with some requests needing the credentials hack and others not. Below is one of my recent configs with some dummy data.

It seems what was tripping up my code is that OPTIONS and POST responses are both coming through the promise, I would expect only POST responses to do this? Please forgive me if I'm wrong.

My current work around is to catch the 204 (no content) response code sent by express and prevent it interfering with the flow of my app. Any ideas on how to improve this are most welcome.

fetch(`${apiServer}/v1/some/path`, {
    method: 'POST',
    headers: {
    Accept: 'application/json',
    'Content-Type': 'application/json',
  },
  body: JSON.stringify({
    someBodyProperty,
  }),
  credentials: 'same-origin',
})
.then(response => {
  if (response.status >= 400) {
    throw new Error('API Server Error')
  }
  if (response.status === 204) {
    return {
      status: 'options'
    }
  }
  return response.json()
})
.then(({ status, data }) => {
  if (status === 'success') {
    dispatch({ type: types.SUCCESS, data })
  } else if (status !== 'options') {
    dispatch({ type: types.ERROR })
  }
})
@amalv

This comment has been minimized.

Copy link

amalv commented Apr 10, 2017

Regarding @tracker1 response I think you should use URLSearchParams instead of FormData when using application/x-www-form-urlencoded;charset=UTF-8 as Content-Type header, as indicated in the fetch documentation or the fetch specs

@cunjieliu

This comment has been minimized.

Copy link

cunjieliu commented Jul 4, 2017

mark

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment