Is it possible to alter the headers of the Request object that is received by the fetch event?
Two attempts:
Modify existing headers:
self.addEventListener('fetch', function (event) {
event.request.headers.set("foo", "bar");
event.respondWith(fetch(event.request));
});
Fails with Failed to execute 'set' on 'Headers': Headers are immutable.
Create new Request object:
self.addEventListener('fetch', function (event) {
var req = new Request(event.request, {
headers: { "foo": "bar" }
});
event.respondWith(fetch(req));
});
Fails with Failed to construct 'Request': Cannot construct a Request with a Request whose mode is 'navigate' and a non-empty RequestInit.
(See also How to alter the headers of a Response?)
Creating a new request object works as long as you set all the options:
// request is event.request sent by browser here
var req = new Request(request.url, {
method: request.method,
headers: request.headers,
mode: 'same-origin', // need to set this properly
credentials: request.credentials,
redirect: 'manual' // let browser handle redirects
});
You cannot use the original mode if it is navigate (that's why you were getting an exception) and you probably want to pass redirection back to browser to let it change its URL instead of letting fetch handle it.
Make sure you don't set body on GET requests - fetch does not like it, but browsers sometimes generate GET requests with the body when responding to redirects from POST requests. fetch does not like it.
You can create a new request based on the original one and override the headers:
new Request(originalRequest, {
headers: {
...originalRequest.headers,
foo: 'bar'
}
})
See also: https://developer.mozilla.org/en-US/docs/Web/API/Request/Request
Have you tried with a solution similar to the one in the question you mention (How to alter the headers of a Response?)?
In the Service Worker Cookbook, we're manually copying Request objects to store them in IndexedDB (https://serviceworke.rs/request-deferrer_service-worker_doc.html). It's for a different reason (we wanted to store them in a Cache, but we can't store POST requests because of https://github.com/slightlyoff/ServiceWorker/issues/693), but it should be applicable for what you want to do as well.
// Serialize is a little bit convolved due to headers is not a simple object.
function serialize(request) {
var headers = {};
// `for(... of ...)` is ES6 notation but current browsers supporting SW, support this
// notation as well and this is the only way of retrieving all the headers.
for (var entry of request.headers.entries()) {
headers[entry[0]] = entry[1];
}
var serialized = {
url: request.url,
headers: headers,
method: request.method,
mode: request.mode,
credentials: request.credentials,
cache: request.cache,
redirect: request.redirect,
referrer: request.referrer
};
// Only if method is not `GET` or `HEAD` is the request allowed to have body.
if (request.method !== 'GET' && request.method !== 'HEAD') {
return request.clone().text().then(function(body) {
serialized.body = body;
return Promise.resolve(serialized);
});
}
return Promise.resolve(serialized);
}
// Compared, deserialize is pretty simple.
function deserialize(data) {
return Promise.resolve(new Request(data.url, data));
}
If future readers have a need to also delete keys in the immutable Request/Response headers and also want high fidelity to the immutable headers, you can effectively clone the Header object:
const mutableHeaders = new Headers();
immutableheaders.forEach((value, key, parent) => mutableHeaders.set(key, value));
mutableHeaders.delete('content-encoding');
mutableHeaders.delete('vary');
mutableHeaders['host'] = 'example.com';
// etc.
You can then create a new Request and pass in your mutableHeaders.
This is preferred to the accepted answer because if you have the need to proxy a Request, you don't want to manually specify every possible header while including the Cloudflare, AWS, Azure, Google, etc. custom CDN headers.
Background Info
The reason why the headers are immutable or read-only in a Request is because:
interface Request extends Body {
readonly cache: RequestCache;
readonly credentials: RequestCredentials;
readonly destination: RequestDestination;
readonly headers: Headers;
readonly integrity: string;
...
The interface for Headers is:
interface Headers {
append(name: string, value: string): void;
delete(name: string): void;
get(name: string): string | null;
has(name: string): boolean;
set(name: string, value: string): void;
forEach(callbackfn: (value: string, key: string, parent: Headers) => void, thisArg?: any): void;
}
Related
When trying to make a request to API endpoint using Cypress, different results are returned when url is directly passed or from a method/enum. Not sure how to go about it as I don't want to hardcode the urls in request.
const SPACEID = some_space_id
const ENVIRONMENTID = development
const ENTRIES_EP = entries
const CONTENT_TYPE_EP = contentType
enum endPoints {
entries = `https://app.contentful.com/spaces/${SPACEID}/environments/${ENVIRONMENTID}/${ENTRIES_EP}`,
content_type = `https://app.contentful.com/spaces/${SPACEID}/environments/${ENVIRONMENTID}/${CONTENT_TYPE_EP}`,
}
cy.api({
method: 'GET',
url: String(endPoints.content_type),
auth: {
bearer: `SOME_TOKEN`,
},
}).then((response) => {
expect(response.status).to.eq(200)
cy.wrap(response).as('resp')
})
The code above returns some HTML content stating only Chrome, Firefox is supported.
However if I add the absolute URL in cy.api call it works fine. I am not sure what I am doing wrong as I have compared both URLs and they are exactly same.
https://app.contentful.com/spaces/some_space_id/environments/development/content_types
https://api.contentful.com/spaces/some_space_id/environments/development/content_types
I have an assignment to: use the method GET to fetch api info from http://api.example.com/. I'm also told to only use: Javascript/Reactjs
However, everywhere I search, I can only fetch the data. Are these two the same thing? Or am I looking at the wrong tutorials?
import React, { Component } from 'react';
class App extends Component {
constructor(props) {
super(props);
this.state = {
items: [],
isLoaded: false,
}
}
componentDidMount() {
fetch('https://jsonplaceholder.typicode.com/users')
.then(res => res.json())
.then(json => {
this.setState({
isLoaded: true,
items: json,
})
});
}
render() {
var { isLoaded, items } = this.state;
if (!isLoaded) {
return <div>Loading...</div>
}
else {
return (
<div className="App">
<ul>
{items.map(item => (
<li key={item.uid}>
Name: {item.name} | Email:{item.email}
</li>
))};
</ul>
</div>
);
}
}
}
export default App;
they are interchangeable, as a technical jargon from the field, "get data from api" and "fetch data from the api" means the same thing.
in javascript, nowadays, we use the Fetch API to interact with an api, using HTTP request methods like GET, HEAD, POST, DELETE.
unfortunately i don't know a page or repository listing these jargons
Fetch is a javascript API used for making XHR requests across the web (normally for interacting with an API).
let promise = fetch(url, {
method: "GET", // POST, PUT, DELETE, etc.
headers: {
// the content type header value is usually auto-set
// depending on the request body
"Content-Type": "text/plain;charset=UTF-8"
},
body: undefined // string, FormData, Blob, BufferSource, or URLSearchParams
referrer: "about:client", // or "" to send no Referer header,
// or an url from the current origin
referrerPolicy: "no-referrer-when-downgrade", // no-referrer, origin, same-origin...
mode: "cors", // same-origin, no-cors
credentials: "same-origin", // omit, include
cache: "default", // no-store, reload, no-cache, force-cache, or only-if-cached
redirect: "follow", // manual, error
integrity: "", // a hash, like "sha256-abcdef1234567890"
keepalive: false, // true
signal: undefined, // AbortController to abort request
window: window // null
});
But GET is an HTTP verb. The primary or most-commonly-used HTTP verbs (or methods, as they are properly called) are POST, GET, PUT, PATCH, and DELETE. These correspond to create, read, update, and delete (or CRUD) operations, respectively.
To understand more about these methods read Architectural Styles and
the Design of Network-based Software Architectures by Dr Roy Fielding or popularly know as roy fielding paper which describes about the RESTful nature of the web and the use of these HTTP verbs. https://www.ics.uci.edu/~fielding/pubs/dissertation/top.htm
I want to post form data to a server that accepts and returns text/html/xml. I am effectively trying to emulate a normal URL encoded form POST. My Angular 8 POST function successfully posts (200 OK), but the server can't understand the data because it is JSON and not URL encoded.
Response and request headers state Content-Type: text/html; Charset=utf-8 and Accept: text/html, application/xhtml+xml, */* and I have added responseType: "text" to the httpClient options. Why is the server still being sent JSON and not URL encoded data?
// obj2 = output from ngForm
// baseUrl2 = server that sends and receives text/html/xml
public postForm(obj2) {
return this.httpClient
.post(this.baseUrl2, obj2, {
headers: new HttpHeaders({
"Content-Type": "application/x-www-form-urlencoded",
Accept: "text/html, application/xhtml+xml, */*"
}),
responseType: "text"
})
.map(data => data);
}
Form data sent:
{"Form data":{"{\"personsNameText\":\"name9\",\"centreEmailAddressText\":\"name9#name.com\",\"centreTelephoneNumberText\":123456789,\"centreNumberText\":\"ab123\",\"centreNameText\":\"ab123\",\"invoiceText\":\"123456789\",\"currencyText\":\"GBP\",\"amountText\":\"100\",\"cardtypeText\":\"Credit card\",\"commentsText\":\"Comments.\",\"declarationText\":true}":""}}
What I want:
personsNameText=name9?centreEmailAddressText=name9#name.com?centreTelephoneNumberText=123456789?centreNumberText=ab123?centreNameText=ab123?invoiceText=123456789?currencyText=GBP?amountText=100?cardtypeText=Credit card?commentsText=Comments.?declarationText=true
I'm not sure of the type of the obj2 object here but I'll assume it's somethings like
interface UserFormData {
['Form data']: { [name: string]: value };
}
You would need to transform this to FormData before posting it. Something along the lines:
const formEncodedObj2 = new FormData();
const obj2Keys = obj2['Form data'];
Object.keys(obj2Keys).forEach(key => formEncodedObj2.append(key, obj2Keys[key]));
And then send the formEncodedObj2 object.
So, this solution solved various problems for me:
Posting x-www-form-urlencoded data using Angular 8's forms and HttpClient
Correcting unwanted encoded characters
My specific problem was that a unique verification string contained ampersands that were being converted to HTML entities, i.e. & to &.
// userdata.service.ts
public postForm(obj) {
return this.httpClient
.post(this.baseUrl2, obj, {
headers: new HttpHeaders({
"Content-Type": "application/x-www-form-urlencoded",
"Accept": "text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8",
"Referer": "http://referer.com" // Replace with your own.
}),
responseType: "text"
})
.map(data => data)
.pipe(
retry(1),
catchError(this.handleError)
);
}
// app.component.ts
PostForm(userdata) {
// Stringify and convert HTML entity ampersands back to normal ampersands.
const corrected = JSON.stringify(userdata).replace(/(&)/gm, '&');
// Convert back to JSON object.
const corrected2 = JSON.parse(corrected);
// entries() iterates form key:value pairs, URLSearchParams() is for query strings
const URLparams = new URLSearchParams(Object.entries(corrected2));
// Convert to string to post.
const final = URLparams.toString();
// Post it
this.userdataService.postForm(final).subscribe(reponse2 => {
console.log(reponse2);
});
}
URLSearchParams() was the breakthrough and, as Vlad suggested, being absolutely sure of the type one is dealing with. I should have used Types to avoid confusion. I probably should use Angular Interceptors to deal with the character manipulation.
I am using RestAPI from Wikibase which in turn use Hyperswitch framework.
In the example code of its yaml file, there is a case to choose to return the response or not depend on the status code
x-request-handler:
- storage:
request:
method: get
headers:
cache-control: '{{cache-control}}'
# cache-control: "no-cache"
uri: /{domain}/sys/key_value/page_summary3/{request.params.title}
catch:
status: 404
return_if:
# Typical case: Return straight from storage.
status: '2xx'
return:
But I would like to return depend on a value found on the response body, say success: 1. How can I do that?
return_if:
'{{storage.body.success}}': 1
maybe? That syntax works in values, I don't know if it works in keys. You can always handle the request manually though: use operationId instead of x-request-handler, and then have a function like
function storage(hyper, req) {
return hyper.get(...).then((res) => {
if (res.body.success === 1) {
return res;
}
return {...};
});
}
and export it with the operation ID you chose.
I'm trying to use apollo RESTDataSource to wrap my rest api. I need to pass some headers to the api call.
I'm following the example from the docs: https://www.apollographql.com/docs/apollo-server/features/data-sources#intercepting-fetches
This is my code:
willSendRequest(request: RequestOptions) {
console.log(`request 1: ${JSON.stringify(request)}`);
request.headers.set('Authorization', this.context.authorization);
console.log(`request 2: ${JSON.stringify(request)}`);
}
I'm expecting the headers to contain 'Authorization'. But it's always empty.
The log from the above code:
request 1: {"method":"POST","path":"partnerinvoices","body":{"command": "input","params":{},"headers":{}}
request 2: {"method":"POST","path":"partnerinvoices","body":{"command":"input","params":{},"headers":{}}
I can override body and params in willSendRequest method without any problem.
There are few ways that you could implement this,
within your Datasources class that extends RESTDataSource set the headers before request is being made
willSendRequest(request) {
request.headers.set('Authorization', 'Bearer .....')
}
or as a third argument in the datasource method (post, get, put, ...)
this.post('endpoint', {}, { headers: { 'Authorization': 'Bearer ...' } })
It is super important, if you are using Typescript, that you match the original signature of the willSendRequest method:
protected willSendRequest?(request: RequestOptions): ValueOrPromise<void>;
(Link to the docs)
So, make sure the method looks like this:
protected willSendRequest?(request: RequestOptions): ValueOrPromise<void> {
request.headers.set("Authorization", this.context.authorization);
}
You need to use request.headers.get('Authorization') to get your desired data. Using JSON.stringify will not give you the headers values as it is not a object literal.
willSendRequest(request: RequestOptions) {
console.log(`request 1: ${request.headers.get('Authorization')}`);
request.headers.set('Authorization', this.context.authorization);
console.log(`request 2: ${request.headers.get('Authorization')}`);
}