So I am testing a cloudflare web worker script and i can't seem to get my code to work with POST requests and such.
url_without_query_strings = request.url.split('?')[0] //remove all query strings
const response = await fetch(url_without_query_strings, {
method: request.method,
headers: request.headers
})
return response
Can anyone see what I am doing wrong ?
The problem is that you are only copying method and headers from the request, but it has more properties than that. POST requests, for example, have a body property which your code is not copying.
In order to perform a fetch that inherits everything from the original request except the URL, do:
const response = await fetch(url_without_query_strings, request)
That is, pass the request itself as the second parameter, rather than a dict. This works because a request object has property names matching exactly all of the options that fetch()'s second parameter expects.
Note that, awkwardly, if you want to modify any request property other than the URL, but keep other properties the same, then you must pass the request as the first parameter and specify the modifications in the second parameter:
const response = await fetch(request, {headers: myHeaders})
This means that if you want to modify the URL and some other property, you need to perform two steps -- first created a new Request object that changes the URL, then modify the headers:
let request2 = new Request(newUrl, request)
const response = await fetch(request2, {headers: myHeaders})
Or, of course, you could do the opposite order:
let request2 = new Request(request, {headers: myHeaders})
const response = await fetch(newUrl, request2)
Or, alternatively, for the specific case of headers, you can take advantage of the fact that once you've constructed your own Request object, you're allowed to directly modify its headers:
let request2 = new Request(newUrl, request)
request2.headers.set("X-Foo", "Bar")
const response = await fetch(request2)
Related
i have a frontend where I define a string in this case videoLink.
Besides that I have an async function that starts when a button is clicked.
//sveltekit
async function addToQueue(){
console.log(videoLink);
const res = await fetch('/tool/server', {
method: 'POST',
body: {
videoData: videoLink
}
})
const json = await res.json()
console.log(json);
videoLink = "";
}
This function sends an http-post request with fetch to my server.js file.
/** #type {import('#sveltejs/kit').RequestHandler} */
export async function POST(event) {
const data = await event.request.body;
const link = data.videoData;
console.log(link)
}
when i run the post-request (by clicking the Button in my frontend), my server logs undefined. As far as I know the server gets the POST request, because it logs only if I click the button.
But why does it return undefined? I have tried to parse the json, but it didn't work. Can anyone help me? What is wrong with my JSON?
Open the developer tools in your browser. Look at the Network tab. Examine the Request payload you are sending:
[object Object]
fetch doesn't support plain objects for the body option. It will convert them to strings with their toString() method.
You need to either provide:
an object which fetch does support. The common options are
URLSearchParams for application/x-www-form-urlencoded data
FormData for multipart/form-data
an encoded string (such as the output of JSON.stringify) and also set the content-type HTTP request header to match
Which you choose will depend on what encodings the server side code supports.
So I have provided
headers: {
'Content-Type': 'application/json'
}
to fetch the data.
On the server side I've done the following:
export async function POST({request}) {
const data = await request.json()
console.log(data)
return {}
}
Now the function returns {}, which isn't mandatory. The reason it works now is that POST gets {request} as an argument.
Notice the curly braces. Thanks a lot to #Quentin for his help.
I have a big issue with receiving data from multipart form request sent by my reactJS front-end to the PHP api handled with Klein.
I just tried to send with Javascript this fetch request
const data = new FormData();
data.append('sourceId', sourceId);
data.append('customerId', customerId);
const resp = await fetch(URL_CREATE_DOC, {
method: 'POST',
body: data,
headers: {
'Content-Type': 'multipart/form-data; boundary=stackoverflowrocks',
}
and then to receive in my PHP Api with this
var_dump($request->files()->all());
but there's no data ! In the header, I see that the data is well sent by the front and when I make a var_dump($request->server()), I see the CONTENT_LENGHT changing if I send files. I think I don't do something well but how can I get the data from the multipart request ?
According to the readme:
$request->
id($hash = true) // Get a unique ID for the request
paramsGet() // Return the GET parameter collection
paramsPost() // Return the POST parameter collection
paramsNamed() // Return the named parameter collection
cookies() // Return the cookies collection
server() // Return the server collection
headers() // Return the headers collection
files() // Return the files collection
body() // Get the request body
params() // Return all parameters
params($mask = null) // Return all parameters that match the mask array - extract() friendly
param($key, $default = null) // Get a request parameter (get, post, named)
files() is exclusively for uploaded files, which you don't seem to have; your values can be found in POST data, which means any one of paramsPost(), params() or param($name) would be correct.
Specifically I am interested in changing all responses with code 403 to code 404, and changing all responses with code 301 to 302. I do not want any other part of the response to change, except the status text (which I want to be empty). Below is my own attempt at this:
addEventListener("fetch", event => {
event.respondWith(fetchAndModify(event.request));
});
async function fetchAndModify(request) {
// Send the request on to the origin server.
const response = await fetch(request);
const body = await response.body
newStatus = response.status
if (response.status == 403) {
newStatus = 404
} else if (response.status == 301) {
newStatus = 302
}
// Return modified response.
return new Response(body, {
status: newStatus,
statusText: "",
headers: response.headers
});
}
I have confirmed that this code works. I would like to know if there is any possibility at all that this overwrites part of the response other than the status code or text, and if so, how can I avoid that? If this goes against certain best practices of Cloudflare workers or javascript, please describe which ones and why.
You've stumbled on a real problem with the Fetch API spec as it is written today.
As of now, status, statusText, and headers are the only standard properties of Response's init structure. However, there's no guarantee that they will remain the only properties forever, and no guarantee that an implementation doesn't provide additional non-standard or not-yet-standard properties.
In fact, Cloudflare Workers today implements a non-standard property: webSocket, which is used to implement WebSocket proxying. This property is present if the request passed to fetch() was a WebSocket initiation request and the origin server completed a WebSocket handshake. In this case, if you drop the webSocket field from the Response, WebSocket proxying will break -- which may or may not matter to you.
Unfortunately, the standard does not specify any good way to rewrite a single property of a Response without potentially dropping unanticipated properties. This differs from Request objects, which do offer a (somewhat awkward) way to do such rewrites: Request's constructor can take another Request object as the first parameter, in which case the second parameter specifies only the properties to modify. Alternately, to modify only the URL, you can pass the URL as the first parameter and a Request object as the second parameter. This works because a Request object happens to be the same "shape" as the constructor's initializer structure (it's unclear if the spec authors intended this or if it was a happy accident). Exmaples:
// change URL
request = new Request(newUrl, request);
// change method (or any other property)
request = new Request(request, {method: "GET"});
But for Response, you cannot pass an existing Response object as the first parameter to Response's constructor. There are straightforward ways to modify the body and headers:
// change response body
response = new Response(newBody, response);
// change response headers
// Making a copy of a Response object makes headers mutable.
response = new Response(response.body, response);
response.headers.set("Foo", "bar");
But if you want to modify status... well, there's a trick you can do, but it's not pretty:
// Create an initializer by copying the Response's enumerable fields
// into a new object.
let init = {...response};
// Modify it.
init.status = 404;
init.statusText = "Not Found";
// Work around a bug where `webSocket` is `null` but needs to be `undefined`.
// (Sorry, I only just noticed this when testing this answer! We'll fix this
// in the future.)
init.webSocket = init.webSocket || undefined;
// Create a new Response.
response = new Response(response.body, init);
But, ugh, that sure was ugly.
I have proposed improvements to the Fetch API to solve this, but I haven't yet had time to follow through on them. :(
I'm making a request to an API for some information. I need to send some information in order to get the required information back. The response is in XML format. When I make the request I get the following error
Uncaught (in promise) TypeError: Failed to execute 'fetch' on 'Window': Request with GET/HEAD method cannot have body.
If a GET request can't have a body how do I send the required information? Basically how do I get this to work?
Here is my code.
getResponse = () => {
const url = 'http://api.pixelz.com/REST.svc/Templates/';
// The data we are going to send in our request
let data = JSON.stringify({
"contactEmail": "myemail#gmail.com",
"contactAPIkey": "MY-API-KEY"
})
// The parameters we are gonna pass to the fetch function
let fetchData = {
method: 'GET',
body: data,
headers: new Headers()
}
fetch(url, fetchData)
.then(function(response) {
// Handle response you get from the server
response.text()
.then(data => console.log(data))
});
}
Indeed GET requests can't have a body, meaning you don't send data while you are getting. There can be two things going on here.
That specific endpoint is supposed to use another method like POST
The data you want to send actually needs to be passed as querystring parameter http://api.pixelz.com/REST.svc/Templates/?contactAPIkey=....&contactEmail=...
I'm writing a wrapper around fetch that I would like to add something to the URL before making the request e.g. identifying query parameters. I can't figure out how to make a copy of a given a Request object with a different URL than the original. My code looks like:
// My function which tries to modify the URL of the request
function addLangParameter(request) {
const newUrl = request.url + "?lang=" + lang;
return new Request(newUrl, /* not sure what to put here */);
}
// My fetch wrapper
function myFetch(input, init) {
// Normalize the input into a Request object
return Promise.resolve(new Request(input, init))
// Call my modifier function
.then(addLangParameter)
// Make the actual request
.then(request => fetch(request));
}
I tried putting the original request as the second arguent to the Request constructor, like so:
function addLangParameter(request) {
const newUrl = request.url + "?lang=" + lang;
return new Request(newUrl, request);
}
which seems to copy most of the attributes of the old request but doesn't seem to preserve the body of the old request. For example,
const request1 = new Request("/", { method: "POST", body: "test" });
const request2 = new Request("/new", request1);
request2.text().then(body => console.log(body));
I would expect to log "test", but instead it logs the empty string, because the body is not copied over.
Do I need to do something more explicit to copy all of the attributes correctly, or is there a nice shortcut that will do something reasonable for me?
I'm using the github/fetch polyfill, but have tested with both the polyfill and the native fetch implementation in the lastest Chrome.
It looks like your best bet is to read the body using the Body interface that Requests implement:
https://fetch.spec.whatwg.org/#body
This can only be done asynchronously since the underlying "consume body" operation always reads asynchronously and returns a promise. Something like this should work:
const request = new Request('/old', { method: 'GET' });
const bodyP = request.headers.get('Content-Type') ? request.blob() : Promise.resolve(undefined);
const newRequestP =
bodyP.then((body) =>
new Request('/new', {
method: request.method,
headers: request.headers,
body: body,
referrer: request.referrer,
referrerPolicy: request.referrerPolicy,
mode: request.mode,
credentials: request.credentials,
cache: request.cache,
redirect: request.redirect,
integrity: request.integrity,
})
);
After doing that, newRequestP will be a promise that resolves to the request you want. Luckily, fetch is asynchronous anyway so your wrapper shouldn't be significantly hampered by this.
(Note: Reading the body using .blob() off of a request that does not have a body seems to return a zero-length Blob object, but it's incorrect to specify any body, even a zero-length one, on a GET or HEAD request. I believe that checking if the original request had Content-Type set is an accurate proxy for whether it has a body, which is what we really need to determine.)