400/500 error handler in Express - sending JSON vs HTML - html

We have this error handler in Express:
app.use(function (err, req, res, next) {
res.status(err.status || 500);
const stck = String(err.stack || err).split('\n').filter(function (s) {
return !String(s).match(/\/node_modules\// && String(s).match(/\//));
});
const joined = stck.join('\n');
console.error(joined);
const isProd = process.env.NODE_ENV === 'production';
const message = res.locals.message = (err.message || err);
const shortStackTrace = res.locals.shortStackTrace = isProd ? '' : joined;
const fullStackTrace = res.locals.fullStackTrace = isProd ? '': (err.stack || err);
if (req.headers['Content-Type'] === 'application/json') {
res.json({
message: message,
shortStackTrace: shortStackTrace,
fullStackTrace: fullStackTrace
});
}
else {
//locals for template have already been set
res.render('error');
}
});
My question is - we want to send back either JSON or HTML depending on the type of request. I assume looking at Content-Type header is the best way to do this. Is there any other way I should be checking?
Isn't the Content-Type header sometimes called 'content-type' (lowercase)?

I prefer using the following (this is for a 404 error but that's not important):
if (req.accepts('html')) {
// Respond with html page.
fs.readFile('404.html', 'utf-8', function(err, page) {
res.writeHead(404, { 'Content-Type': 'text/html' });
res.write(page);
res.end();
});
} else {
if (req.accepts('json')) {
// Respond with json.
res.status(404).send({ error: 'Not found' });
} else {
// Default to plain-text. send()
res.status(404).type('txt').send('Not found');
}
}
Basically you can use the req object's accepts method to find out what you should send as a response.

Related

Vue JS, Axios retry request. Prevent JSON quotes

I have an axios interceptor for cases, where I need the user to be authorized, but he isn't. For example, because the token is expired.
Now, after a token refresh, the original request should be retried.
However, currently the original requests, seems to be changed, so that the Server gives me a JSON.parse error.
SyntaxError: Unexpected token " in JSON at position 0
at JSON.parse (<anonymous>)
at createStrictSyntaxError (/var/app/current/node_modules/body-parser/lib/types/json.js:158:10)
at parse (/var/app/current/node_modules/body-parser/lib/types/json.js:83:15)
at /var/app/current/node_modules/body-parser/lib/read.js:121:18
at invokeCallback (/var/app/current/node_modules/raw-body/index.js:224:16)
at done (/var/app/current/node_modules/raw-body/index.js:213:7)
at IncomingMessage.onEnd (/var/app/current/node_modules/raw-body/index.js:273:7)
at IncomingMessage.emit (events.js:314:20)
at IncomingMessage.EventEmitter.emit (domain.js:483:12)
at endReadableNT (_stream_readable.js:1241:12)
This is because, instead of the original request, that is JSON, it seems to process it again, puts it in quotes etc., so it becomes a string and the bodyparser, throws the error above.
So the request content, becomes:
"{\"traderaccount\":\"{\\\"traderaccountID\\\":\\\"undefined\\\",\\\"traderID\\\":\\\"2\\\",\\\"name\\\":\\\"Conscientious\\\",\\\"order\\\":99,\\\"myFxLink\\\":\\\"\\\",\\\"myFxWidget\\\":\\\"\\\",\\\"copyFxLink\\\":\\\"83809\\\",\\\"tokenLink\\\":\\\"\\\",\\\"tradertext\\\":{\\\"tradertextID\\\":\\\"\\\",\\\"traderaccountID\\\":\\\"\\\",\\\"language\\\":\\\"\\\",\\\"commission\\\":\\\"\\\",\\\"affiliateSystem\\\":\\\"\\\",\\\"leverage\\\":\\\"\\\",\\\"mode\\\":\\\"\\\",\\\"description\\\":\\\"\\\"},\\\"accountType\\\":\\\"\\\",\\\"accountTypeID\\\":1,\\\"minInvest\\\":2000,\\\"currency\\\":\\\"\\\",\\\"currencySymbol\\\":\\\"\\\",\\\"currencyID\\\":1,\\\"affiliateSystem\\\":1}\"}"
instead of
{"traderaccount":"{\"traderaccountID\":\"undefined\",\"traderID\":\"2\",\"name\":\"Conscientious\",\"order\":99,\"myFxLink\":\"\",\"myFxWidget\":\"\",\"copyFxLink\":\"83809\",\"tokenLink\":\"\",\"tradertext\":{\"tradertextID\":\"\",\"traderaccountID\":\"\",\"language\":\"\",\"commission\":\"\",\"affiliateSystem\":\"\",\"leverage\":\"\",\"mode\":\"\",\"description\":\"\"},\"accountType\":\"\",\"accountTypeID\":1,\"minInvest\":2000,\"currency\":\"\",\"currencySymbol\":\"\",\"currencyID\":1,\"affiliateSystem\":1}"}
from the original axios request content.
Both are the unformated request contents, that I can see in the developer network console.
The content type, is application/json in both cases.
Below is the Interceptor code:
Axios.interceptors.response.use(
(response) => {
return response;
},
(err) => {
const error = err.response;
if (
error !== undefined &&
error.status === 401 &&
error.config &&
!error.config.__isRetryRequest
) {
if (this.$store.state.refreshToken === "") {
return Promise.reject(error);
}
return this.getAuthToken().then(() => {
const request = error.config;
request.headers.Authorization =
Axios.defaults.headers.common[globals.AXIOSAuthorization];
request.__isRetryRequest = true;
return Axios.request(request);
});
}
return Promise.reject(error);
}
);
private getAuthToken() {
if (!this.currentRequest) {
this.currentRequest = this.$store.dispatch("refreshToken");
this.currentRequest.then(
this.resetAuthTokenRequest,
this.resetAuthTokenRequest
);
}
return this.currentRequest;
}
private resetAuthTokenRequest() {
this.currentRequest = null;
}
// store refreshToken
async refreshToken({ commit }) {
const userID = this.state.userID;
const refreshToken = Vue.prototype.$cookies.get("refreshToken");
this.commit("refreshLicense");
commit("authRequest");
try {
const resp = await axios.post(serverURL + "/refreshToken", {
userID,
refreshToken,
});
if (resp.status === 200) {
return;
} else if (resp.status === 201) {
const token = resp.data.newToken;
const newRefreshToken = resp.data.newRefreshToken;
Vue.$cookies.set(
"token",
token,
"14d",
undefined,
undefined,
process.env.NODE_ENV === "production",
"Strict"
);
Vue.$cookies.set(
"refreshToken",
newRefreshToken,
"30d",
undefined,
undefined,
process.env.NODE_ENV === "production",
"Strict"
);
axios.defaults.headers.common[globals.AXIOSAuthorization] = token;
commit("authSuccessRefresh", { newRefreshToken });
} else {
this.dispatch("logout");
router.push({
name: "login",
});
}
} catch (e) {
commit("authError");
this.dispatch("logout");
}
So, can you help me to prevent Axios on the retried request to change the request content. So it doesn't put it into quotes and quote the already exisitng quotes?
Thanks to the comment I found a solution.
Try to parse the content before resending it:
axios.interceptors.response.use(
(response) => response,
(error) => {
const status = error.response ? error.response.status : null;
if (status === 401 && error.config && !error.config.__isRetryRequest) {
return refreshToken(useStore()).then(() => {
const request = error.config;
request.headers.Authorization =
axios.defaults.headers.common["Authorization"];
request.__isRetryRequest = true;
try {
const o = JSON.parse(request.data);
if (o && typeof o === "object") {
request.data = o;
}
} catch (e) {
return axios.request(request);
}
return axios.request(request);
});
}
return Promise.reject(error);
});

Node.js, Mysql TypeError: Cannot read property 'apikey' of undefined

I am working on a basic auth middleware for a API it uses Node.js Mysql but if someone puts a incorrect key in auth header and sends the request the entire API crashes heres my code the issue is with the callback but I don't know how to fix that.
var express = require('express');
var app = express();
app.get('/', (request, response) => {
response.sendStatus(200);
});
let listener = app.listen(3000, () => {
console.log('Your app is currently listening on port: ' + listener.address().port);
});
var mysql = require('mysql');
var connection = mysql.createConnection({
host : '127.0.0.1',
user : 'root',
database : 'systemdata'
});
connection.connect();
function systemAuth(apikey, callback)
{
connection.query('SELECT apikey FROM systemdata.systemkeys WHERE apikey = ?', [apikey], function(err, result)
{
if (err)
callback(err,null);
else
callback(null,result[0].apikey);
});
}
var auth = function (req, res, next) {
systemAuth(req.headers.apikey, function(err,data){
if (err) {
console.log("ERROR : ",err);
} else {
console.log("result from db is : ",data);
}
if(data == req.headers.apikey) {
next()
}else{
res.status(401).send({"error": "Missing or Invalid API-Key", "apikey": req.headers.apikey, "valid": "false"})
}
})
}
app.use(auth)
You will also have to check whether your result actually contains any rows.
A query not returning any rows is not an error, so err won't be set, if result is an empty array. And accessing an element by an index which does not exist leads to undefined, thus the error you are seeing.
function systemAuth(apikey, callback)
{
connection.query('SELECT apikey FROM systemdata.systemkeys WHERE apikey = ?', [apikey], function(err, result)
{
if (err) // some error with the query
callback(err,null);
else if (!result || result.length == 0) // no matching rows found
callback(new Error("invalid apikey"), null);
else // a matching row is found
callback(null,result[0].apikey);
});
}

How to send HTML as a response in REST?

I have created an API in Nodejs. I have tried creating a call which returns HTML to display a site in the browser.
My Call looks like this:
router.get('/displayHTML', checkAccessToken, (req, res, next) => {
if (req.query.data === undefined) {
return res.status(900).json({
message: 'Data does not exist'
});
}
Data.find({ data: req.query.data}).exec()
.then(data => {
if (data.length < 1) {
return res.status(400).json({
message: "Nothing found"
});
}
// I need to return HTML here so the user sees something in his browser
return res.status(200).json({
data: data
});
}).catch(error => {
return res.status(500).json({
message: error
});
});
});
Check the fs_library: https://nodejs.org/docs/v0.3.1/api/fs.html
var http = require('http'),
lib = require('fs');
lib.readFile('./page.html', function (err, html) {
if (err) {
throw err;
}
http.createServer(function(request, response) {
response.writeHeader(200, {"Content-Type": "text/html"});
response.write(html);
response.end();
}).listen(8000);
});

Insert data into res.body back to client

with my client js i have perform a fetch and send along some data.
And i've attempted to do a res.send or res.json to send a result back.
Do I need to create a new json and send it?
Express Server
const express = require('express');
const app = express();
const pg = require('pg');
const conString = 'postgres://postgres:password#localhost/postgres';
const bodyParser = require('body-parser');
app.use(bodyParser.json()); //support json encoded bodies
app.use(bodyParser.urlencoded({ extended: true})); //support encoded bodies
app.use(function(req, res, next) {
res.header("Access-Control-Allow-Origin", "*");
res.header("Access-Control-Allow-Headers", "Origin, X-Requested-With, Content-Type, Accept");
next();
});
app.listen(3000, function () {
console.log('Server listening on port 3000!');
});
app.post('/', function(req, res){
var uname = req.body.username;
var pw = req.body.password;
QueryByUserName(uname, function(err, result){
if(err){
console.error('error happened', err);
//handle error
}
if(uname == result.username){
//name is in our database
if(pw == result.password){
console.log("User and Password Correct!");
res.json({ message: 'correct' });
} else {
console.log("Password Incorrect!");
res.json({ message: "incorrect" });
}
} else if(result.username == "undefined"){ //placeholder
console.log("username does not exist");
res.send( {message: "dne"} );
console.log(res);
}
});
});
function QueryByUserName(input, callback) {
pg.connect(conString, function(err, client, done){
if(err){
callback(err);
return;
}
client.query(SelectAll(), function(err, result){
//Query database for all usernames
done();
var isFound = false;
for(var x = 0; x < result.rows.length; x++){
//loop through all usernames from database.
if(result.rows[x].username == input){
isFound = true;
//username exists, now we obtain the rest of the data.
client.query(findDataInDatabase(input, '*'), function(err, result){
done();
if(err){
callback(err);
return;
}
console.log(result.rows[0]);
callback(null, result.rows[0]);
//return all information regarding user to callback function
});
} else if(x == (result.rows.length - 1) && !isFound){
callback(null, {username: "undefined"}); //placeholder
//Username does not exist in database
}
} //end of For Loop
});
});
}
function findDataInDatabase(username, WhatWeLookingFor) {
return 'select ' + WhatWeLookingFor + ' from users where username = \'' + username + '\'';
}
Express server side will try to send a message to the client.
but when I do this i did a console.log(res) it shows that the body is { null, null, null}
Client login Function
handleLogin: function(){
let fetchData = {
method: "post",
headers: {
'Accept': 'application/json',
'Content-Type': 'application/json'
},
body: JSON.stringify({
username: this.state.user,
password: this.state.password,
message: null
})
}
var fromServer = fetch('http://localhost:3000/', fetchData)
.then(function(response){
if( !response.ok){
throw Error (response.statusText);
}
console.log(response);
console.log(response.message);
console.log(response.body);
console.log(response.body.text);
return response.json();
})
.catch(error => console.log("there was an error --> " + error));
},
Edit : Screenshots below
Since you have returned, res.json({ message: 'correct' });
use,
response.message
Instead of,
response.body
var fromServer = fetch('http://localhost:3000/', fetchData)
.then(response => response.json())
.then(response => {
console.log(response.message);
})
After talking to Sraven, who was a huge help.
The code is sending a req, and the server is responding with a response with the message included. The problem was on the client side where it was not getting the response correctly.
Below is the working code and was able to finally produce a response.
var fromServer = fetch('http://localhost:3000/', fetchData)
.then(response => response.json())
.then(response => {
console.log(response.message);
})

Make REST API on Meteor+React without other packages

For example, when the path is
/json/users/4
meteor app must return json something like
{
id: 4,
name: 'Alex'
}
I'm using reactrouter:react-router for client routing. I know about reactrouter:react-router-ssr, but how to use it to response raw json? And make it not conflicting with existing client routing?
I found the answer. Meteor's default Webapp package will help (doc):
WebApp.connectHandlers.use("/hello", function(req, res, next) {
res.writeHead(200);
res.end("Hello world from: " + Meteor.release);
});
I put this in server folder. Other routes will be rendered as they was.
So, there is more useful example (es6):
WebApp.connectHandlers.use("/payme", function(req, res, next) {
res.writeHead(200, {'Content-Type': 'application/json'});
if (req.method === 'POST') {
req.on('data', (chunk) => {
const body = chunk.toString();
if (body.length < 1e6) {
const params = body.split('&').reduce((result, item) => {
const [key, val] = item.split('=');
//do it for utf-8 values (I use it for cyrillic strings)
result[key] = unescape(decodeURI(val)).replace(/\+/g, ' ');
return result;
}, {}); //post method params
//do something and get resulting json
res.end(JSON.stringify(result));
} else
res.end(JSON.stringify({error: 'too big query'}));
});
} else
res.end(JSON.stringify({error: 'isnt post req'}));
});
req.query can be used to get GET params.