Logging axios request and response headers - node.js

We are needing log the HTTP request that axios will send to the remote host, along with the response. This data will be stored in a database for a number of days before being automatically pruned.
The lifecycle we are looking at for the logging:
// creates the session that joins both the request and response
function createSession (url: string): string;
// log the request message as sent
function logRequest (sessionId: string, headers: Record<string, any>, body: Record<string, any> | string);
// log the request message as received
function logResponse (sessionId: string, headers: Record<string, any>, body: Record<string, any> | string);
// close the session, recording the final status and any error message
function closeSession(sessionId: string, status: number, error?: string);
We have looked at the request and response interceptors, but the issue we are having is that the request interceptor is before axios adds all the headers is going to send and the response interceptor doesn't seem to have access to the request, to be able to correlate the request and response.
Without needing to channel all this through a proxy to do the logging, how would you suggest doing this with axios?

Here's what I usually do:
In the request interceptor, I use some UUID library (or maybe the crypto core module of node) to generate a UUID, then attach it to the config object as a request ID, say config.reqId. Same config object should be accessible in response.config, or if an error occurs, in error.response.config and I can get the reqId from there. Then, if you have some script to parse the logs, you can correlate the request and response using this ID.
The disadvantage here is that, yes, the accurate req headers may not be logged.
Also, if you're just looking for the request object in response, then they should be accessible in response.request, going from what I checked in the axios docs. You can try that out.
const axios = require("axios");
const getNewUuid = require("./someUuidGeneratorUtilFunction.js");
const logger = require('./yourChoiceOfLogger.js');
const config = {
BASE_URL: "https://your.baseurl.here"
}
const myAxiosInstance = axios.create({
baseURL: config.BASE_URL,
timeout: 30000,
})
myAxiosInstance.interceptors.request.use((config) => {
const customUuid = getNewUuid();
config.reqId = customUuid;
const message = {
reqId: customUuid,
time: Date.now(),
config: config
}
logger.info(message)
return config;
})
myAxiosInstance.interceptors.response.use((response) => {
const customUuid = response.config && response.config.reqId ? response.config.reqId : "";
const message = {
reqId: customUuid,
time: Date.now(),
status: response.status,
data: response.data,
headers: response.headers,
logMessage: "RESPONSE RECEIVED"
}
logger.info(message)
return response;
},(error) => {
const customUuid = error.response && error.response.config && error.response.config.reqId ? error.response.config.reqId : "";
const errorResponse = error.response ? error.response : {
status: null,
data: null,
headers: null
}
const message = {
reqId: customUuid,
time: Date.now(),
status: errorResponse.status,
data: errorResponse.data,
headers: errorResponse.headers,
logMessage: error.message || "ERROR"
}
logger.error(message)
return Promise.reject(error)
})
module.exports = myAxiosInstance;

Related

How to make a multipart request from node JS to spring boot?

I have a node js backend that needs to send a file to a spring boot application.
The file is local uploads/testsheet.xlsx.
Here is the code to upload the file using the form-data npm module and Axios.
const formData = new FormData()
formData.append("file", fs.createReadStream("uploads/testsheet.xlsx"));
formData.append("status", status);
const path = `/endpoint`
const auth = {
username: username,
password: password
}
const url = `${baseUrl}${path}`
const response = await sendRequest(url, auth, "POST", formData, null, null, null)
//sendRequest Code -
try {
const response = await axios({
method,
url,
data,
params,
headers,
auth,
config,
})
return response
} catch (err) {
console.log(`Fetch Error: => ${method}:${url} => ${err.message || err.response}`)
return err.response ? { error: err.response.data.message } : { error: err.message }
}
The Spring boot side has the following code -
#PostMapping("/endpoint")
public StatusResponse update(#RequestParam("file") MultipartFile file, #RequestParam("status") String status){
boolean response = transactionService.update(file, status);
return statusResponse;
}
On the spring boot side, I keep getting this error -
org.springframework.web.multipart.MultipartException: Current request is not a multipart request
While sending from the postman, everything works correctly.!!!
How do I send the multipart file successfully?
You should use the .getHeaders() function that the formData object provides, in order to correctly set the multipart form boundary, e.g:
const res = await axios.post(url, formData, {
headers: formData.getHeaders()
});

Parsing Slack Interactive Message POST payload parameter to JSON in Azure Functions

I'm trying to create an Azure Function to take POST requests from Slack Message Interactions. I'm able to get a test request to come in following this guide using ngrok. However the payload is not coming in like a normal POST request body. Assuming this is because it's a "parameter" payload and not a body.
module.exports = async (context, req) => {
const { body } = req;
context.log(body);
context.res = {
body,
};
};
Output:
payload=%7B%22type%22%3A%22block_actions%22%2C%22user%22%3A%7B%22id%22%3A%22xxx%22%2C%22username%22%3A%22...
How do I parse this POST parameter payload into JSON in an Azure Function?
With help from this post I was able to figure this out for my use case.
Using qs package npm i qs
const { parse } = require('qs');
module.exports = async (context, req) => {
const payload = JSON.parse(parse(req.rawBody).payload);
context.log(payload);
context.res = {
payload,
};
};
Output:
{
type: 'block_actions',
user: {
id: 'xxx',
username: 'xxx',
name: 'xxx',
team_id: 'xxx'
},
api_app_id: 'xx',
...
}

Nodejs - Axios not using Cookie for post request

I'm struggling with AXIOS: it seems that my post request is not using my Cookie.
First of all, I'm creating an Axios Instance as following:
const api = axios.create({
baseURL: 'http://mylocalserver:myport/api/',
header: {
'Content-type' : 'application/json',
},
withCredentials: true,
responseType: 'json'
});
The API I'm trying to interact with is requiring a password, thus I'm defining a variable containing my password:
const password = 'mybeautifulpassword';
First, I need to post a request to create a session, and get the cookie:
const createSession = async() => {
const response = await api.post('session', { password: password});
return response.headers['set-cookie'];
}
Now, by using the returned cookie (stored in cookieAuth variable), I can interact with the API.
I know there is an endpoint allowing me to retrieve informations:
const readInfo = async(cookieAuth) => {
return await api.get('endpoint/a', {
headers: {
Cookie: cookieAuth,
}
})
}
This is working properly.
It's another story when I want to launch a post request.
const createInfo = async(cookieAuth, infoName) => {
try {
const data = JSON.stringify({
name: infoName
})
return await api.post('endpoint/a', {
headers: {
Cookie: cookieAuth,
},
data: data,
})
} catch (error) {
console.log(error);
}
};
When I launch the createInfo method, I got a 401 status (Unauthorized). It looks like Axios is not using my cookieAuth for the post request...
If I'm using Postman to make the same request, it works...
What am I doing wrong in this code? Thanks a lot for your help
I finally found my mistake.
As written in the Axios Doc ( https://axios-http.com/docs/instance )
The specified config will be merged with the instance config.
after creating the instance, I must follow the following structure to perform a post requests:
axios#post(url[, data[, config]])
My requests is working now :
await api.post('endpoint/a', {data: data}, {
headers: {
'Cookie': cookiesAuth
}
});

CORS error on posting file upload form from browser to firebase but no error from postman

I have built a Node app with admin SDK and deployed it to Firebase. it has an API endpoint for uploading file via POST method and Form data in post's body.
Problem is: everything works when i send the request from Postman, but posting from browser instantly fails with CORS policy. in firebase console log is see Function execution took 1206 ms, finished with status: 'crash' and no other detail.
In the backend i already have app.use(cors({ origin: true })) and other posts with json body work just fine from browser. which make me think this CORS error might not actually be CORS?
when I check the request header from the network part of inspect tools it has content-type: multipart/form-data
Am i not constructing my request right in the front end (REACT)?
import React, { useState } from "react";
import { useAuth } from "../contexts/AuthContext";
import Recorder from "../components/Main/Recorder";
const NewAudio = (props) => {
const { currentUser } = useAuth();
const isComment = props.location.state.status;
let threadId;
if (isComment) {
/** If the new audio is a comment, a threadId is associated to it */
threadId = props.location.state.threadId;
}
const [audioDetails, setAudioDetails] = useState({
url: null,
blob: null,
chunks: null,
duration: {
h: null,
m: null,
s: null,
},
});
function handleAudioStop(data) {
console.log(data);
setAudioDetails(data);
}
function handleAudioUpload() {
// POST endpoint
const targetUrl =
"<my URL>";
// Creation of the FormData object
let newAudioData = new FormData();
// Form data formatting and other details to post the form.
let audioTitle = document.getElementById("audioTitle").value;
let tags = document.getElementById("audioTags").value;
let audioTags = tags.split(", ");
let userid = currentUser.userId;
let password = currentUser.password;
let audioFileName =
audioTitle.replace(/\s+/g, "-") + "_" + userid + "_" + Date.now();
// Addition of data to the FormData object
newAudioData.append("audioTitle", audioTitle);
newAudioData.append("audioTags", JSON.stringify(audioTags));
newAudioData.append("userid", userid);
newAudioData.append("password", password);
newAudioData.append("file", audioDetails.blob, audioFileName + ".ogg");
// POST REQUEST
fetch(targetUrl, {
method: "POST",
body: newAudioData,
})
.then((response) => {
console.log(response);
})
.catch((err) => {
console.log(err);
});
}
function handleReset() {
const reset = {
url: null,
blob: null,
chunks: null,
duration: {
h: null,
m: null,
s: null,
},
};
setAudioDetails(reset);
}
return (
<div>
{isComment ? "Comment for thread " + threadId : "New thread"}
<Recorder
audioDetails={audioDetails}
setAudioDetails={setAudioDetails}
handleAudioStop={handleAudioStop}
handleAudioUpload={handleAudioUpload}
handleReset={handleReset}
/>
</div>
);
};
export default NewAudio;
in postman the request looks like this and works great:
Request headers:
Error in console:
As i suspected it was not really a CORS problem.
the problem was that i was trying to set a wrong key names:
// Addition of data to the FormData object
newAudioData.append("audioTitle", audioTitle);
newAudioData.append("audioTags", JSON.stringify(audioTags));
where as my application was expecting 'threadTitle' and 'threadTags'.
so changing them to their correct form solved the problem:
// Addition of data to the FormData object
newAudioData.append("threadTitle", audioTitle);
newAudioData.append("threadTags", JSON.stringify(audioTags));

Mocking node-fetch with jest creating a Response Object for mocking

I am trying to create Response Objects for mocking with jest, I can't seem to get the right syntax.
Initialization,
jest.mock('node-fetch')
const fetch = require('node-fetch')
const { Response, Headers } = jest.requireActual('node-fetch')
// Example adapted from https://fetch.spec.whatwg.org/#example-headers-class
const meta = {
'Content-Type': 'application/json',
'Accept': '*/*',
'Breaking-Bad': '<3'
}
// You can in fact use any iterable objects, like a Map or even another Headers
const headers = new Headers(meta)
const copyOfHeaders = new Headers(headers)
const ResponseInit = {
status: 200,
statusText: 'fail',
headers: headers
}
With a basic test
test('Basic Test', async () => {
const token = ''
const getDocList = new Response(JSON.stringify(downloadDocumentData), ResponseInit)
fetch.mockResolvedValueOnce(Promise.resolve(getDocList))
await module.doSomething('mock', token)
.then( async(res) => {
await expect(res.data).toEqual(Object)
})
}, 5000)
I'm getting an error which is
FetchError {
message:
'invalid json response body at reason: Unexpected token H in JSON at position 2',
type: 'invalid-json' }
How can I initial a response for valid json, I have tried a lot of different things.
Following the article at https://jestjs.io/docs/en/bypassing-module-mocks but I want to return and test json instead.
We should use jest.mock(moduleName, factory, options) to mock node-fetch module and fetch function.
In order to construct the response object of the fetch function, you need to use the Response class provided by the node-fetch module, so use jest.requireActual(moduleName) to get the original, unmocked node-fetch Module and Response class.
Of course, we can construct the response object arbitrarily, but the instance of the Response class is really close to the real response.
The same goes for headers object.
Here is a working demo:
index.js:
const fetch = require('node-fetch');
module.exports = {
async doSomething(url, token) {
return fetch(url).then(res => res.json());
}
};
index.spec.js:
jest.mock('node-fetch');
const fetch = require('node-fetch');
const { Response, Headers } = jest.requireActual('node-fetch');
const mod = require('./');
const meta = {
'Content-Type': 'application/json',
Accept: '*/*',
'Breaking-Bad': '<3'
};
const headers = new Headers(meta);
const copyOfHeaders = new Headers(headers);
const ResponseInit = {
status: 200,
statusText: 'fail',
headers: headers
};
test('Basic Test', async () => {
const token = '';
const downloadDocumentData = { data: {} };
const getDocList = new Response(JSON.stringify(downloadDocumentData), ResponseInit);
fetch.mockResolvedValueOnce(Promise.resolve(getDocList));
const res = await mod.doSomething('mock', token);
expect(res).toEqual({ data: {} });
expect(fetch).toBeCalledWith('mock');
});
Unit test result:
PASS src/stackoverflow/58648691/index.spec.js
✓ Basic Test (5ms)
Test Suites: 1 passed, 1 total
Tests: 1 passed, 1 total
Snapshots: 0 total
Time: 2.557s
Source code: https://github.com/mrdulin/jest-codelab/tree/master/src/stackoverflow/58648691

Resources