Modify the URL of a new Request Object in ES6 - javascript

I'm just playing around the with Fetch API and I came across something that I can't seem to find the answer to. If I create a new request object like so:
let request = new Request('http://www.foo.com', {
method: 'GET',
mode: 'no-cors',
headers: new Headers({
'Content-Type': 'application/x-www-form-urlencoded'
})
});
If I then try and modify the URL (append a query string to it) I receive an error in the browser (Cannot assign to read only property). I know that usually with an object, you can set writable to 'True' but does this work the same for a request object?
The reason that I ask is that I am trying to append a querystring to the end of the URL. These options are in another object, which I've got the values of and concatenated into a string so that it's something like:
number=1&id=130&foo=bar etc.
Am I trying to over engineer what I am doing here?

You can copy all the properties from the old request into a new one with a new url like so:
const init = {
method: 'GET',
mode: 'no-cors',
headers: new Headers({
'Content-Type': 'application/x-www-form-urlencoded'
})
};
const oldRequest = new Request("https://old-url.com", init);
const newRequest = new Request("https://new-url.com", oldRequest);
console.log(newRequest.mode === oldRequest.mode); // true
console.log(newRequest.headers.get('Content-Type') === oldRequest.headers.get('Content-Type')); // true
console.log(newRequest.method === oldRequest.method); // true

Request object is immutable by design, don't try to change it.
I wish clone() instance method accepted options to be able to to tweak the cloned object, but it doesn't. So I came up with this function:
function appendQuery(urlOrReq, queryString) {
// make sure we deal with a Request object even if we got a URL string
const req = urlOrReq instanceof Request ? urlOrReq : new Request(urlOrReq);
const {
cache, credentials, headers, integrity, method,
mode, redirect, referrer, referrerPolicy, url, body
} = req;
// Don't add query string if there's none
const urlWithQuery = url + (!queryString ? '' : '?' + queryString);
return new Request(urlWithQuery, {
cache, credentials, headers, integrity, method,
mode, redirect, referrer, referrerPolicy, body
})
}
Here's the same in TypeScript (only the first line is different):
function appendQuery(urlOrReq: RequestInfo, queryString?: string): Request {
// make sure we deal with a Request object even if we got a URL string
const req = urlOrReq instanceof Request ? urlOrReq : new Request(urlOrReq);
const {
cache, credentials, headers, integrity, method,
mode, redirect, referrer, referrerPolicy, url, body
} = req;
// Don't add query string if there's none
const urlWithQuery = url + (!queryString ? '' : '?' + queryString);
return new Request(urlWithQuery, {
cache, credentials, headers, integrity, method,
mode, redirect, referrer, referrerPolicy, body
})
}
Here's how it works:
const req = new Request('https://google.com')
appendQuery(req, 'foo=42')
// => Request {method: "GET", url: "https://google.com?foo=42", headers: Headers, destination: "", referrer: "about:client", …}

Related

What the difference between these two API calls? [duplicate]

I want to set _boundry in my header.
First, I dispatch the form data:
//component.js
const form = new FormData();
form.append('email', 'eray#serviceUser.com')
form.append('password', '12121212')
dispatch(FetchLogin.action(form))
Second, I prepare api call;
//loginService.js
import api from '#/Services'
export default async form => {
const response = await api.post('user/login/', form)
return response.data
}
Third, I make api call;
//Services/index.js
import axios from 'axios'
import { Config } from '#/Config'
const instance = axios.create({
baseURL: Config.API_URL,
headers: {
'Content-Type': `multipart/form-data; boundary=${form._boundary}`, //Cannot access form here
},
timeout: 3000,
})
instance.interceptors.response.use(
response => response,
({ message, response: { data, status } }) => {
return handleError({ message, data, status })
},
)
export default instance
I want to access form data within to axios instance to be able to use form._boundry in headers.
How can I pass form data from loginService.js to Services/index.js?
This question seems to come up often enough yet I cannot seem to find a canonical answer so here goes...
When performing AJAX requests from a browser (via fetch or XMLHttpRequest), the runtime knows what to do for certain request body formats and will automatically set the appropriate Content-type header
If the request body is a FormData instance, the Content-type will be set to multipart/form-data and will also include the appropriate mime boundary tokens from the data instance.
All of these examples will post the data as multipart/form-data with appropriate mime boundary tokens
const body = new FormData();
// attach files and other fields
body.append("file", fileInput.files[0]);
body.append("foo", "foo");
body.append("bar", "bar");
// fetch
fetch(url, { method: "POST", body });
// XMLHttpRequest
const xhr = new XMLHttpRequest();
xhr.open("POST", url);
xhr.send(body);
// Axios
axios.post(url, body);
If the request body is a URLSearchParams instance, the Content-type will be set to application/x-www-form-urlencoded
All of these examples will post the data as application/x-www-form-urlencoded
const body = new URLSearchParams({ foo: "foo", bar: "bar" });
// serialises to "foo=foo&bar=bar"
// fetch
fetch(url, { method: "POST", body });
// XMLHttpRequest
const xhr = new XMLHttpRequest();
xhr.open("POST", url);
xhr.send(body);
// Axios
axios.post(url, body);
You only need to manually set the content-type if you intend to send string data in a particular format, eg text/xml, application/json, etc since the runtime cannot infer the type from the data.
const body = JSON.stringify({ foo: "foo", bar: "bar" });
// fetch
fetch(url, {
method: "POST",
headers: {
"content-type": "application/json",
},
body
});
// XMLHttpRequest
const xhr = new XMLHttpRequest();
xhr.open("POST", url);
xhr.setRequestHeader("content-type", "application/json");
xhr.send(body);
On Axios
Axios will automatically stringify JavaScript data structures passed into the data parameter and set the Content-type header to application/json so you only need minimal configuration when dealing with JSON APIs
// no extra headers, no JSON.stringify()
axios.post(url, { foo: "foo", bar: "bar" })
Under the hood, Axios uses XMLHttpRequest so the specifications for FormData and URLSearchParams also apply.
Axios v0.27.1 is broken
This specific version of Axios is unable to make a proper request with FormData. Do not use it!
Axios v1.0.0+ is broken
This is verging into my own opinion but every Axios release post v1.0.0 has been fundamentally broken in one way or another. I simply cannot recommend anyone use it for any reason.
Much better alternatives are:
The Fetch API (also available in Node v18+)
got for Node.js
ky for browsers
NodeJS
When using Axios from the backend, it will not infer Content-type headers from FormData instances. You can work around this using a request interceptor.
axios.interceptors.request.use(config => {
if (config.data instanceof FormData) {
Object.assign(config.headers, config.data.getHeaders());
}
return config;
}, null, { synchronous: true });
or simply merge in the headers when making a request
axios.post(url, body, {
headers: {
"X-Any-Other-Headers": "value",
...body.getHeaders(),
},
});
See https://github.com/axios/axios#form-data
On jQuery $.ajax()
jQuery's $.ajax() method (and convenience methods like $.post()) default to sending request body payloads as application/x-www-form-urlencoded. JavaScript data structures will be automatically serialised using jQuery.param() unless told not to. If you want the browser to automatically set the Content-type header based on the body format, you also need to configure that in the options
const body = new FormData()
body.append("foo", "foo")
body.append("bar", "bar")
$.ajax({
url,
method: "POST",
data: body,
contentType: false, // let the browser figure it out
processData: false // don't attempt to serialise data
})

How to take an mp3 file as input from user in React or JavaScript [duplicate]

I want to set _boundry in my header.
First, I dispatch the form data:
//component.js
const form = new FormData();
form.append('email', 'eray#serviceUser.com')
form.append('password', '12121212')
dispatch(FetchLogin.action(form))
Second, I prepare api call;
//loginService.js
import api from '#/Services'
export default async form => {
const response = await api.post('user/login/', form)
return response.data
}
Third, I make api call;
//Services/index.js
import axios from 'axios'
import { Config } from '#/Config'
const instance = axios.create({
baseURL: Config.API_URL,
headers: {
'Content-Type': `multipart/form-data; boundary=${form._boundary}`, //Cannot access form here
},
timeout: 3000,
})
instance.interceptors.response.use(
response => response,
({ message, response: { data, status } }) => {
return handleError({ message, data, status })
},
)
export default instance
I want to access form data within to axios instance to be able to use form._boundry in headers.
How can I pass form data from loginService.js to Services/index.js?
This question seems to come up often enough yet I cannot seem to find a canonical answer so here goes...
When performing AJAX requests from a browser (via fetch or XMLHttpRequest), the runtime knows what to do for certain request body formats and will automatically set the appropriate Content-type header
If the request body is a FormData instance, the Content-type will be set to multipart/form-data and will also include the appropriate mime boundary tokens from the data instance.
All of these examples will post the data as multipart/form-data with appropriate mime boundary tokens
const body = new FormData();
// attach files and other fields
body.append("file", fileInput.files[0]);
body.append("foo", "foo");
body.append("bar", "bar");
// fetch
fetch(url, { method: "POST", body });
// XMLHttpRequest
const xhr = new XMLHttpRequest();
xhr.open("POST", url);
xhr.send(body);
// Axios
axios.post(url, body);
If the request body is a URLSearchParams instance, the Content-type will be set to application/x-www-form-urlencoded
All of these examples will post the data as application/x-www-form-urlencoded
const body = new URLSearchParams({ foo: "foo", bar: "bar" });
// serialises to "foo=foo&bar=bar"
// fetch
fetch(url, { method: "POST", body });
// XMLHttpRequest
const xhr = new XMLHttpRequest();
xhr.open("POST", url);
xhr.send(body);
// Axios
axios.post(url, body);
You only need to manually set the content-type if you intend to send string data in a particular format, eg text/xml, application/json, etc since the runtime cannot infer the type from the data.
const body = JSON.stringify({ foo: "foo", bar: "bar" });
// fetch
fetch(url, {
method: "POST",
headers: {
"content-type": "application/json",
},
body
});
// XMLHttpRequest
const xhr = new XMLHttpRequest();
xhr.open("POST", url);
xhr.setRequestHeader("content-type", "application/json");
xhr.send(body);
On Axios
Axios will automatically stringify JavaScript data structures passed into the data parameter and set the Content-type header to application/json so you only need minimal configuration when dealing with JSON APIs
// no extra headers, no JSON.stringify()
axios.post(url, { foo: "foo", bar: "bar" })
Under the hood, Axios uses XMLHttpRequest so the specifications for FormData and URLSearchParams also apply.
Axios v0.27.1 is broken
This specific version of Axios is unable to make a proper request with FormData. Do not use it!
Axios v1.0.0+ is broken
This is verging into my own opinion but every Axios release post v1.0.0 has been fundamentally broken in one way or another. I simply cannot recommend anyone use it for any reason.
Much better alternatives are:
The Fetch API (also available in Node v18+)
got for Node.js
ky for browsers
NodeJS
When using Axios from the backend, it will not infer Content-type headers from FormData instances. You can work around this using a request interceptor.
axios.interceptors.request.use(config => {
if (config.data instanceof FormData) {
Object.assign(config.headers, config.data.getHeaders());
}
return config;
}, null, { synchronous: true });
or simply merge in the headers when making a request
axios.post(url, body, {
headers: {
"X-Any-Other-Headers": "value",
...body.getHeaders(),
},
});
See https://github.com/axios/axios#form-data
On jQuery $.ajax()
jQuery's $.ajax() method (and convenience methods like $.post()) default to sending request body payloads as application/x-www-form-urlencoded. JavaScript data structures will be automatically serialised using jQuery.param() unless told not to. If you want the browser to automatically set the Content-type header based on the body format, you also need to configure that in the options
const body = new FormData()
body.append("foo", "foo")
body.append("bar", "bar")
$.ajax({
url,
method: "POST",
data: body,
contentType: false, // let the browser figure it out
processData: false // don't attempt to serialise data
})

React ajax request with multipart file and json data

I have a base request like this:
export const request = (options) => {
const headers = new Headers({
'Content-Type': 'application/json',
});
if (Common.getToken()) {
headers.append('Authorization', 'Bearer ' + Common.getToken())
}
const defaults = {headers: headers};
options = Object.assign({}, defaults, options);
return fetch(options.url, options)
.then(response =>
response.json().then(json => {
if (!response.ok) {
return Promise.reject(json);
}
return json;
})
);
};
and my ajax request:
onCreateNewPost(postDataRequest, photoBody) {
const formData = new FormData();
formData.append('photo', photoBody);
formData.append('postData', JSON.stringify(postDataRequest));
return request({
url: API_BASE_URL + '/posts/new-post',
method: 'POST',
body: formData
});
};
where postDataRequest - json object included post title, description etc...
photoBody - image file.
In the backend I have a controller's method:
#PostMapping(value = "/api/posts/new-post")
#PreAuthorize("hasRole('ADMIN')")
public ResponseEntity createNewPost(#CurrentUser UserPrincipal currentUser,
#RequestBody NewPostDataRequest postRequest,
#RequestParam MultipartFile photo) {
// method body
return ResponseEntity.ok(new ActionCompleteResponse(true));
}
but when I send a request, I get Status Code: 400. What is the problem? I can separately send either json data or multipart data, but I can’t figure out how to transfer them together with one request. I tried to put headers without a Content-Type in the request, as in the code below, so that the request itself indicates it, but in response I get code 415.
onCreateNewPost(postDataRequest, photoBody) {
const formData = new FormData();
formData.append('photo', photoBody);
formData.append('postData', JSON.stringify(postDataRequest));
const headers = new Headers({});
if (Common.getToken()) {
headers.append('Authorization', 'Bearer ' + Common.getToken());
}
return request({
url: API_BASE_URL + '/posts/new-post',
headers: headers,
method: 'POST',
body: formData
});
};
What should I do?
Okay, I found the solution:
1. Clear headers data (except Authorization token)
2. Add to #PostMapping consumes = MediaType.MULTIPART_FORM_DATA_VALUE and add #RequestPart to method parameter
ajax request like:
onCreateNewPost(postDataRequest, photoBody) {
const formData = new FormData();
formData.append('post', new Blob([JSON.stringify(postDataRequest)], {
type: "application/json"
}));
formData.append('photo', photoBody);
const headers = new Headers({});
if (Common.getToken()) {
headers.append('Authorization', 'Bearer ' + Common.getToken())
}
return request({
url: API_BASE_URL + '/posts/new-post',
method: 'POST',
headers: headers,
body: formData
});
};
and spring controller like
#PostMapping(value = "/new-post", consumes = MediaType.MULTIPART_FORM_DATA_VALUE)
#PreAuthorize("hasRole('USER')")
public ResponseEntity createNewPost(#CurrentUser UserPrincipal currentUser,
#RequestPart("post") #Valid PostEntity post,
#RequestPart("photo") #Valid MultipartFile photo) throws IOException {
post.setAuthor(currentUser.getUsername());
post.setAuthorId(currentUser.getId());
post.setCommentsCount(0L);
post.setDate(LocalDate.now());
post.setPhoto(photo.getBytes());
postService.save(post);
return ResponseEntity.ok(new ActionCompleteResponse(true));
}
#Sergey Scream solution is correct, I just want to add some information to clarify the problem.
So if you want to send json and a file using FormData you have to wrap your json in a blob including the type like he did:
formData.append('post', new Blob([JSON.stringify(postDataRequest)], {
type: "application/json"
}));
Adding your json like this will not work:
formData.append('post', JSON.stringify(postDataRequest));
You're setting Content-Type to application/json in request but body to form data in onCreateNewPost. If you removed line 3 from request then your current solution should work.
Also you're setting the Authorization header twice in onCreateNewPost and request.

HttpParams set null for call

I have a problem with HttpParams and HttpHeaders after migrating my project from Angular 7 to Angular 8. When I call the API the params are not added. If anyone can help me fix this problem it will be great.
Here is the method in which I define the headers as well as the params.
fetchJson(url: string, parameters ? : any) {
this.token = this.cookieService.get('access_token');
this.contrat_token = this.cookieService.get('contrat_token');
let headers = new HttpHeaders();
headers = headers.append('Content-Type', 'application/json');
headers = headers.append('Authorization', 'Bearer ' + this.token);
headers = headers.append('contrat_token', this.contrat_token);
let params = new HttpParams()
params.set('search', parameters);
console.log('les headers');
console.log(headers);
console.log('params');
console.log(params.toString())
return this._http.get(url, {
headers,
params
}).pipe(map((resp: any) => {
if (resp.status === 401 || resp.status == 401 || resp.status.toString() == "401") {
this.clearCookie();
} else {
let reponse = resp;
if (reponse == -1 || reponse == "-1") {
this.router.navigate(["/"]);
}
}
return resp;
}
And I call this method in my services as follows.
getDetailThematiquePrevNext(id: string, typeBase: string) {
let URL = this.urlDecorator.urlAPIDecorate("DI", "GetDetailThematiqueHeaderPrevNext");
let params = this.urlDecorator.generateParameters({
id: id,
typeBase: typeBase,
});
return this.apiFetcher.fetchJson(URL, params);
}
Reason provided by Cue is correct, You need to use chaining or do what you did for headers
let headers = new HttpHeaders();
headers = headers.append('Content-Type', 'application/json');
headers = headers.append('Authorization', 'Bearer ' + this.token);
headers = headers.append('contrat_token', this.contrat_token);
let params = new HttpParams()
params = params = params.set('search', parameters);
More readable way to write this would be as follows
const headers = new HttpHeaders()
.append('Content-Type', 'application/json')
.append('Authorization', 'Bearer ' + this.token)
.append('contrat_token', this.contrat_token);
const params = new HttpParams().set('search', parameters);
Also, you can drop Content-Type header, as it is json by default
Probably due to lazy parsing. You have to do a get or getAll to access values to determine the state.
HttpParams class represents serialized parameters, per the MIME type application/x-www-form-urlencoded. The class is immutable and all mutation operations return a new instance.
HttpHeaders class represents the header configuration options for an HTTP request. Instances should be assumed immutable with lazy parsing.
You may want to pass your options directly into the instance for both headers and params:
let headers = new HttpHeaders({
'Content-Type': 'application/json',
'Authorization': 'Bearer ' + this.token,
'contrat_token': this.contrat_token
});
let params = new HttpParams({
search: parameters
});
As #Siraj stated in an answer, there are alternative ways to set values for headers and params such as set...
let headers = new HttpHeaders().set('name', 'value');
let params = new HttpParams().set('name', 'value');
Or append...
let headers = new HttpHeaders().append('name', 'value');
let params = new HttpParams().append('name', 'value');
The important thing to note here is that these methods require chaining otherwise each method creates a new instance.
You could also convert objects like so:
let headerOptions = {
'Content-Type': 'application/json',
'Authorization': 'Bearer ' + this.token,
'contrat_token': this.contrat_token
}
let headers = new HttpHeaders();
Object.keys(headerOptions).forEach((key) => {
headers = headers.set(key, headerOptions[key]);
});
It's also worth avoiding any binding of objects by reference, and instead pass as parameters:
return this._http.get(url, {
headers: headers,
params: params
});
And finally, because your type annotation is "any" for the parameters argument, params expects HttpParamsOptions which is a key/value object where values must be a string annotation.
let params = new HttpParams({
search: JSON.stringify(parameters)
});
Try console.log(params.getAll('search')) but, to make sure headers and params are sent, a better place to check will be Network tab in DevTools.

Headers object for fetchAPI is always empty

I have this request function that is wrappers around the fetch API to issue request to my API. But when my frontend app issues request, the headers object is always empty. What am I doing wrong ?
export function request(method, url, payload) {
const body = JSON.stringify(payload);
const headers = new Headers();
headers.append('Content-Type', 'application/json');
const parameters = {
headers: headers,
method: method,
body: body,
cache: "default"
};
return Observable.create(observer => {
fetch(url, parameters)
.then(response => {
observer.next(response);
observer.complete();
})
.catch(error => {
observer.error(error);
});
});
}
Have you checked in the network tab of the DevTools if the headers are really missing?
I have the same trouble than you describe, my Header object seems always empty from the Chrome DevTools, but if try to check a specific header like
let headers = new Headers({'X-whatever-name': 'whatever-value'});
myHeader.has('X-whatever-name'); // returns true
Also if I check the detail of the Request Headers in the DevTools (Networking tab), I can see that my custom headers are sent properly.
So only the JS api (entries, keys, ...) seems to be broken for me, but the request is correctly sent.
Try just making it a simple object:
const headers = {
"Content-Type": "application/json"
};

Categories