I want to send a new FormData() as the body of a POST request using the fetch api
The operation looks something like this:
var formData = new FormData()
formData.append('myfile', file, 'someFileName.csv')
fetch('https://api.myapp.com',
{
method: 'POST',
headers: {
"Content-Type": "multipart/form-data"
},
body: formData
}
)
The problem here is that the boundary, something like
boundary=----WebKitFormBoundaryyEmKNDsBKjB7QEqu
never makes it into the Content-Type: header
It should look like this:
Content-Type:multipart/form-data; boundary=----WebKitFormBoundaryyEmKNDsBKjB7QEqu
When you try the "same" operation with a new XMLHttpRequest(), like so:
var request = new XMLHttpRequest()
request.open("POST", "https://api.mything.com")
request.withCredentials = true
request.send(formData)
the headers are correctly set
Content-Type:multipart/form-data; boundary=----WebKitFormBoundaryyEmKNDsBKjB7QEqu
So my questions are:
how do I make fetch behave exactly like XMLHttpRequest in this situation?
if this is not possible, why?
Thanks everybody! This community is more or less the reason I have professional success.
The solution to the problem is to explicitly set Content-Type to undefined so that your browser or whatever client you're using can set it and add that boundary value in there for you. Disappointing but true.
I removed "Content-Type" and added 'Accept' to http headers and it worked for me. Here are the headers I used,
'headers': new HttpHeaders({
// 'Content-Type': undefined,
'Accept': '*/*',
'Authorization':
"Bearer "+(JSON.parse(sessionStorage.getItem('token')).token),
'Access-Control-Allow-Origin': this.apiURL,
'Access-Control-Allow-Methods': 'GET, POST, OPTIONS, PUT, PATCH, DELETE',
'Access-Control-Allow-Headers': 'origin,X-Requested-With,content-type,accept',
'Access-Control-Allow-Credentials': 'true'
})
fetch(url,options)
If you set a string as options.body, you have to set the Content-Type in request header ,or it will be text/plain by default.
If options.body is specific object like let a = new FormData() or let b = new URLSearchParams(), you don't have to set the Content-Type by hand.It will be added automaticlly.
for a ,it will be something like
multipart/form-data; boundary=----WebKitFormBoundary7MA4YWxkTrZu0gW
as you see, the boundary is automaticlly added.
for b, it is application/x-www-form-urlencoded;
I had the same issue, and was able to fix it by excluding the Content-Type property, allowing the browser to detect and set the boundary and content type automatically.
Your code becomes:
var formData = new FormData()
formData.append('myfile', file, 'someFileName.csv')
fetch('https://api.myapp.com',
{
method: 'POST',
body: formData
}
)
Add headers:{content-type: undefined} browser will generate a boundary for you
that is for uploading a file part-and-part with streaming
if you are adding 'multiple/form-data' it means you should create streaming and upload your file part-and-part
So it is okay to add request.headers = {content-type: undefined}
I'm using the aurelia-api (an wrapper to aurelia-fetch-client).
In this case the Content-Type default is 'application/json'. So I set the Content-Type to undefined and it worked like a charm.
According to FormData documentation, you shoudn't manually set the Content-Type header so browser itself will set it correctly:
Warning: When using FormData to submit POST requests using XMLHttpRequest or the Fetch_API with the multipart/form-data Content-Type (e.g. when uploading Files and Blobs to the server), do not explicitly set the Content-Type header on the request. Doing so will prevent the browser from being able to set the Content-Type header with the boundary expression it will use to delimit form fields in the request body.
So if your code (or library/middleware/etc) manually set the Content-Type, you have two ways to fix it:
rewrote your code (or whatever you use) to don't set Content-Type by default
set Content-Type to undefined or remove it from headers to let your browser do it's work
Related
I am sending a post request with axios. However, when I check the browser console, I see that the request header is actually content-type: multipart/form-data. How can I enforce application/x-www-form-urlencoded? Or does it even matter?
let data = new FormData();
data.append('grant_type', 'authorization_code');
// ... removed for conciseness
return axios.post(`${AUTH_URL}/token`,
data,
{
headers: {
'Content-Type': 'application/x-www-form-urlencoded'
}
}
FormData objects always serialise to multipart/form-data. They have to because they support file uploading and application/x-www-form-urlencoded and application/json do not.
If you want to send application/x-www-form-urlencoded data you should pass a URLSearchParams object instead.
This is described in the axios documentation.
In either case, you shouldn't specify the Content-Type in the headers since the underlying browser APIs will infer it correctly from the object type passed as the body data.
I set axios common header Content-Type = application/json by below code:
axios.defaults.headers.common['Content-Type'] = 'application/json';
But when I tried to use axios.post('apiurl', json), browser Content-Type still is application/x-www-form-urlencoded
I tried to use axios.post('Get_Office_PO_NO_Data', json, { headers: { 'content-type': 'application/json' } }) and axios can request application/json content type
Per the documentation, you want post, not common:
axios.defaults.headers.post['Content-Type'] = 'application/json';
// −−−−−−−−−−−−−−−−−−−−^^^^
Which makes sense; other request types (GET, DELETE, etc.) don't have any request body to apply the content type to. (PUT and PATCH do, but I guess they figured it was unlikely you'd want to set the same default content type for POST and PUT, or it was just the whim of the developer.)
(Alternatively you might consider creating an instance with your defaults via axios.create, and then using the instance. Changing global defaults always makes me a bit uneasy. :-) )
I read all over but couldn't find the answer.
When I use FormData(), it returns status 404 bad request.
However, if I pass the data (hardcoded) as in const requestBody (example below), it works perfectly.
This is my code:
var formData = new FormData();
formData.append("nickname", "johxns");
formData.append("password", "john_password");
formData.append("email", "john#server.com");
// If I do it this way, and assign this to body inside fetch, it works perfectly
// const requestBody = '{"nickname": "johxns","password":"john_password","email":"john#server.com"}';
fetch("http://localhost:5000/create_user", {
// if instead of formData, I assign requestBody to body, it works!
body: formData,
headers: {
"Content-Type": "application/json"
},
method: "POST"
}).then(function(response) {
return response.text();
}).then(function(data){
console.log('data', data);
}).catch(function(err){
console.err(err);
});
I already tried with URLSearchParams, but still couldn't make it work.
Thanks.
You shouldn't set the Content-Type header to application/json if you're not sending json. According to this answer, you don't need to set the Content-Type header.
body data type must match "Content-Type" header
Using Fetch
You should either send json data if you set Content-Type to application/json or not set any Content-Type if using FormData API since the fetch function is able to determine the correct Content-Type.
See here for more informations.
According to the fetch specs it appears that as long as a Content-Type is specified that is one of "application/x-www-form-urlencoded", "multipart/form-data", or "text/plain" and other conditions are satisfied then a POST request should not result in a preflight request. In practice however I've had a difficult time specifying multiple headers for fetch in a way that doesn't cause the OPTIONS request for the preflight check.
ex 1.
fetch("https://differentsubodmain.example.com/api/resource", {
headers: {
"Content-Type": "text/plain, application/json",
Accept: "application/json"
},
method: "POST",
body: JSON.stringify({})
})
ex 2.
var myHeaders = new Headers();
myHeaders.append('Accept', 'application/json');
myHeaders.append('Content-Type', 'text/plain');
myHeaders.append('Content-Type', 'application/json');
fetch("https://differentsubodmain.example.com/api/resource", {
headers: myHeaders,
method: "POST",
body: JSON.stringify({})
})
ex 3.
fetch("https://differentsubodmain.example.com/api/resource", {
headers: [
["Content-Type", "application/json"],
["Content-Type", "text/plain"],
["Accept", "application/json"]
],
method: "POST",
body: JSON.stringify({})
})
Neither of these examples succeed in requesting without the preflight request but specifying either with only "Content-Type": "text/plain" appears to work just fine. The example here however shows both being specified in a request and suggests that it shouldn't cause a preflight. Is this just an issue with different browser implementations or am I missing something?
It looks like perhaps I hadn't read that reference carefully. Below is the important excerpt.
Warning. This intentionally does not use extract a MIME type as that algorithm is rather forgiving and servers are not expected to implement it.
If extract a MIME type were used the following request would not result in a CORS preflight and a naïve parser on the server might treat the request body as JSON
It looks like we are largely constrained to the mime types application/x-www-form-urlencoded, multipart/form-data, or text/plain to avoid preflight requests for CORS.
Reference:
https://fetch.spec.whatwg.org/#example-cors-safelisted-request-header-content-type
I'm trying to use fetch api.
First i create a new Headers() object:
var oHeaders = new Headers({
'Accept': 'application/json',
'Content-Type': 'application/json',
"X-DocuSign-Authentication": '{"Username":"xxx","Password":"xxx","IntegratorKey":"xxx"}'
})
After headers is instantiated if i try to log headers everything is correct.
oHeaders.forEach(function(v){console.log(v)})
//logs: 2 application/json {"Username":"xxx","Password":"xxx","IntegratorKey":"xxx"}
the i create the Request object:
var oReq = new Request('https://eu.docusign.net/restapi/v2/login_information', {
method: 'GET',
headers: oHeaders,
mode: 'no-cors',
});
If i try to log the headers of the request object only the accept header will be there.
oReq.headers.forEach(function(v){console.log(v)})
//logs: application/json
If i try to fetch(oReq) i get 401 unauthorized response.
What makes the headers disappear?
When you set mode: 'no-cors'for a request, browsers won’t allow you to set any request headers other than CORS-safelisted request-headers. See the spec requirements:
To append a name/value (name/value) pair to a Headers object (headers), run these steps:
Otherwise, if guard is "request-no-cors" and name/value is not a CORS-safelisted request-header, return.
In that algorithm, return equates to “return without adding that header to the Headers object”.