I have a react native app that uses MongoDB as the database with express and node js I also use Axios to communicate with the client to the server
Now the app constantly sends and receives data from the database rapidly, e.g a user makes as much as 3 to 4 requests to and from the backend per second when the app is in use,
Everything works fine but there are a lot of 429 errors, how to handle this error or prevent it from occurring without compromising the users experiences a lot?
this below is the axios instanace
const instance = axios.create({ baseURL: 'http://9rv324283.ngrok.io' })
this below is fetching the data from the database
<NavigationEvents
onWillFocus={() => {
try {
const response = await instance.get('fetchNewDishes');
this.setState({data: response.data})
} catch(err) {
console.log(err)
}
}}>
this below is send data to the database
<TouchableOpacity onPress={() => instance.patch(`/postNewDish/${this.state.dish}`)}>
<Text style={{ fontSize: 16, color: '#555', padding: 15 }}>Post Dish</Text>
</TouchableOpacity>
I would suggest you to use axios interceptors to actually trace the error handling in axios , see below example :
import ax from 'axios';
import {config} from '../global/constant';
const baseUrl = config.apiUrl;
let axios = ax.create({
baseURL: baseUrl,
withCredentials: true,
headers: {
'Content-Type': 'application/json;charset=UTF-8',
'Access-Control-Allow-Origin': '*',
},
});
axios.interceptors.request.use(req => handleRequest(req));
axios.interceptors.response.use(
res => handleResponse(res),
rej => handleError(rej),// here if its an error , then call handleError and do what you want to do with error.
);
// sending the error as promise.reject
const handleError = error => {
let errorResponse = {...error};
console.log({...error}, 'error');
return Promise.reject({
data: errorResponse.response.data,
code: errorResponse.response.status,
});
};
Hope it helps. feel free for doubts
Are you in control of the backend? It is possible there is a middleware that limits requests such as express-rate-limit
Make sure to either disable these middlewares, or allow many more requests per minute in the middleware configs.
I had a play around with this using https://httpstat.us/429/cors, which always returns error 429 with retry-after set to 5 (seconds), and came up with this using axios-retry:
import axios from "axios";
import axiosRetry from "axios-retry";
let instance = axios.create({ baseURL: "https://httpstat.us" });
axiosRetry(instance, {
retryCondition: (e) => {
return (
axiosRetry.isNetworkOrIdempotentRequestError(e) ||
e.response.status === 429
);
},
retryDelay: (retryCount, error) => {
if (error.response) {
const retry_after = error.response.headers["retry-after"];
if (retry_after) {
return retry_after;
}
}
// Can also just return 0 here for no delay if one isn't specified
return axiosRetry.exponentialDelay(retryCount);
}
});
// Test for error 429
instance({
url: "/429/cors",
method: "get"
})
.then((res) => {
console.log("429 res: ", res);
})
.catch((e) => {
console.log("429 e: ", e);
});
// Test to show that code isn't triggered by working API call
instance({
url: "/200/cors",
method: "get"
})
.then((res) => {
console.log("200 res: ", res);
})
.catch((e) => {
console.log("200 e: ", e);
});
I'm working on adding this to axios-retry properly for https://github.com/softonic/axios-retry/issues/72
Related
Im in the process of moving an app from Vue 2 -> 3
I decided to take a moment to really upgrade and refactor all my repo and that led to using Vue3 recs on new tech, one being vite
My problem is I don't totally understand how the backend API process works so im struggling to move my api route from vue-cli to vite.
I would like to keep using the logic that call functions from api/users to remain in place but im open to a better option
Ultimately I get 404 - Not Found as my response which means its cant find the route
Heres my api/user.js
import request from '../utils/request'
export function login(data) {
return request({
url: '/user/login',
method: 'post',
data
})
}
export function getInfo(token) {
return request({
url: '/user/info',
method: 'get',
params: { token }
})
}
export function logout() {
return request({
url: '/user/logout',
method: 'post'
})
}
utils/Request.js
import axios from 'axios'
import { ElMessageBox, ElMessage } from 'element-plus'
import { userStore } from '../stores/user'
import { getToken } from '../utils/auth'
// create an axios instance
const service = axios.create({
baseURL: import.meta.env.VUE_APP_BASE_API, // url = base url + request url
// withCredentials: true, // send cookies when cross-domain requests
timeout: 5000 // request timeout
})
// request interceptor
service.interceptors.request.use(
config => {
// do something before request is sent
const useStore = userStore;
console.log("Req", "Req Init");
if (useStore.token) {
// let each request carry token
// ['X-Token'] is a custom headers key
// please modify it according to the actual situation
config.headers['X-Token'] = getToken()
}
return config
},
error => {
// do something with request error
console.log(error) // for debug
console.log("failed") // for debug
return Promise.reject(error)
}
)
// response interceptor
service.interceptors.response.use(
/**
* If you want to get http information such as headers or status
* Please return response => response
*/
/**
* Determine the request status by custom code
* Here is just an example
* You can also judge the status by HTTP Status Code
*/
response => {
const res = response.data
console.log("Res", "Res Init");
// if the custom code is not 20000, it is judged as an error.
if (res.code !== 20000) {
ElMessage({
message: res.message || 'Error',
type: 'error',
duration: 5 * 1000
})
// 50008: Illegal token; 50012: Other clients logged in; 50014: Token expired;
if (res.code === 50008 || res.code === 50012 || res.code === 50014) {
// to re-login
ElMessageBox.confirm('You have been logged out, you can cancel to stay on this page, or log in again', 'Confirm logout', {
confirmButtonText: 'Re-Login',
cancelButtonText: 'Cancel',
type: 'warning'
}).then(() => {
store.dispatch('user/resetToken').then(() => {
location.reload()
})
})
}
return Promise.reject(new Error(res.message || 'Error'))
} else {
return res
}
},
error => {
console.log('err' + error) // for debug
ElMessage({
message: error.message,
type: 'error',
duration: 5 * 1000
})
return Promise.reject(error)
}
)
export default service
And a peek at my store that actually calls the endpoint
import { login, logout, getInfo } from '../api/user'
actions: { // user login
login({ commit }, userInfo) {
const { username, password } = userInfo
// **Call is made here to 'login'**
return new Promise((resolve, reject) => {
login({ username: username.trim(), password: password }).then(response => {
const { data } = response
commit('SET_TOKEN', data.token)
setToken(data.token)
resolve()
}).catch(error => {
reject(error)
})
})
},
Lastly where the call originates from: this.store.login in my login.vue component
this.store.login('user/login', this.loginForm).then(() => {
this.$router.push({ path: this.redirect || '/' })
this.loading = false
}).catch(() => {
this.loading = false
})
This is my first StackOverflow post, so be kind if I need to include something else and thanks to any ideas or tips. Thank you
I've tried to search on different ports but the route still comes back as undefined.
I tried to change the vite config to include the server option but it still doesn't seem to help.
Not sure If I need to modify the config or not but I did have some settings related to the server mock on my old webpack config
await axios
.post(
executeBatch,
{
headers: {
"Content-Type": "application/json",
"x-access-token": localStorage.getItem("token"),
},
}
)
.then(
(response) =>
this.$store.commit(
"insertOutputFile",
response.data.outputFile._id
),
);
alert("You can download the result");
so sometimes i get empty response with status code 200,
was thinking about retrying the request if that happens,
am wondering what is the correct way to approach this issue.
I think axios interceptors will work for you.
axios.interceptors.response.use((response) => {
return response
},
async function(error) {
const originalRequest = error.config;
if () { // condition
originalRequest._retry = true;
}
}
)
You can create setupAxios file in your base redux directory and export it from index.js in your base redux directory.
export {default as setupAxios} from "./setupAxios";
And define setupAxios from your root index.js file
import * as _redux from "./redux";
_redux.setupAxios(axios, store);
Btw, I am using react.js, it could be a little different in vue.js.
I am making a Discord bot, and I want it to be able to use the YouTube API to fetch new uploads from a specific channel.
I have searched elsewhere, but they all say how to upload videos, not how to track uploads.
Is this possible, and how can I do it?
Edit: Tried PubSubHubbub but it was very confusing and I couldn't get it to work
Here an example built on top of Node.js (v12) and Fastify and published with ngrok:
I wrote some comments explaining what it is happening:
const fastify = require('fastify')({ logger: true })
const xmlParser = require('fast-xml-parser')
const { URLSearchParams } = require('url')
const fetch = require('node-fetch')
// add an xml parser
fastify.addContentTypeParser('application/atom+xml', { parseAs: 'string' }, function (req, xmlString, done) {
try {
const body = xmlParser.parse(xmlString, {
attributeNamePrefix: '',
ignoreAttributes: false
})
done(null, body)
} catch (error) {
done(error)
}
})
// this endpoint needs for authentication
fastify.get('/', (request, reply) => {
reply.send(request.query['hub.challenge'])
})
// this endpoint will get the updates
fastify.post('/', (request, reply) => {
console.log(JSON.stringify(request.body, null, 2))
reply.code(204)
reply.send('ok')
})
fastify.listen(8080)
.then(() => {
// after the server has started, subscribe to the hub
// Parameter list: https://pubsubhubbub.github.io/PubSubHubbub/pubsubhubbub-core-0.4.html#rfc.section.5.1
const params = new URLSearchParams()
params.append('hub.callback', 'https://1f3dd0c63e78.ngrok.io') // you must have a public endpoint. get it with "ngrok http 8080"
params.append('hub.mode', 'subscribe')
params.append('hub.topic', 'https://www.youtube.com/xml/feeds/videos.xml?channel_id=UCfWbGF64qBSVM2Wq9fwrfrg')
params.append('hub.lease_seconds', '')
params.append('hub.secret', '')
params.append('hub.verify', 'sync')
params.append('hub.verify_token', '')
return fetch('https://pubsubhubbub.appspot.com/subscribe', {
headers: { 'content-type': 'application/x-www-form-urlencoded' },
body: params,
method: 'POST'
})
})
.then((res) => {
console.log(`The status must be 204. Received ${res.status}`)
// shows the error if something went wrong
if (res.status !== 204) {
return res.text().then(txt => console.log(txt))
}
})
I used my channel id to do some testing, consider that the notification is not in real-time, the POSTs are triggered after several minutes usually.
I'm trying to make a POST request using axios to my firebase cloud-function on form submit in react app. But I get '500' error everytime I make a request with an html-page response This app works best with javascriot enabled.
Latest Update:
It looks like there is no issue with cloud function
code. Rather more of a react-component issue. I used Postman to send
the POST request with header prop Content-Type set to application/json
and sending body in raw format {"email": "example_email"} and got
expected response from the cloud function. But when sent the request from
react component above, I get an html file response saying the app
works best with javascript enabled
I've tried setting Content-Type to both Application/json and multipart/form-data as I suspected it to be an issue but still got no luck.
Following is my code for cloud function and react submit form:
Cloud Function
const functions = require('firebase-functions');
const cors = require('cors')({ origin: true })
const runThisFunc1 = require(./libs/runThisFunc1);
const runThisFunc2 = require(./libs/runThisFunc2);
exports.wizardFunc = functions.https.onRequest((request, response) => {
cors(request, response, () => {
let email = request.body.email;
try {
return runThisFunc1(email)
.then(data => {
console.log("Word Done by 1!");
return runThisFunc2(data);
})
.then(res => {
console.log("Word Done by 2!");
return response.status(200).send("Success");
})
.catch(err => {
console.error("Error: ", err.code);
return response.status(500).end();
});
}catch(err) {
return response.status(400).end();
}
});
});
React-Form-Component Snippet
import axios from 'axios'
...
handleSubmit = e => {
e.preventDefault()
const { email } = this.state
axios({
method: 'post',
url: `${process.env.REACT_APP_CLOUD_FUNCTION_ENDPOINT}`,
data: { email: email },
config: {
headers: {
'Content-Type': 'multipart/form-data'
}
}
})
.then(res => {
//do something with reponse here
})
.catch(error => {
console.error(error)
})
}
...
Is there something wrong I am doing in the code or the request config is wrong?
I'm trying to send a CORS POST request to my API and it throws a TypeError every time I use the 'Authorization' header. The request doesn't even get sent, so the server is not involved. But this only happens in my tests. When I try it in Chrome it works just fine.
Here is the function that I'm testing:
export const postNewEmployee = formData => {
return fetch('http://localhost:3003', {
method: 'POST',
headers: {
'Authorization': 'Bearer test123',
'Content-Type': 'application/json'
},
body: JSON.stringify(formData)
})
.then(response => response)
.catch(error => {
throw error;
});
};
And its test:
import * as API from './api';
describe('postNewEmployee', () => {
it('posts the form data asynchronously', () => {
let formData = {
employee: {
name: 'Test Person',
email: 'test#person.nu',
address: 'an adress 123'
}
};
return API.postNewEmployee(formData)
.then(json => {
expect(json.status).toEqual(201);
}).catch(error => {
console.log(error);
});
});
});
The application is a react/redux app created with create-react-app, so I'm using Jest and JSDOM to test this. The thing is, if I comment out the Authorization header from the fetch()-call, it works fine. But if I add that header I get this:
TypeError: Cannot convert undefined or null to object
at Object.getRequestHeader (/Users/johanh/Kod/react-app/node_modules/react-scripts/node_modules/jsdom/lib/jsdom/living/xhr-utils.js:20:23)
at setDispatchProgressEvents (/Users/johanh/Kod/react-app/node_modules/react-scripts/node_modules/jsdom/lib/jsdom/living/xmlhttprequest.js:909:38)
at XMLHttpRequest.send (/Users/johanh/Kod/react-app/node_modules/react-scripts/node_modules/jsdom/lib/jsdom/living/xmlhttprequest.js:700:11)
at /Users/johanh/Kod/react-app/node_modules/react-scripts/node_modules/whatwg-fetch/fetch.js:429:11
at Object.<anonymous>.self.fetch (/Users/johanh/Kod/react-app/node_modules/react-scripts/node_modules/whatwg-fetch/fetch.js:373:12)
at Object.<anonymous>.exports.postNewEmployee.formData [as postNewEmployee] (/Users/johanh/Kod/react-app/src/api/api.js:20:10)
at Object.it (/Users/johanh/Kod/react-app/src/api/api.test.js:75:16)
at Object.<anonymous> (/Users/johanh/Kod/react-app/node_modules/react-scripts/node_modules/jest-jasmine2/build/jasmine-async.js:42:32)
at attemptAsync (/Users/johanh/Kod/react-app/node_modules/react-scripts/node_modules/jest-jasmine2/vendor/jasmine-2.4.1.js:1919:24)
at QueueRunner.run (/Users/johanh/Kod/react-app/node_modules/react-scripts/node_modules/jest-jasmine2/vendor/jasmine-2.4.1.js:1874:9)
And as I said, this only happens in the test. In the browser it works fine.
I feel like I'm missing something obvious here, but I just can't see it. I've looked in the fetch spec and the jsdom documentation, but to no avail. Any ideas?
Normally you should not make real requests in a unit test. The best way to handle this is to use a mock instead of the real fetch implementation.
I assume you are using the JS implementation of fetch. So you can set fetch to what ever you want in your test.
import * as API from './api';
describe('postNewEmployee', () => {
it('posts the form data asynchronously', () => {
// set fetch to a mock that always returns a solved promise
const fetch = jest.fn((url, options) => return Promise.resolve({status: 201}))
global.fetch = fetch;
let formData = {
employee: {
name: 'Test Person',
email: 'test#person.nu',
address: 'an adress 123'
}
};
//test that fetch was called with the correct parameters
expect(fetch.mock.calls[0][0]).toBe('http://localhost:3003')
expect(fetch.mock.calls[0][1]).toEqual(formData)
return API.postNewEmployee(formData)
.then(json => {
expect(json.status).toEqual(201);
}).catch(error => {
console.log(error);
});
});
});