Vaadin-Upload not working with http-proxy-middleware - node.js

I have a node.js / Polymer 1 website. I am using HTTP-proxy-middleware to route api calls (/api/webapi) to my backend API server.
On one of the pages I have a vaadin-upload (v2.3.0) component that sends files to the api. Everything appears to work fine when running on local host but when I deploy to our test servers I am experiencing issues. Either the upload completes quickly and then sits "processing" for a long time or it stalls.
Using postman I have managed to send a file to the API directly, to the proxy server. I have also managed to get the upload component to call the API directly. All these cases work correctly, and output from the API would suggest in all cases the API is receiving/processing data at the same rate. From this I have narrowed it down to an interaction between Vaadin-Upload and http-proxy-middleware.
Does anyone have experience with this and help me configure the proxy correctly.
proxy configuration:
const url = require('url');
var hpmproxy = require('http-proxy-middleware');
var config = require('../config');
// Adds user authorization token from passport to request
var addAuthTokenMiddleware = function (req, res, next) {
if (req.session && req.isAuthenticated()) {
req.headers['authorization'] = 'Bearer ' + req.user.token;
next();
} else {
req.abort();
}
};
function isLoggedIn(req, res, next) {
// if user is authenticated in the session, carry on
if (req.session && req.isAuthenticated())
return next();
res.status(403).end();
};
function restream(proxyReq, req) {
if (isMultipartRequest(req))
console.log('Multipart');
if (!isEmpty(req.body)) {
console.log("parse");
var bodyData = JSON.stringify(req.body);
proxyReq.setHeader('Content-Type', 'application/json');
proxyReq.setHeader('Content-Length', Buffer.byteLength(bodyData));
proxyReq.write(bodyData);
}
console.log("-->[proxyReq]----", proxyReq.path, proxyReq.getHeader('Content-Type'));
};
function handleResponse(proxyRes, req, res) {
console.log('---[proxyRes]<---', proxyRes.req.method, proxyRes.req.path, proxyRes.statusCode);
};
function isMultipartRequest(req) {
let contentTypeHeader = req.headers['content-type'];
return contentTypeHeader && contentTypeHeader.indexOf('multipart') > -1;
};
function isEmpty(obj) {
for(var prop in obj) {
if(obj.hasOwnProperty(prop))
return false;
}
return JSON.stringify(obj) === JSON.stringify({});
}
var options = {
target: config.webApiHost,
changeOrigin: true, // needed for virtual hosted sites
pathRewrite: {
'^/api/webapi/': config.webApiPath
},
secure: !config.selfSigned,
onProxyRes: handleResponse,
onProxyReq: restream
// ,logLevel: 'debug'
};
var hpmApiProxy = hpmproxy(options);
module.exports = function (app, passport, config) {
app.use('/api/webapi/', isLoggedIn, addAuthTokenMiddleware, hpmApiProxy);
console.log(' WebAPI Proxy Loaded');
}

Related

Node API, choosing an endpoint based on content-type in middleware function

I have an endpoint analysis in my Node/Express API that accepts JSON requests and currently have a middleware function that checks if content-type=application/json or returns an error. I added another endpoint called analysis/csv which can accept a csv with multer and requires content-type=multipart/form-data. I'm trying to basically have the user call the same endpoint analysis and then choose between them based on the content-type. I tried using res.redirect(analysis + /csv) but I get an error Can't set headers after they are sent. There is another middleware function after that checks for authentication before finally hitting up the endpoint itself.
My code so far:
router.use((req, res, next) => {
let url: string = req.originalUrl;
let endpoint: string = url.substring(url.lastIndexOf('/') + 1);
let multiEndpoints: string[] = ['analysis', ...];
if (req.get('content-type') != 'application/json' && multiEndpoints.includes(endpoint)) {
if (req.get('content-type') != 'multipart/form-data') {
// Send an error that content type must be application/json
}
else {
// I want to redirect to endpoint + /csv here
next();
}
}
else {
next();
}
});
The error says it all: Can't set headers after they are sent
After redirecting, you are looking for next. You don't need to call next when you redirect. You can update it like this:
router.use((req, res, next) => {
let url: string = req.originalUrl;
let endpoint: string = url.substring(url.lastIndexOf('/') + 1);
let multiEndpoints: string[] = ['analysis', ...];
if (req.get('content-type') != 'application/json' &&
multiEndpoints.includes(endpoint)) {
if (req.get('content-type') != 'multipart/form-data') {
// Send an error that content type must be application/json
}
else {
// I want to redirect to endpoint + /csv here
// next(); remove this line
res.redirect('/myRoute')
}
}
else {
next();
}
});
after doing redirect make sure you don't call next

POST http://localhost:3000/login/aa/aa 404 (Not Found)

I have an angular app and a nodejs backend server. I want to get data from my backend but when I try to connect to it with Angular HTTPClient, it says: POST http://localhost:3000/login/aa/aa 404 (Not Found).However, when I put the link manually into the browser, it works perfectly fine. Here is some code:
service.ts
addUser(user: IUser): Observable<IUser> {
return this.httpClient.post<IUser>(`http://localhost:3000/login/${user.email}/${user.passwort}`, user, {
headers: new HttpHeaders({
'Content-Type': 'application/json'
})
})
.pipe(catchError(this.handleError));
}
index.js
var mysql = require('mysql');
var express = require('express');
var app = express();
const port = process.env.PORT || 3000;
[...]
app.get('/login/:email/:pw',function(req,res) {
res.setHeader('Content-Type', 'application/json');
var passwort = new Passwort(''+req.params.pw);
passwort.comparePasswort();
con.query("SELECT u.Email, u.Hash FROM User u WHERE u.Email LIKE "+ "'" + req.params.email+ "'", function(err, result ){
if(err) throw err;
console.log(result)
res.send("test")
})
});
Thanks for every answer and for your time!
Your route in your backend is set as a get request and not a post request.
You should either convert your request to a get in your service with this.httpClient.get... or convert to a post request in your backend with app.post.
The reason it works in your browser is that the browser performs a GET request when acessing something using the address bar.
In backed you declared a get method and from frontend you are calling post. your code in service should be :-
addUser(user: IUser): Observable<IUser> {
return this.httpClient.get<IUser>(`http://localhost:3000/login/${user.email}/${user.passwort}`, {
headers: new HttpHeaders({
'Content-Type': 'application/json'
})
})
.pipe(catchError(this.handleError));
}
before using /:email you need to subscribe this particular element
const mongoose = require("mongoose");
const User = mongoose.model("User");
const userParams = (req, res, next, email) => {
User.findOne({email:email})
.then((user)=> {
if (!user) {
return res.sendStatus(404);
}
req.user = user;
return next();
})
.catch(next);
};
module.exports = userParams;
then use that in express router by typing
router.param("email", userParams);
this way your router will get to know what the params you are trying to send
In your index.js file, you are creating a handler for a GET request (which is the default request sent by your browser while accessing your webpage)
But in your service.ts file you are trying to send a post request to the server which is not handled, so the simple solution would be to replace the line
return this.httpClient.post<IUser> `http://localhost:3000/login/${user.email}/${user.passwort}`, user, {
with:
return this.httpClient.get<IUser> `http://localhost:3000/login/${user.email}/${user.passwort}`, user, {
For more info you can read this: https://angular.io/guide/http

nodejs async middlewares is not working

I am trying to make an OAuth authentication flow myself in nodejs, express, and mongodb. I am trying to check if the client is available in my MongoDB before each route. So I made a middleware function that checks if client id and client secret are available in my DB. Here is my middleware function
module.exports = function() {
return function(req, res, next) {
// Implement the middleware function based on the options object
winston.info("check client")
User.getClient(req.get("clientId"), req.get("clientSecret"), function (err, client) {
winston.info("checking time");
if (client) {
return next();
} else {
}
});
winston.info("check client done")
}
}
But as i am calling my mongoose model i think i middleware isnt working perfectly. By the time async call finishes. script is passing through.
So what i want to achieve here is that the middleware should pass if the client is available in our db. So middleware has to go forward after it gets response from the mongoose. Here is my router code.
var express = require('express')
, router = express.Router()
, clientVerify = require("../middlewares/clientVerify");
router.use(clientVerify())
I used promise to solve the issue. Thanks to #Vassilis Pallas for his guideline in the comments.
return new Promise( function(resolve,reject){
User.getClient(req.get("clientId"), req.get("clientSecret"), function (err, client) {
winston.info(client);
if (client) {
resolve( client );
} else {
res.status(401).send({
status: 401,
message: "unauthorized"
});
}
});
}).then(function(saveResult){
saveResult = saveResult && typeof saveResult == 'object' ? saveResult.toJSON() : saveResult;
winston.info(saveResult);
if (saveResult)
return next();
});

node http-proxy: async modification of request body

I need to modify the request body asynchronously. Something along the lines of this:
proxy.on('proxyReq', function(proxyReq, req, res, options) {
if(req.body) {
new Promise(function(resolve){
setTimeout(function() { // wait for the db to return
'use strict';
req.body.text += 'test';
let bodyData = JSON.stringify(req.body);
proxyReq.setHeader('Content-Type','application/json');
proxyReq.setHeader('Content-Length', Buffer.byteLength(bodyData));
// stream the content
proxyReq.write(bodyData);
resolve();
},1);
});
}
});
When I run this I get the error saying cannot modfiy headers once they have been set. Which makes sense.
How can I halt the sending of the request until I'm ready? I've looked at removing various listeners from proxyReq without success..
By looking at the source code #-) it seems like it's not really possible because the proxyReq event is sent and then the code moves on.
If it would instead wait for a promise, it would be possible (if you'd return that promise as well).
A minimal fork on this lib could be for example:
// Enable developers to modify the proxyReq before headers are sent
proxyReq.on('socket', function(socket) {
if(server) { server.emit('proxyReq', proxyReq, req, res, options); }
});
(proxyReq.proxyWait || Promise.resolve())
.then( ... // rest of the code inside the callback
And then
proxy.on('proxyReq', function(proxyReq, req, res, options) {
if(req.body) {
proxyReq.proxyWait = new Promise(function(resolve){
setTimeout(function() { ...
But depending on your use case, there might be other solutions as well. For example, consider if it's really necessary that you use this proxy library. It You could alternatively use http directly, where you have all the control on the events and callbacks.
You can set selfHandleResponse: true inside the HttpProxy.createProxyServer. This then allows (and forces) you to handle the proxyRes manually!
const proxy = HttpProxy.createProxyServer({selfHandleResponse: true});
proxy.on('proxyRes', async (proxyReq, req, res, options) => {
if (proxyReq.statusCode === 404) {
req.logger.debug('Proxy Request Returned 404');
const something = await doSomething(proxyReq);
return res.json(something);
}
return x;// return original proxy response
});
I came here looking for the solution to a slightly different problem: Modifying the request headers (not body) before proxying.
I post this here in case that it is helpful to others. And maybe the code can be adapted to also modify the request body.
const http = require('http');
const httpProxy = require('http-proxy');
var proxy = httpProxy.createProxyServer({});
var server = http.createServer(function(req, res) {
console.log(`${req.url} - sleeping 1s...`);
setTimeout(() => {
console.log(`${req.url} - processing request`);
req.headers['x-example-req-async'] = '456';
proxy.web(req, res, {
target: 'http://127.0.0.1:80'
});
}, 1000);
});
server.listen(5050);

Jupyterhub Configurable Http Proxy issue

I have been working with Jupyterhub's Configurable Http Proxy and I have been adding the necessary options for the proxy to handle client's ssl certificates without having to use the command line options.
My main goal is that I want to take in a clients request to the proxy and add their certificate information to the header. Once in the header, I will use jupyterhub's authenticator to craft a username.
My issue is that when I use the proxy.on('proxyReq method available for the http-proxy to set the header, I get this error: [Error: Can't set headers after they are sent.]
I have been looking all over the code to see where a response/request is being written or sent, but I cannot find it.
Here is the ConfigurableProxy function code, I can give you more if needed:
function ConfigurableProxy (options) {
var that = this;
this.options = options || {};
this.trie = new trie.URLTrie();
this.auth_token = this.options.auth_token;
this.includePrefix = options.includePrefix === undefined ? true : options.includePrefix;
this.routes = {};
this.host_routing = this.options.host_routing;
this.error_target = options.error_target;
if (this.error_target && this.error_target.slice(-1) !== '/') {
this.error_target = this.error_target + '/'; // ensure trailing /
}
this.error_path = options.error_path || path.join(__dirname, 'error');
if (this.options.default_target) {
this.add_route('/', {
target: this.options.default_target
});
}
options.ws = true;
options.secure= true;
// These are the ssl options
options.ssl = {
//Right the key and cert are relative path on my computer
//but these can be changed.
key: fs.readFileSync('/Users/grantherman/Desktop/jupyterHubCSProject/ssl/server.key'),
cert: fs.readFileSync('/Users/grantherman/Desktop/jupyterHubCSProject/ssl/server.crt'),
requestCert: true,
//Right now this is set to false, but if we add a CA to these options
// and set this to true, the proxy will reject all unkown ssl certs
rejectUnauthorized: false
};
var response = [];
var data = [];
var proxy = this.proxy = httpProxy.createProxyServer(options);
proxy.on('proxyReq', function(proxyReq, req, res, options) {
console.log("proxy request");
try{
proxyReq.setHeader('X-Special-Proxy-Header', req.socket.getPeerCertificate());
}catch(err){
console.log(err);
}
});
proxy.on('data', function(data, req, res, options) {
data.push(data);
});
proxy.on('proxyRes', function(proxyRes, req, res, options) {
response.push(proxyRes);
});
proxy.on('error', function(error, req, res, options) {
log.add(error);
});
proxy.on('close', function (req, socket, head) {
// view disconnected websocket connections
console.log('Client disconnected');
});
// tornado-style regex routing,
// because cross-language cargo-culting is always a good idea
this.api_handlers = [
[ /^\/api\/routes(\/.*)?$/, {
get : bound(this, authorized(this.get_routes)),
post : json_handler(bound(this, authorized(this.post_routes))),
'delete' : bound(this, authorized(this.delete_routes))
} ]
];
I think this is going to require modifications to configurable-http-proxy itself. The place to add headers is on the original req object prior to initiating the proxied request, here.
It would look something like:
ConfigurableProxy.prototype.handle_proxy = function (kind, req, res) {
...
req.headers['X-My-Header'] = 'My-Value';
// dispatch the actual method
this.proxy[kind].apply(this.proxy, args);
Adding a hook to CHP for modifying the request here, on its way through, should make this doable without modifying the CHP source.

Resources