Current setup is VueJS + SSR, but any React JS solutions probably work too.
I have a file called axios.js in which I create a new axios instance and set the apropiate Authorization headers.
import axios from 'axios'
axios.interceptors.request.use((config) => {
config.foo = 123;
});
const instance = axios.create();
instance.verylongunexistingattribute = new Date().toUTCString()
instance.interceptors.request.use((config) => {
config.foo = 456;
});
axios.get('/foo'); // config.foo === 123 -> true
instance.get('/foo'); // config.foo === 456 -> true
export { instance, createInstance, initializeApiV2 }
Seems fine to me, then in my file where I do the AJAX requests I simply do:
import axios from '#/src/api/axios'
let fetchUserProperties = () => {
console.log("axios:instance", axios.verylongunexistingattribute)
console.log("axios:instance.request", new Date().toUTCString())
return new Promise((resolve, reject) => {
axios.get(`/profile/properties`)
.then((result) => {
resolve(result.data)
})
.catch((error) => {
reject(error)
})
});
}
Just to remember, this works perfect in the browser, but fails miserably on node.
Why does it fail: enter link description here
Basically, the instance created in axios.js is being cached by node, so that following request made by diferent users end up using the same instance. And if a requestInterceptor with the token has already been defined then it might show other users data :(
I can't get my head around it, as I can't just return a function which creates a new instance of axios everytime it is called.
How can I create an axios instance which will only live for 1 request?
I tried to explain it as best as I could, if you need more info just ask.
Related
I have a remix application to act like frontend.
I load data from my backend and for some data I need to load it only once and reuse it on different pages.
In previous frontend we used localstorage but here is server side which returns me ReferenceError: window is not defined
import {LoaderFunction} from "#remix-run/node";
import authenticator from "~/services/auth.server";
import Layout from "~/src/Layout";
import {fetchData} from "~/services/fetch.service";
export let loader: LoaderFunction = async ({request}) => {
const user = await authenticator.isAuthenticated(request, {failureRedirect: "/login",});
const configs = await fetchData('GET', request, 'api/configs/all')
.then((response) => {
return response;
})
.catch(async error => {
await authenticator.logout(request, {redirectTo: "/login"});
});
try {
localStorage.setItem('parameters', configs);
} catch (e) {
console.log(e);
}
return {
user: user,
request: request
};
};
export default function DashboardPage() {
const data = useLoaderData();
return (
<Layout user={data?.user} request={data.request}>
</Layout>
);
}
I need the config to be accessable at any time, it's not usefull if I need to load it all the time.
You can't use localStorage on the server-side, since it is first set on the client. You could use cookies since they are accessible on the server-side.
I'm starting to test my application using Jest and Supertest (for endpoints). Tests work smoothly but Jest detects 2 open handles after running tests which prevents Jest from exiting cleanly.
This open handles are generated by an external async function that is being called within my test file. I'm using an external function to request a JWT Token from Auth0 API; but that request to Auth0 also provides in it's response crucial information to pass the endpoint's middlewares (more info about this below). Two things to have in mind here:
So far, I can't avoid requesting a token from Auth0 because that response, as I said, also includes a user object with key information. Auth0 sets this object outside of the body response, at that same level, but not within it. That information is key to pass the endpoint's middleware.
I've isolated all the errors to be sure that the problem shows up only when I call the external async function that requests from Auth0 API's the token and user info; the issue is generated by using that function (called getToken) within the test file.
Test file code
import app from "../app";
import mongoose from "mongoose";
import supertest from "supertest";
import { getToken } from "../helpers";
import dotenv from "dotenv";
import * as config from "../config";
dotenv.config();
const api = supertest(app);
let authToken: any;
let db: any;
beforeAll(async() => {
try {
mongoose.connect(config.MONGODB_URI, {
useNewUrlParser: true,
useUnifiedTopology: true,
useCreateIndex: true,
});
db = mongoose.connection;
db.on("error", console.error.bind(console, "Console Error:"));
db.once("open", () =>
console.log(`App connected to "${db.name}" database`)
);
authToken = await getToken()
} catch (err) {
return err
}
});
describe("GET /interview/:idCandidate", () => {
test("With auth0 and read permissions", async () => {
await api
.get("/interview/1")
.set("Authorization", "Bearer " + authToken)
.expect(200)
});
});
afterAll(async () => {
try {
await db.close();
} catch (err) {
return err;
}
});
getToken external function that requests info to Auth0 API
The getToken function that is imported from external module is as follows:
import axios from 'axios'
var options = {
url: //url goes here,
form:
{
// form object goes here
},
json: true
};
const getToken = async () => {
try {
const tokenRequest = await axios.post(options.url, options.form)
return tokenRequest.data.access_token
} catch (err){
return err
}
}
export default getToken;
Issue
Once my tests are run, they run as expected until Jest's --detectOpenHandles configuration detects the two following open handles:
Jest has detected the following 2 open handles potentially keeping Jest from exiting:
● TLSWRAP
60 | case 0:
61 | _a.trys.push([0, 2, , 3]);
> 62 | return [4 /*yield*/, axios_1.default.post(options.url, options.form)
| ^
63 | ];
64 | case 1:
at RedirectableRequest.Object.<anonymous>.RedirectableRequest._performRequest (node_modules/follow-redirects/index.js:265:24)
at new RedirectableRequest (node_modules/follow-redirects/index.js:61:8)
at Object.request (node_modules/follow-redirects/index.js:456:14)
at dispatchHttpRequest (node_modules/axios/lib/adapters/http.js:202:25)
at httpAdapter (node_modules/axios/lib/adapters/http.js:46:10)
at dispatchRequest (node_modules/axios/lib/core/dispatchRequest.js:53:10)
at Axios.request (node_modules/axios/lib/core/Axios.js:108:15)
at Axios.<computed> [as post] (node_modules/axios/lib/core/Axios.js:140:17)
at Function.post (node_modules/axios/lib/helpers/bind.js:9:15)
at call (dist/helpers/getToken.js:62:54)
at step (dist/helpers/getToken.js:33:23)
at Object.next (dist/helpers/getToken.js:14:53)
at dist/helpers/getToken.js:8:71
at __awaiter (dist/helpers/getToken.js:4:12)
at Object.token (dist/helpers/getToken.js:56:34)
at call (dist/test/api.test.js:87:48)
at step (dist/test/api.test.js:52:23)
at Object.next (dist/test/api.test.js:33:53)
at dist/test/api.test.js:27:71
at __awaiter (dist/test/api.test.js:23:12)
at dist/test/api.test.js:72:32
● TLSWRAP
141 | switch (_a.label) {
142 | case 0: return [4 /*yield*/, api
> 143 | .get("/interview/1")
| ^
144 | .set("Authorization", "Bearer " + authToken)
145 | .expect(200)];
146 | case 1:
at Test.Object.<anonymous>.Test.serverAddress (node_modules/supertest/lib/test.js:61:33)
at new Test (node_modules/supertest/lib/test.js:38:12)
at Object.get (node_modules/supertest/index.js:27:14)
at call (dist/test/api.test.js:143:26)
at step (dist/test/api.test.js:52:23)
at Object.next (dist/test/api.test.js:33:53)
at dist/test/api.test.js:27:71
at __awaiter (dist/test/api.test.js:23:12)
at Object.<anonymous> (dist/test/api.test.js:139:70)
I'm certain that the error is coming from this getToken async function.
Why am I not mocking the function?
You might be wondering why am I not mocking that function and as I said before, when Auth0 responds with the token (which refreshes quite often by the way), it also responds with info regarding the user, and that info goes outside the response.body. As a matter of fact, it goes at the same hierarchical level as the body. So, if I you wanted to mock this function, I would have to set the Authorization header with the bearer token on one side (which is easy to do with Supertest), and the user info provided by Auth0 on the other side; but this last step is not possible (at least as far as I know; otherwise, how do you set a user info property at the same hierarchy level as the body and not within it?)
Things I've tried
I've tried adding a longer timeout to the test and to beforeAll(); I've tried adding the done callback instead of using async/await within beforeAll() and some other not very important things and none of them solves the open handle issue. As a matter of fact, I've checked if the request process to Auth0 API is closed after the response and effectively, that connection closes but I still get open handle error after running the tests.
Any idea would be highly appreciated!
I've been also struggling with a similar problem today and failed to find a definite solution, but found a workaround. The workaround (posted by alfreema) is to put the following line before you make a call to axios.post:
await process.nextTick(() => {});
This seems to allow Axios to complete its housekeeping and be ready to track new connections opened afterwards. This is just my speculation, I hope someone else can shed more light on it and provide a proper solution.
Thanks #RocketR, it's really work.
await process.nextTick(() => { });
const newData = await axios.post(`${url}`, pattern);
const token = await axios.post(`${url}/token`, tokenData,
{
headers: {
'Content-Type': 'application/x-www-form-urlencoded'
}
});
Every call I will call await process.nextTick(() => {});.
Example:
await process.nextTick(() => {});
await supertest(app)
.get("/api/getBooking")
.set({ Authorization: `Bearer ${TOKEN}` })
.then((response) => {
expect(response.statusCode).toBe(200);
})
I'm trying to make POST request on front page via 'jquery ajax' to my server, and then with that data from front make POST request to outer server on my server. Using that final response I got from outer request, I wanna render new data into my front using ajax success function.
It seemed to be working well on local server, but when I deploy this project with heroku or azure, this whole process take 1000~2000ms and doesn't seem to be working at all.
what's wrong with my code?
I'm trying to build some detecting system that would notify users if there's a vacancy on wanted course. so I let user to pick a class and at the same time I call a function to check if there's a vacancy on that class via POST request to school server.
//index.html
//in front page, when user pick a course to start observing, I send POST to my server
function scanEmpty(data,cn,elem){
$.ajax({
url: './getLeftSeat',
crossDomain: true,
type: 'POST',
data: data+`&cn=${cn}`,
success: function (data) {
alert('got!')
}
})
}
//app.js
// when I get POST from my server I call scanEmpty()
app.post('/getLeftSeat', async (req,res) => {
scanEmpty(qs.stringify(req.body), req.body["cn"], () => {res.json({success: true})})
})
// and that is like this
const scanEmpty = async(data, CN, cb) => {
if(await parseGetLeftSeat(await getData(data), CN)) cb()
else {
await scanEmpty(data,CN,cb)
}
}
// send POST to school server and get response using axios
async function getData(data) {
return await axios.post(school_server_url, data, {'Content-Type': 'application/x-www-form-urlencoded;charset=UTF-8'});
}
// it's just parsing and get data that I want
const parseGetLeftSeat = async (res, CN) => {
return new Promise((resolve, reject) => {
const $ = cheerio.load(res.data);
$("#premier1 > div > table > tbody > tr > td").each((i, e) => {
if (e.firstChild && e.firstChild.data == CN && e.next) {
const tmp = e.next.next.next.next.next.next.next.next.next.next.next.next.next.next.next.next.next.next.next.next.next.next.next.next.next.next.next.next.next.next.firstChild.data.split('/')
resolve(Number(tmp[1].trim()) - Number(tmp[0].trim()) < 1 ? false : true)
}
})
})
}
It works alright though takes 1000~2000 ms when actually on deploy server while it takes 100~200 ms on local server. I tested some codes and it looks like axios.post() is the one. but even if I change it to node-fetch, the result was the same. I really don't know what to do.
This morning I deployed a MERN stack login app in heroku successfully. But, when I tried to login
GET http://localhost:5000/user/login/email/password net::ERR_CONNECTION_REFUSED
in the console.
I understood that that the error is because I am making get request in axios using
axios.get("http://localhost:5000/user/login/" + this.state.email + "/" + this.state.password).then((res) => {
if (res.status === 200) {
this.setState({ status: res.status, name: res.data.name });
console.log(res.data);
}
else
throw new Error(res.status);
}).catch((err) => {
this.setState({ isInvalid: true });
})
But, the port is being dynamically allocated on the server side.
const port = process.env.PORT||5000;
app.listen(port, () => {
console.log("Server started on port:" + port);
});
Tried allocating only hardcoded value to the port. Still no luck
There are lots of mistakes in your code. You have deployed your app but your URL is still localhost which is not Heroku URL. First of all you need to setup env variables for your application like this.
You can put this in some constant file from where you get your end point. Don't write END POINTS directly in the ajax calls. Use constant and create a single file for from where you do all the ajax calls of the application.
You can set the env for both frontend and backend and this is how you should work. The development env should be separate from production one.
if (process.env.NODE_ENV === "development") {
API = "http://localhost:8000";
} else if (process.env.NODE_ENV === "production") {
API = "https://be-prepared-app-bk.herokuapp.com";
}
Don't use GET for the login and sending email and password in parameters. You should use POST and send all the data in body.
Here's how you single ajax file should look alike:
import { API_HOST } from "./constants";
import * as auth from "../services/Session";
const GlobalAPISvc = (endPoint, method, data) => {
const token = auth.getItem("token");
const uuid = auth.getItem("uuid");
return new Promise((resolve, reject) => {
fetch(`${API_HOST}${endPoint}`, {
method: method,
body: JSON.stringify(data),
headers: {
"Content-Type": "application/json",
"x-authentication": token,
uuid: uuid
}
})
.then(res => {
return res.json();
})
.then(json => {
resolve(json);
})
.catch(error => {
reject(error);
});
}).catch(error => {
return error;
});
};
export default GlobalAPISvc;
I have created an application in MERN which I made public on GitHub. Feel free to take help from that. Repository Link
Firstly, I would suggest you, not to use get request method for login.
Secondly, if you've deployed your backend code then use dynamic url provided by heroku for login request.
e.g. if your url is xyz.heroku.com then axios.get('xyz.heroku.com/user/login/'+email+'/'+password);
as now you don't need to hard-code the port or use localhost.
A user selects an image (in this case from Facebook, but I don't that is relevant to the problem). A request gets sent to the server to fetch the image and save it locally. The request resolves with 200. The resolved request is picked up on the client and the image is dynamically added to the page with JS by creating a new Image.
The problem: Even though the request does not resolve until the write process has finished, when the client tries to load the image it returns a 404.
I've tried adding an error handler on the client that reloads the image if it returns with a 404. This works after a few hundred milliseconds. However there is another problem. Often the image is only half loaded and the rest is black.
It is as if the image has not finished processing when the request resolves.
I've no idea what is causing the problem. I can't see how the request could be resolving before the operation is complete.
import fs from 'fs';
import request from 'request';
import gm from 'gm';
import createUserImageFolders from './imageHelpers/createUserImageFolders';
import getNextImageNumber from './imageHelpers/getNextImageNumber';
import saveImages from './imageHelpers/saveImages';
export default function uploadFacebookPhoto(req, params) {
return new Promise((resolve, reject) => {
createUserImageFolders(req.session.currentDesignerSessionId);
const userDir = 'static/images/user/' + req.session.currentDesignerSessionId;
const imageNumber = getNextImageNumber(userDir + '/uploads/full');
const filename = `${userDir}/uploads/full/${imageNumber}.jpg`;
const thumbWidth = 100;
// Stream the image from the remote server.
request.head(req.body.photoUrl, (err, response, body) => {
request(req.body.photoUrl).pipe(fs.createWriteStream(filename).on(
'close',
() => {
// Create thumbnail
gm(filename)
.noProfile()
.quality(85)
.resize(thumbWidth)
.write(userDir + `/uploads/thumb/${imageNumber}.jpg`, (error) => {
if (error) {
console.error('Graphics Magic (gm) error ', error);
reject({error: 500});
} else {
resolve(true);
}
});
}
));
});
});
}