why can't I send http requests outside of a web app? - mongodb

when I try to make requests to a server running on localhost:8080, it sometimes gives a 404 or 405 error depending on the type of request. most recently this has happened when I try unit testing via JEST.
I know for a fact there's nothing wrong with the actual code because it works perfectly fine when other people run it on their PCs.
the exact same fetch requests that fail in unit tests work perfectly fine within a web application.
I first noticed this problem on Postman. The problem seems to occur when I try making requests just before or after the server has started listening, and then I have to wait for a bit before requests work again. Of course it makes sense that it wouldn't work when my server isn't listening yet, but the problem persists for a while after it does. This problem usually doesn't occur on postman anymore now that I make sure to wait for the server to start listening.
here's the response with a GET or POST request:
<!DOCTYPE html>
<html><head><title>Not Found</title></head>
<body>
<h2>Access Error: 404 -- Not Found</h2>
<pre>Cannot open document for: /myroute/mypath</pre>
</body>
</html>
with a PUT request:
<!DOCTYPE html>
<html><head><title>Method Not Allowed</title></head>
<body>
<h2>Access Error: 405 -- Method Not Allowed</h2>
<pre>The "PUT" method is not supported by file handler</pre>
</body>
</html>
simple versions of the code I'm working with (again, I know for a fact this code works on a PC other than mine)
const mongoose = require('mongoose');
require('../src/model/logboekTemplate.js');
const fetch = require('node-fetch');
const SERVERIP = "http://localhost:8080/logboeken/"
const dbName = 'rekenlogboek';
const Logboek = mongoose.model('Logboek');
describe('Logboeken route Tests', () => {
beforeAll(async () => {
mongoose.connect(`mongodb://localhost:27017/${dbName}`, { useUnifiedTopology: true, useNewUrlParser: true, retryWrites: false }).then().catch(err => {
console.log(err);
});
await Logboek.deleteMany({});
});
beforeEach(async () => {
await Logboek.create([
{
naam: "testlogboek",
},
]);
});
afterEach(async () => {
await Logboek.deleteMany({});
});
//laat de gehele lijst logboeken zien
test('get /logboeklijst', async () => {
let response = await fetch(SERVERIP + "/logboeklijst", {
method: 'get', // *GET, POST, PUT, DELETE, etc.
mode: 'cors', // no-cors, *cors, same-origin
});
expect(response.status).toEqual(201);
let body = await response.json()
expect(body[0].naam).toEqual("testlogboek");
})
})
This server works perfectly fine normally, it's just the unit tests that fail and even then only when I do it on my laptop.
const express = require('express')
const http = require('http')
const mongoose = require('mongoose');
const cors = require('cors');
const bodyParser = require('body-parser');
const app = express();
const server = http.createServer(app);
app.use(bodyParser.text());
app.use(bodyParser.json());
app.use(cors());
const { logboekRouter } = require('./routes/logboeken');
app.use('/logboeken', logboekRouter);
//-------------------------------------//
//Mongoose
//-------------------------------------//
let dbName;
dbName = "rekenlogboek";
mongoose.connect(`mongodb://localhost:27017/${dbName}`, { useUnifiedTopology: true, useNewUrlParser: true, retryWrites: false }).then().catch(err => {
console.log(err);
});
server.listen(8080,
function () {
console.log("The Server is listening on port 8080.")
});
and the command line
> project-tests#1.0.0 test C:\Users\Wouter\Documents\GitHub\{my project}
> jest --runInBand --verbose
(node:23248) DeprecationWarning: collection.ensureIndex is deprecated. Use createIndexes instead.
console.warn
Mongoose: looks like you're trying to test a Mongoose app with Jest's default jsdom test environment. Please make sure you read Mongoose's docs on configuring Jest to test Node.js apps: http://mongoosejs.com/docs/jest.html
at Object.<anonymous> (deployment/node_modules/mongoose/lib/helpers/printJestWarning.js:4:11)
at Object.<anonymous> (deployment/node_modules/mongoose/lib/index.js:46:1)
FAIL deployment/routes/logboeken.test.js
Logboeken route Tests
× get /logboeklijst (42 ms)
● Logboeken route Tests › get /logboeklijst
expect(received).toEqual(expected) // deep equality
Expected: 201
Received: 404
212 | mode: 'cors', // no-cors, *cors, same-origin
213 | });
> 214 | expect(response.status).toEqual(201);
| ^
215 | let body = await response.json()
216 | expect(body[0].naam).toEqual("testlogboek");
217 | })
at Object.<anonymous> (deployment/routes/logboeken.test.js:214:33)
at runMicrotasks (<anonymous>)

Related

How to Connect to localhost Mongodb using an emulator?

First time making a question here so sorry for anything that isn't clear.
Basically I took a react course that set up a functional site with crud and now I'm trying to set up something similar in react native. So I'm new to react, react-native, and mongodb; basically everything I'm working with. This is for self study as well, so just trying to expand my skills.
This is the code that works in my react app under server.js from the backend
const client = await MongoClient.connect("mongodb://localhost:27017", {
useUnifiedTopology: true,
});
//api inbetween these two
app.listen(8000, () => console.log("Listening on port 8000"));
On my front end in my package.json I have this
"proxy": "http://localhost:8000/",
from my understanding this basically lets my frontend "proxy"/mask itself as if its coming from port 8000. Then I can call it using a url like below.
useEffect(() => {
const fetchData = async () => {
const result = await fetch(`/api/articles/${name}`);
const body = await result.json();
setArticleInfo(body);
};
fetchData();
}, [name]);
In my react-native app I get an unhandled promise rejection and in postman I get an internal 500 error with no associated error seems to be null since nothing is printed. Doing some research, I think the issue is because now I'm running this app on an emulator so I think it can't identify the main computer or localhost?
I've tried changing the localhost part of the address to my ipv4 address, to the expo connection url, 0.0.0.0, and also to what I believe is the android localhost 10.0.2.2 but none of them seems to work. example of android localhost change.
server.js
const client = await MongoClient.connect("mongodb://10.0.2.2:27017", {
useUnifiedTopology: true,
});
//api inbetween these two
app.listen(8000, () => console.log("Listening on port 8000"));
package.json
"proxy": "http://10.0.2.2:8000/",
When it's set to 10.0.2.2 postman times out and the app still has the unhandled promise error.
When it's set to 0.0.0.0 postman returns a 500 error with no details and the app still has the unhandled promise error.
When it's set to the expo url it straight up doesn't work.
When its set to my ipv4 address I get the unhandled promise and postman returns this error
"error": {
"name": "MongoServerSelectionError",
"reason": {
"type": "Single",
"setName": null,
"maxSetVersion": null,
"maxElectionId": null,
"servers": {},
"stale": false,
"compatible": true,
"compatibilityError": null,
"logicalSessionTimeoutMinutes": null,
"heartbeatFrequencyMS": 10000,
"localThresholdMS": 15,
"commonWireVersion": null
}
}
Posts I've looked at
Connect to MongoDB Atlas Cluster db with react-native app
Mongodb connection with react native form
React Native / Expo : Fetch throws “Network request failed”
why do we use 10.0.2.2 to connect to local web server instead of using computer ip address in android client
Your useEffect is wriiten wrong
Write it like this
useEffect(() => {
GetArticles(); // Called whenever there's a change in name
}, [name]);
// Create this function outside useEffect
const GetArticles = async () => {
try {
// Also in your fetch you have yo write url like this
const result = await fetch(
`http://${YOUR_IPv4_ADDRESS}:8000/api/articles/${name}`
);
const body = await result.json();
setArticleInfo(body);
} catch (error) {
console.log(error);
}
};
Also this is 100% correct
const client = await MongoClient.connect("mongodb://localhost:27017", {
useUnifiedTopology: true,
});
//api inbetween these two
app.listen(8000, () => console.log("Listening on port 8000"));
Don't change it to this
const client = await MongoClient.connect("mongodb://10.0.2.2:27017", {
useUnifiedTopology: true,
});
//api inbetween these two
app.listen(8000, () => console.log("Listening on port 8000"));

GET request to port 81 using axios (or even js native fetch)

I have a Node.js API running on port 81, and want to hit the endpoint from JavaScript like this:
function fetchFromApi() {
const axios = require('axios');
console.log('using port 81',axios.defaults);
axios.request({
method: 'get',
url:'/api/getAccountList',
port: 81, // port options is not valid - this does not have the desired result
})
.then( response => {
console.log(response);
const data = response.data;
const errors = (data.errors) ? data.errors : false;
if (errors) {
setErrors(errors);
}
})
.catch( reason => {
console.log(reason);
});
}
The network tab in chrome developer tools show this request still went to port 80.
When I try to code the entire protocol, port, host and url in the axios request, I get a CORS error:
axios.get('http://localhost:81/api/getAccountList')
Error is:
Access to XMLHttpRequest at 'http://localhost:81/api/getAccountList'
from origin 'http://localhost' has been blocked by CORS policy: No
'Access-Control-Allow-Origin' header is present on the requested
resource.
My API server is a simple Node.js server:
const express = require('express');
const app = express();
const port = 81;
app.get('/api/getAccountList', (req, res) => {
const userIdBy = req.params.userIdBy;
const apiToken = req.params.apiToken;
if (!(userIdBy && apiToken)) {
res.status(200).json({errors:['Missing credentials']});
return true;
}
// check the user id and api token match up:
console.log('Hello');
});
app.listen(port);
How can I make my client query the API using HTTP on port 81?
CORS is a security feature in most browsers that disables cross-origin requests—i.e., requests from a different hostname. To surpass it, install the cors dependency on your Express server via npm using:
npm install cors
Then you need to add it to every app via the cors() function to every {{httpMethod}} you want to allow other domains to make requests to.
Try editing your code like this:
const express = require('express');
const cors = require('cors')
const app = express();
const port = 81;
app.get('/api/getAccountList', cors(), (req, res)=>{})
On the client side, to get Axios to GET from port 81 on the same host as the javascript is running I used:
import axios from 'axios';
//...
//...
axios.defaults.baseURL = window.location.protocol + "//" + window.location.hostname + ":81";
const result = await axios('/your/endpoint');
//...
//...
Can you try to add this to your Node.js server?
// Add headers
app.use(function (req, res, next) {
// Website you wish to allow to connect
res.setHeader('Access-Control-Allow-Origin', 'http://localhost:81');
// Request methods you wish to allow
res.setHeader('Access-Control-Allow-Methods', 'GET, POST, OPTIONS, PUT, PATCH, DELETE');
// Request headers you wish to allow
res.setHeader('Access-Control-Allow-Headers', 'X-Requested-With,content-type');
// Set to true if you need the website to include cookies in the requests sent
// to the API (e.g. in case you use sessions)
res.setHeader('Access-Control-Allow-Credentials', true);
// Pass to next layer of middleware
next();
});
You can try to add only Access-Control-Allow-Origin header or modify others to your needs.
To achieve the required CORS protection AND avoid hard coding the servers FQDN / hostname, I used this code in my node api server:
const express = require('express');
const app = express();
const apiProviderPort = 81;
const allowedApiConsumerPort = 80;
app.use(function (req, res, next) {
const host = req.get('host'); // NOTE host is the fqdn:port
const hostSplit = host.split(':');
var fqdn;
if (hostSplit.length == 1) {
// I am not sure this is needed, it will be if hostname is fqdn[:port]
fqdn = host;
} else if (hostSplit.length == 2) {
fqdn = hostSplit[0];
} else {
console.log('Error the host contained multiple colons!');
}
console.log('protocol:',req.protocol,'host:',host,'fqdn:' + fqdn);
// next line edited March 2020 - I changed + '//' + to + '//:' +
// as the developer tools console showed
// The 'Access-Control-Allow-Origin' header contains the invalid value 'http//localhost:3000'.
const allowableOrigin = req.protocol + '//' + fqdn + ':' + allowedApiConsumerPort;
console.log('allowableOrigin:',allowableOrigin)
res.setHeader('Access-Control-Allow-Origin', allowableOrigin);
next();
});
app.get('/api/userDocReportData/', (req, res) => {
const userIdBy = req.params.userIdBy;
const apiToken = req.params.apiToken;
if (!(userIdBy && apiToken)) {
res.status(200).json({errors:['Missing credentials']});
return true;
}
// check the user id and api token match up:
// ...
// get your payload etc
res.status(200).json({errors:false,payload:{} });
});
app.listen(apiProviderPort);
I enhanced #webprogrammers answer above as I wanted code that could work in any environment (localhost; test.example.com, live.example.com etc)

I'm getting a Web Push Error Code Status 403, which is driving me nuts, because its telling me to use firebase. What's going on?

I keep getting a WebPush Error (Status Code 403) fro Chrome for a PWA I'm building and the body says that I need to use the VAPID server key from the 'firebase console' but I used nodes Web-Push library to generate the VAPID Keys, whats going on? Do I have to use firebase to build PWAs in Chrome?
Here's the Error Message I'm getting from the browser when I send a push notification:
name: 'WebPushError',
message: 'Received unexpected response code',
statusCode: 403,
headers:
{ 'content-type': 'text/plain; charset=utf-8',
'x-content-type-options': 'nosniff',
'x-frame-options': 'SAMEORIGIN',
'x-xss-protection': '0',
date: 'Thu, 31 Oct 2019 19:59:02 GMT',
'content-length': '194',
'alt-svc':
'quic=":443"; ma=2592000; v="46,43",h3-Q049=":443"; ma=2592000,h3-Q048=":443"; ma=2592000,h3-Q046=":443"; ma=2592000,h3-Q043=":443"; ma=2592000',
connection: 'close' },
body:
'the key in the authorization header does not correspond to the sender ID used to subscribe this user. Please ensure
you are using the correct sender ID and server Key from the Firebase console.\n',
endpoint:
'https://fcm.googleapis.com/fcm/send/exXmW3OFOTY:APA91bEKW_vxnvOZohog34pprDH6XvBsxtfnUpBdYY7z_7q4GZGa4wrmtBBg4kTRwLtgy3lNpCs8SMlvOr4nY-Fu_4zUus6zEJh69581Ier14QZxkEEVXyZHKRaZcmHa3zmbZRB4VD7Z
and here's the code that is running my node server:
//Handle imports
const express = require('express')
const cors = require('cors')
const bodyParser = require('body-parser')
const webPush = require('web-push')
const vapidKeys = require('./vapid.json')
const path = require('path')
//Setup application
const app = express()
app.use(cors())
app.use(bodyParser.json())
app.use('/static', express.static(path.join(__dirname,'frontend')))
const port = 8080
//Set up webpush
webPush.setVapidDetails(
'mailto: <email>',
vapidKeys.publicKey,
vapidKeys.privateKey
)
const pushOptions = {
proxy: '<proxy>'
}
//setup Push Notification
const sendNotification = (subscription, dataToSend='') => {
webPush.sendNotification(subscription, dataToSend, pushOptions).catch(error => { console.log('Damn it: ', error.message, '||', error)
})
}
//Server Routes Defined
app.get('/', (req, res) => res.sendFile('index.html', { root: './' }))
//Setup Database Methods
const dummyDb = {subscription: null}
const saveToDatabase = async subscription => {
dummyDb.subscription = subscription
}
//Other Server Routes
app.post('/save-subscription', async (req, res) => {
const subscription = req.body
await saveToDatabase(subscription)
console.log('subscribed!')
res.json({message: 'success'})
})
app.get('/send-notification', (req, res) => {
const subscription = dummyDb.subscription
const message = 'hello world'
sendNotification(subscription, message)
res.json({message: dummyDb.subscription})
})
app.listen(port, () => console.log(`Example app listening on port ${port}!`))
I have node.js express, postgres, angular 8 app.
I had the same problem and I got it working by adding the "gcm_sender_id": in the manifest.webmanifest file (or manifest.json I also used firebase generated public and private keys.
your gcm_sender_id is your project id in google cloud or firebase sender id
Same situation and almost lost my sanity. I tried inserting gcm_sender_id with a Firebase senderId and worked finally. I didn't have a Firebase account, but I was able to create a project in seconds and my senderId was ready to be used in the messaging settings.
But a caveat: After my modification in the manifest.json (in my case) in the root's folder, it was needed to uninstall the current service worker and restart my React project. Then I followed again all steps back by asking permissions and subscribe the user and finally trigger a push notification.
During my heat researches for a solution, I found that gcm_sender_id is also used to send and validate push messages from other browsers. According to Google Web Updates:
For Chrome prior to version 52, Opera Android and the Samsung Browser,
you're also still required to include a 'gcm_sender_id' in your web
app's manifest.json. The API key and sender ID are used to check
whether the server making the requests is actually allowed to send
messages to the receiving user.

rest-hapi standalone endpoint not returning handler results

Forgive me if it's a silly question, but the last time I coded in javascript was almost 20 years ago... I'm re-learning javascript these weeks and I'm not sure I got it all.
I'm using hapi with rest-hapi and want to add some standalone endpoints, basically translating the backend portion of this Autodesk tutorial form express.
I'm using the basic rest-hapi example main script, and tried to add a route with the following code:
//api/forge.js
module.exports = function(server, mongoose, logger) {
const Axios = require('axios')
const querystring = require('querystring')
const Boom = require('boom')
const FORGE_CLIENT_ID = process.env.FORGE_CLIENT_ID
const FORGE_CLIENT_SECRET = process.env.FORGE_CLIENT_SECRET
const AUTH_URL = 'https://developer.api.autodesk.com/authentication/v1/authenticate'
const oauthPublicHandler = async(request, h) => {
const Log = logger.bind('User Token')
try {
const response = await Axios({
method: 'POST',
url: AUTH_URL,
headers: {
'content-type': 'application/x-www-form-urlencoded',
},
data: querystring.stringify({
client_id: FORGE_CLIENT_ID,
client_secret: FORGE_CLIENT_SECRET,
grant_type: 'client_credentials',
scope: 'viewables:read'
})
})
Log.note('Forge access token retrieved: ' + response.data.access_token)
return h.response(response.data).code(200)
} catch(err) {
if (!err.isBoom){
Log.error(err)
throw Boom.badImplementation(err)
} else {
throw err
}
}
}
server.route({
method: 'GET',
path: '/api/forge/oauth/public',
options: {
handler: oauthPublicHandler,
tags: [ 'api' ],
plugins: {
'hapi-swagger': {}
}
}
})
}
The code works and I can display the access_token in nodejs console, but swagger doesn't get the response:
At first I thought that an async function cannot be used as handler, but my hapi version is 17.4.0, and it supports async handlers.
What am I doing wrong?
It turns out it was an easy fix: I just needed to specify the Hapi server hostname in my main script!
The problem was with CORS, since Hapi used my machine name instead of localhost. Using
let server = Hapi.Server({
port: 8080,
host: 'localhost'
})
solved my problem.

cannot GET / express.js routing

I am at the early stages of a simple tasks manager that I want to build with the MEAN Stack.
I can figure/resolve a simple routing issue. I don't see any error message in the terminal or console except for the 404 client error.
the root path is ok. I get a response back
I use html docs to render the ui for both.
this is how I have set up my server.js
var express = require('express')
var path = require('path')
var bodyParser = require('body-parser')
var index = require('./routes/index');
var tasks = require('./routes/tasks');
var app = express();
const port = '3456'
app.use('/', index)
app.use('api', tasks) <= HERE
//view engine
app.set('views', path.join(__dirname, 'views'))
app.set('view engine', 'ejs')
app.engine('html', require('ejs').renderFile);
//static folder
app.use(express.static(path.join(__dirname, 'client')))
app.use(bodyParser.json())
app.use(bodyParser.urlencoded({extended: false}))
app.listen(port, function() {
console.log('Starting the server at port' + port );
})
tasks.js
to render the template at the set route
var express = require('express')
var router = express.Router();
var mongojs = require('mongojs');
var db = mongojs('mongodb://sandy:cookie2080#ds147304.mlab.com:47304/tasklists_21092017', ['tasks'])
router.get('/tasks', function(req, res, next) {
res.send('api')
res.render('tasks.html')
db.tasks.find(function(err, tasks){
if (err) {
res.send('error message ' + err)
}
res.json(tasks)
})
})
module.exports = router;
and, index.js fyi
var express = require('express')
var router = express.Router();
router.get('/', function(req, res, next) {
res.render('index.html')
})
module.exports = router;
screenshot at the link below of the 404 error in browser after starting server on port 3456
404 error - screenshot
thanks for the help. I am sure it can be a little detail. it is very hard to debug though.
This error occurs because there's no route that handles the endpoint /api. What you can do here is create a middleware that will handle the /api. You can do it in your tasks.js like this:
tasks.js
router.get('/', function(req, res, next) {
res.send('This is api.')
})
Or if what you want to do is to direct the user from the endpoint /api to /api/tasks then you could do it like this:
router.get('/', function(req, res, next) {
res.redirect('/api/tasks')
})
Hope this helps.
I changed the port number. The issue was that the port 3000, was not responding to the requests, as it was still in use by an older process hence producing the warning
errno: 'EADDRINUSE',.
Just used the port 5000 to try out and it went through smoothly.
By the way I am using vs code.