I am using Fetch to make cross origin requests in javascript.
Cloudflare (proxying my traffic) will sometimes return a 429 (rate limiting).
When they return 429, they do not include the Access-Control-Allow-Origin header.
So now my fetch with mode: 'cors' fails, and throws a TypeError
How can I catch when this happens, vs. when it throws for other reasons like network errors?
My code is as follows:
try {
let response = await fetch(uri, config); // this throws
if (!response.ok) { // this line does not run
throw response.statusText;
}
let json = await response.json();
return json;
} catch (e) {
console.log(e.message); // "Failed to fetch"
}
Checking MDN docs, I'm not sure if it's possible to detect this 429 separate from other network errors?
A fetch() promise will reject with a TypeError when a network error is encountered or CORS is misconfigured on the server-side, although this usually means permission issues or similar
The short version is you can't, and this is by design.
The only place where this could be fixed is the server-side. Either Cloudflare needs to be changed/configured to send the appropriate headers or using a different service that does send the headers on error.
Without a server-side change, the error will be a generic CORS error.
The other alternative might to do build something like an 'iframe proxy', effectively letting you circumvent CORS entirely.
status code: 429 is returned when user sends too many requests in given time.
that means eventListners are making multiple requests(thats what I think).
So, to stop eventListners from making multiple requests use
event.stopImmediatePropagation();
If several listeners are attached to the same element for the same event type, they are called in the order in which they were added. If stopImmediatePropagation() is invoked during one such call, no remaining listeners will be called.
read more about this here MDN docs.
Related
We run our staging environment on a shared host. Occasionally we run into troubles with unknown errors. Cloudflare displays it as error 520 when the page loads.
It's not a resource problem, and we don't have server access. It really does not happen often, so I want to run a Cloudflare Worker that refreshes the page if there is a 520 error.
Does anyone know how to write this, please?
Something like below should do it. Note that this doesn't "refresh" the page. Instead, the error page never reaches the user's browser at all, because on an error, the whole request is retried and the retry response goes to the browser instead.
Of course, it would be better to figure out why the error is happening. Cloudflare's error 520 means that your origin server is returning invalid responses to Cloudflare. Here is a page discussing what to do about it.
That said, while the problem is being investigated, a Worker can offer a convenient way to "sweep the problem under the rug" so that your visitors can access your site without problems.
export default {
async fetch(request, env, ctx) {
if (request.body) {
// This request has a body, i.e. it's submitting some information to
// the server, not just requesting a web page. If we wanted to be able
// to retry such requests, we'd have to buffer the body so that we
// can send it twice. That is expensive, so instead we'll just hope
// that these requests (which are relatively uncommon) don't fail.
// So we just pass the request to the server and return the response
// nomally.
return fetch(request);
}
// Try the request the first time.
let response = await fetch(request);
if (response.status == 520) {
// The server returned status 520. Let's retry the request. But
// we'll only retry once, since we don't want to get stuck in an
// infinite retry loop.
// Let's discard the previous response body. This is not strictly
// required but it helps let the Workers Runtime know that it doesn't
// need to hold open the HTTP connection for the failed request.
await response.arrayBuffer();
// OK, now we retry the request, and replace the response with the
// new version.
response = await fetch(request);
}
return response;
}
}
I am using fetch to make requests to the backend of my application, which uses an OAuth authentication provider.
When the session expires, the fetch request is redirected to the authentication provider, causing it to fail because of a CORS error (which I cannot change).
In this case, fetch throws a TypeError: failed to fetch.
Unfortunately, I am also getting a TypeError: failed to fetch, when the network is down or the request times out for another reason.
How can I reliably discriminate between these failure modes?
I have not found any useful properties on the thrown TypeError, that would allow these failures to be distinguished.
It is possible to catch redirects using the redirect: "manual" option.
fetch(url, {redirect: "manual"})
While it is not possible to catch the redirection URL for security purposes, this is an acceptable resolution for me at this point.
If I do:
fetch(someUrl)
.then(logResponse)
.catch(logError);
and I get rate-limited (ie. the response has a 429 code), I don't go into the then (I can see thatlogResponse is never run).
Instead I go straight to the catch block, and it just gets passed an extremely simple error object with a name/type/message and nothing else (TypeError: Failed to fetch).
How can I access the response that came back (eg. so I can recognize, programmatically, the 429)?
As per https://developer.mozilla.org/en-US/docs/Web/API/Fetch_API/Using_Fetch#Checking_that_the_fetch_was_successful:
A fetch() promise will reject with a TypeError when a network error is encountered or CORS is misconfigured on the server-side, although this usually means permission issues or similar — a 404 does not constitute a network error, for example.
This means (and #sideshowbarker confirmed in comments) that it is impossible to access the response details on this class of errors. The best one can do is .catch, and then assume some sort of networking issue (like a CORS-denying rate-limiting API, but possibly something else) is to blame.
It seems odd, but evidently it's impossible to ever know that you got a 429 when you get a 429 ... unless the response has CORS headers on it.
I have a generated React site I am hosting in an S3 bucket. One of my components attempts to fetch something when loaded:
require('isomorphic-fetch')
...
componentDidMount() {
fetch(`${url}`)
.then(res => {
console.log(res);
this.setState({
users: res
})
})
.catch(e => {
// do nothing
})
}
The url I am fetching is an AWS API Gateway. I have enabled CORS there, via the dropdown, with no changes to the default configuration.
In my console, for both the remote site and locally during development, I see:
"Failed to load url: No 'Access-Control-Allow-Origin' header is present on the requested resource." etc
However, in the Chrome Network tab, I can see the request and the response, with status 200, etc. In the console, my console.log and this.setState are never called, however.
I understand that CORS is a common pain point, and that many questions have touched on CORS. My question: Why does the response show no error in the Network tab, while simultaneously erroring in the console?
The fetch(`${url}`) call returns a promise that resolves with a Response object, and that Response object provides methods that resolve with text, JSON data, or a Blob.
So to get the data you want, you need to do something like this:
componentDidMount() {
fetch(`${url}`)
.then(res => res.text())
.then(text => {
console.log(text);
this.setState({
users: text
})
.catch(e => {
// do nothing
})
}
Failed to load url: No 'Access-Control-Allow-Origin' header is present on the requested resource." etc
That means the browser isn’t allowing your frontend code to access the response from the server, because the response doesn’t include the Access-Control-Allow-Origin header.
So in order for the above code to work, you’ll need to fix the server configuration on that server so that it sends the necessary Access-Control-Allow-Origin response header.
However, in the Chrome Network tab, I can see the request and the response, with status 200, etc. In the console, my console.log and this.setState are never called, however.
That’s expected in the case where the server doesn’t send the Access-Control-Allow-Origin response header. In that case, the browser still gets the response — and that’s why you can see it in the devtools Network tab — but just because the browser gets the response doesn’t mean it will expose the response to your frontend JavaScript code.
The browser will only let your code access the response if it includes the Access-Control-Allow-Origin response header; if the response doesn’t include that header, then the browser blocks your code from accessing it.
My question: Why does the response show no error in the Network tab, while simultaneously erroring in the console?
For the reason outlined above. The browser itself runs into no error in getting the response. But your code hits an error because it’s trying to access an res object that’s not there; the browser hasn’t created that res object, because the browser isn’t exposing the response to your code.
You may be seeing the status 200 for the OPTIONS not the GET. There is a setting for CORS to handle legacy, so it won't confuse your client. I had to do that last time in a React app. Your error is that your CORS isn't configured properly (sorry, obviously). Chrome won't let your client tlak to the backend if it doesn't get the headers properly. Other browsers probably also, probably React also. It may be some kind of HTTP protocol if only one side has CORS enabled. Someone can correct me there. It's a similar security consideration as sending a request to HTTP from HTTPS. Chrome blocks it.
It looks to me like it's your backend. CORS isn't active or it would put that header on, and after that, you would see errors about origin mismatch in the frontend client.
In my experience, it's a 2-3 step combo, make sure OPTIONS don't send confusing signals to your client (look for settings to do with 200). This is a config setting in your backend. Then, make sure the backend is configured to use CORS. You very specifically need to enter the origin hostname and port that the backend is to expect traffic from.
I could probably give better input if I see what languages and/or frameworks you are using besides React.
This is what you would do in Express JS and node for your Backend:
const cors = require('cors')
// note http or https
app.use(cors({
origin: 'http://example.com:1337',
//origin: '*',
methods: 'GET,HEAD,PUT,PATCH,POST,DELETE',
optionsSuccessStatus: 200
// some legacy browsers (IE11, various SmartTVs) choke on 204
}))
My last React app was detonating without optionsSuccessStatus by throwing success when it was fail.
To give you a little bit of imagery to work with, CORS is simple but finicky. It's a simple matter of alignment. Once your backend is configured to a) use CORS and b) know who to accept traffic from, it's done. Once your frontend is configured to handle this traffic, it's done. It's like aligning a square peg in a round hole until you get the config settings aligned.
Try using Postman to send some GET requests to the Backend. You can observe the headers from there.
I'm writing e2e test for a single page application with nightwatch.js.
I have some API request like an authentication. So I want to use fakeServer of sinon.js for mocking response data. Here's my code.
import sinon from 'sinon';
const WAIT_TIME = 5000;
const host = 'http://localhost:3000/#/';
const uri = new RegExp(escape('/users/login'));
module.exports = {
'Login Test': function(browser) {
let server;
browser
.windowSize('basicTest', 1440, 710)
.url(host + 'account/login')
.waitForElementVisible('body', WAIT_TIME)
.setValue('input[type=email]', 'sample#sample.com')
.setValue('input[type=password]', 'password')
.execute(function() {
server = sinon.fakeServer.create();
server.respondWith('POST', uri, [
200, { 'Content-Type': 'application/json' }, JSON.stringify(someResponseData),
]);
})
.submitForm('form')
.execute(function() {
server.respond();
})
.waitForElementNotPresent('input.[type=submit]', WAIT_TIME) // the page should be redirected to another page
.execute(function() {
server.restore();
server = null;
})
.end();
},
};
I can't mock response, and got the error below (When the API serve is running, got no error, but the response won't be mocked one).
Error: Origin is not allowed by Access-Control-Allow-Origin
I want to know, first of all, is it correct way to use sinon.js's fakeServer? And is that possible on e2e(and nightwatch.js)?
Please give me a help.
To answer your question I first need to explain the nature of the error you receive.
That's a CORS (cross origin resource sharing) error.
Basically, some service behind your single page app knows not to allow requests which don't originate from your app. The service returning that error (I can't tell what it is from the information you've posted) detects a request coming from not your app, and rejects it.
You could disable the CORS security of the service (I highly recommend you don't do this) or you could attempt to change the origin header of the request. This is tricky, as most modern browsers specifically prevent this change in order to protect users. Since you are using Nightwatch, your environment will, in general, be that of a browser.
Based on this error you must have set up the server incorrectly because it seems as if your request is still hitting your actual API and not the mocked server.
Probably because you when submit the form, the browser will still submit the form to where it is supposed to go (and not your mock server) unless it is told otherwise. Looking at your code, you are setting up a mock server, but from this file alone it is not clear how the browser is supposed to know to send requests to that mocked server.
See nock for an alternative solution, I'm about to start using it :)