Node Proxy - Proxy a SSL localhost target from a basic http server - node.js

What I am trying to do:
Proxy a java api that runs on https://127.0.0.1:443/api/ along side my UI that runs on non-SSL http://127.0.0.1:1337/ in order to circumnavigate some CORS issues.
My attempt:
Proxy the api at the SSL port 443 to my non-SSL development port of 1338.
proxy my UI to 1337
Proxy 1137 to :8080/index.html and proxy 1338 to :8080/api/
Access my app from localhost:8080
My problem:
The UI comes in just fine... but I can not hit the API at :8080/api/httpSession/init
Yes, I can still hit the API at https://localhost/api/httpSession/init
api.js - Renders index.html at :1337
var app = express();
app.all('*', function (req, res, next) {
res.header('Access-Control-Allow-Origin', '*');
res.header('Access-Control-Allow-Methods', 'PUT, GET, POST, DELETE, OPTIONS');
res.header('Access-Control-Allow-Headers', 'Content-Type');
next();
});
var options = {
changeOrigin: true,
target: {
https: true
}
};
httpProxy.createServer(443, '127.0.0.1', options).listen(1338);
start.js - Proxies 1337 and 1338 into 8080
// First I start my two servers
uiServer.start(); // renders index.html at 1337
apiServer.start(); //
// I attempt to patch them back into one single non-SSL port.
app
.use('/', proxy({target: 'http://localhost:1337/'}))
.all('/api/*', proxy({target: 'http://localhost:1338/'}))
.listen(8080, function () {
console.log('PROXY SERVER listening at http://localhost:%s', 8080);
});

What you're looking for is request piping. Try this example:
// Make sure request is in your package.json
// if not, npm install --save request
var request = require('request');
// Intercept all routes to /api/...
app.all('/api/*', function (req, res) {
// Get the original url, it's a fully qualified path
var apiPath = req.originalUrl;
// Form the proxied URL to your java API
var url = 'https://127.0.0.1' + apiPath;
// Fire off the request, and pipe the response
// to the res handler
request.get(url).pipe(res);
});
Make sure to add some error handling if the api can't be reached, such as this SO solution.

For the proxy issue, my guess is that it is keeping the /api/* in the url and that's not present on the router in your API service. You could try adding /api to the router in the API service since it's going to keep the url string the same when it sends it. Otherwise, you likely need to proxy and rewrite the url so that the API will match the request to a route.
On another note, what about just installing the cors module and using in the app? I do something similar and it's working well without all the proxy items. https://www.npmjs.com/package/cors

Related

Google App Engine server not adding CORS headers

I have an Express server as backend of my Dialogflow chatbot, when I access from the same host, I have no problem requesting the API, but when I access from the Firebase hosting whereI have my web App, I cannot request anything since the CORS headers are not being added, even though I added them.
This is the code:
// Sends static files from the public path directory
app.use(express.static(path.join(__dirname, '/Public')))
// Set Server Config
app.use(bodyParse.urlencoded({
extended: true
}));
app.use(bodyParse.json());
app.use(helmet.frameguard());
app.use(function (request, response, next) {
// Website you wish to allow to connect
// Request methods you wish to allow
response.setHeader('Access-Control-Allow-Methods', 'GET, POST, OPTIONS, PUT, PATCH, DELETE');
// Request headers you wish to allow
response.setHeader('Access-Control-Allow-Headers', 'X-Requested-With,content-type');
response.setHeader("Access-Control-Allow-Origin", 'https://newagent-249c5.web.app/');
// Pass to next layer of middleware
next();
});
// Configure Routes
app.use('/api', apiRoutes);
// Server index.html page when request to the root is made
app.get('/', function (request, response, next) {
response.sendFile('./Public/index.html');
});
var server = app.listen(8080, function () {
var host = server.address().address;
var port = server.address().port;
console.log("server listening at http://%s:%s", host, port);
});
This is cross-origin
This is the headers when I access from the same origin
For CORS most important is Access-Control-Allow-Origin header, and that client, who requesting, has host equal to value of this header. You can test things out pretty simple locally, spin up simple express server on, say, port 3000, and serve index.html with another server, express or nginx, on some other port. Then add request in index.html to first server, and try to fix CORS on this simple example.

Node JS server proxy using additional paths not working

I have a simple node js server like this:
var express = require('express');
var app = express();
var httpProxy = require('http-proxy');
var apiProxy = httpProxy.createProxyServer();
var serverOne = 'http://<address>:<port>/sap/opu/odata/sap/Z_ATTENDANCE_SRV/';
app.use(express.static('webapp'));
app.use(express.static('./'));
app.all("/attendance/*", function(req, res) {
console.log('redirecting to Server1: ' + serverOne);
apiProxy.web(req, res, {target: serverOne});
});
app.listen(3000);
So address localhost:3000/attendance should redirect me to http://<address>:<port>/sap/opu/odata/sap/Z_ATTENDANCE_SRV/ but it is not, I am getting 404.
I was able to make it work when I set the proxy path as "/*" instead of "/attendance/*", but when I wanted to access entity set "AttendanceSet" via localhost:3000/AttendanceSet it also gave me 404. Do I need to create proxy for all my paths? Shouldn't the /* do that?
When I check initialisation of oDataModel in SAPUI5 app I can see such a request (In this case I have set "/*" for the proxy):
Request URL: http://localhost:3000/$metadata?sap-language=EN
Request Method: GET
Status Code: 200 OK (from disk cache)
Remote Address: [::1]:3000
Referrer Policy: no-referrer-when-downgrade
By this logic, I should be able to access entity set AttendanceSet, but I guess I am missing something.
Thanks.
For urls like localhost:3000/AttendanceSet or localhost:3000/Attendance you have to remove the / you have in you endpoint like this.
app.all("/attendance*", function(req, res) {
console.log('redirecting to Server1: ' + serverOne);
apiProxy.web(req, res, {target: serverOne});
});
Looks like problem was with cache. I deleted browser cache and also, when running node js server I used addition -c-1 to do no caching.

Why and how this simple node.js proxy works?

I have a frontend-only web application on Netlify which has to consume an API on OpenSubtitles.org. Although OpenSubtitles.org enables CORS, sometimes I got preflight errors, so I decided to use a proxy.
I had problems using Netlify's proxy feature, so I decided I will create my own proxy on Heroku, and send my requests from the frontend to there, so these will be proxied to OpenSubtitles.org from a server.
I came up with the following based on the code I found here:
const express = require('express');
const request = require('request');
express()
.use('/', function(req, res) {
req.pipe(
request({
url: 'http://rest.opensubtitles.org/search' + req.url,
headers: {
'User-Agent': 'TemporaryUserAgent'
}
})
).pipe(res);
})
.listen(process.env.PORT || 8000);
I thought I deploy this, try it out, then I will enable CORS on it after that. However I've just realized it is working perfectly without doing anything else. How is it possible? Why can I call this from a frontend-only app on a different domain without explicitly enabling CORS?
Also, what if the server crashes, how to handle the errors there?
CORS is working because the url you're requesting responds with the header Access-Control-Allow-Origin set with a value of *. Since you're piping that response and its headers back to the original res object, it will enable CORS as if it was coming from your local proxy.
Below is a more straightforward example of how to proxy a request to another site and return its response intact using node streams.
const express = require('express')
const request = require('request')
const port = process.env.PORT || 1337
let server = express()
const proxyMiddleware = (req, res, next) => {
let url = `https://www.google.com/${req.url}`
let proxyRequest = request(url)
// Pass request to proxied request url
req.pipe(proxyRequest)
// Respond to the original request with the response from proxyRequest
proxyRequest.pipe(res)
}
server.use(proxyMiddleware)
server.listen(port, () => console.log(`Listening on ${port}`))

Forward HTTP to HTTPS - AWS Windows Node.js

I have a applications running on AWS Windows and Node.js. I can access using http and https. But i need it to forward http to https if anyone access through http.
I can think of many way, but would appreciate any advice on the best approach. The server is a EC2 instance, accessed through a load balancer.
If you're using express, this middleware module makes it easy to enforce https: https://www.npmjs.com/package/express-force-ssl
If you're using a reverse proxy in front of your app (ELB, nginx, etc), you'll need to set the trust proxy setting.
Here's a sample without the above module:
// Forward all requests to HTTPS.
// enable reverse proxy support in Express. This causes the
// the "X-Forwarded-Proto" header field to be trusted so its
// value can be used to determine the protocol. See
// http://expressjs.com/api#app-settings for more details.
app.enable('trust proxy');
// Add a handler to inspect the req.secure flag (see
// http://expressjs.com/api#req.secure). This allows us
// to know whether the request was via http or https.
app.use((req, res, next) => {
if (req.secure) {
// request was via https, so do no special handling
next();
} else {
// request was via http, so redirect to https
console.log('Redirecting to https');
res.redirect('https://' + req.headers.host + req.url);
}
});
Complete sample app.js
var express = require('express');
var app = express();
// Forward all requests to HTTPS.
// enable reverse proxy support in Express. This causes the
// the "X-Forwarded-Proto" header field to be trusted so its
// value can be used to determine the protocol. See
// http://expressjs.com/api#app-settings for more details.
app.enable('trust proxy');
// Add a handler to inspect the req.secure flag (see
// http://expressjs.com/api#req.secure). This allows us
// to know whether the request was via http or https.
app.use((req, res, next) => {
if (req.secure) {
// request was via https, so do no special handling
next();
} else {
// request was via http, so redirect to https
console.log('Redirecting to https');
res.redirect('https://' + req.headers.host + req.url);
}
});
// Respond to any GET requests with our message
app.get('*', (req, res) => {
res.send('This is only served over https');
});
// Listen on the assigned port
var port = process.env.PORT || 3001;
app.listen(port);
console.log('Hello started on port ' + port);
Redirect only GET requests, respond with error for non-GET requests
app.all('*', (req, res, next) => {
if (req.secure) {
next();
} else if (req.method === 'GET') {
res.redirect(`https://${req.headers.host}${req.url}`);
} else {
res.status(401).send('Secure channel required');
}
});

Cross-domain POST request in Node.JS with preflight?

I have just started with Node.
I am trying to get cross-domain form data from an HTML form to parse in a Node.js server. I have been able to do this with simple POST data, not with POST requests that require preflight.
I am running the Node code on cloud9 app servers. I am also using the Cors module to handle the requests. This module works well with simple requests (test here to see a simple request work), however with requests that require preflight I get this result from the Chrome inspector console.
XMLHttpRequest cannot load https://nms-motaheri-1.c9.io:8080/mail.
The request was redirected to 'https://c9.io:8080/api/nc/auth?.....SHORTENED',
which is disallowed for cross-origin requests that require preflight.
Here is my server.js code:
// Define dependencies
var express = require('express')
, cors = require('cors')
, app = express()
, parse_post = require("parse-post");
// Core module config
var corsOptions = {
origin: '*',
preflightContinue: true // <- I am assuming this is correct
};
app.use(cors(corsOptions));
// Respond to option request with HTTP 200
// ?? Why is this not answering my OPTION requests sufficiently ??
app.options('*',function(req,res){
res.send(200);
});
// Give a hello world response to all GET requests
app.get('/',function(req,res){
res.sendFile(__dirname + '/index.html');
});
// Handle all POST requests to /mail
app.post('/mail', parse_post(function(req, res) {
console.log(req.body);
res.json({msg: 'This is CORS-enabled for all origins!'});
})
);
// Listen on default Cloud9 port which is 8080 in this case
app.listen(process.env.PORT, function(){
console.log('CORS-enabled web server listening on port ' + process.env.PORT);
});
Why is this happening and how can I satisfactorily answer the OPTION request for my POST with pre-flight?
Here is the post request and response in Chrome dev tools:
Turns out that part of the problem was that the cloud9 server was set to private making these requests all redirect.
After making the server public, the redirections stopped. However, I received an error that the Node.js server did not have any Access-Control-Allow-Origin headers to allow requests from my cross origin domain. I noticed that "simple" with-out preflight requests would go through. So instead of trying to understand why it was not accepting my allow-all-origin-configuration on the Node.js side I decided to serialized the POST data to get rid of the preflight requirement and changed the data type in my angular request to plain text.
To get rid of preflight, first get rid of any POST header configuration (cache, etc), make sure your request Content-Type is plain text and make sure your actual content is plain text too. So if it is in JSON serialize it in jQuery before sending it with POST.
This is what my new Angular Post request code looked like:
sendEmail: function(email) {
var config = {
headers: {
'Content-Type': 'text/plain'
}
};
var POSTDATA= JSON.stringify(POSTDATAJSON);
return $http.post(POSTURL, POSTDATA, config)
}
And in Node.js this, I am using the cors Node.js module:
app.post('/mail', parse_post(function(req, res) {
var postReq = JSON.parse(Object.keys(req.body));
}));

Resources