I need to make a single middleware that will handle each response to web user. I try to make something like the following:
function ajaxResponseMiddleware(req, res, next) {
var code = res.locals._code || 200;
var data = res.locals._response;
res.json(code, data);
}
app.get('/ajax1', function(req, res, next){
// Do something and add data to be responsed
res.locals._response = {test: "data2"};
// Go to the next middleware
next();
}, ajaxResponseMiddleware);
app.get('/ajax2', function(req, res, next){
// Do something and add data to be responsed
res.locals._response = {test: "data2"};
res.locals._code = 200;
// Go to the next middleware
next();
}, ajaxResponseMiddleware);
The response is handled in ajaxResponseMiddleware function where I can add some default state for all my ajax responses.
One thing that I don't like in the approach above is the adding ajaxResponseMiddleware function in each route.
So what do you think about this approach? May you advise improvements or share your experience.
middleware is just a function function (req, res, next) {}
var express = require('express');
var app = express();
// this is the middleware, you can separate to new js file if you want
function jsonMiddleware(req, res, next) {
res.json_v2 = function (code, data) {
if(!data) {
data = code;
code = 200;
}
// place your modification code here
//
//
res.json(code, data)
}
next();
}
app.use(jsonMiddleware); // Note: this should above app.use(app.router)
app.use(app.router);
app.get('/ajax1', function (req, res) {
res.json_v2({
name: 'ajax1'
})
});
app.listen(3000);
Related
This is what i'm thinking, pseudo code.
const myRedirect = (routePath) => {
newUrl = routePath;
if (matches condition)
newUrl = do_some_modification(routePath);
return next(newUrl);
}
const myFunc = (routePath, myRedirect) => (newUrl, middleware) => {
return (ctx, newUrl, next) => {
return middleware(ctx, newUrl, next);
}
};
How to modify it to make it work please ?
const route = async function(ctx, next){
if(shouldRedirect){
ctx.redirect('/redirect-url'); // redirect to another page
return;
}
ctx.someData = getSomeData(); // ctx.someData will be available in the next middleware
await next(); // go to next middleware
}
I am trying to get ACL behavior by implementing my own webhooks for VerneMQ. I am using express and apicache node packages for this. I hope the code is meaningful to non-javascript-programmers as well.
In my vernemq.conf I have set up my hooks, and they get called correctly:
$ vmq-admin webhooks show
+-----------------+------------------------------+-------------+
| hook | endpoint |base64payload|
+-----------------+------------------------------+-------------+
|auth_on_subscribe|http://127.0.0.1:3000/vmq/sub | true |
|auth_on_register |http://127.0.0.1:3000/vmq/auth| true |
| auth_on_publish |http://127.0.0.1:3000/vmq/pub | true |
+-----------------+------------------------------+-------------+
Also I disabled all other plugins and disabled anonymous login.
My webhooks implementation in express (simplified):
const express = require('express');
const apicache = require('apicache');
const bodyparser = require('body-parser');
// short cache times for demonstration
const authcache = apicache.middleware('15 seconds');
const pubcache = apicache.middleware('5 seconds');
const subcache = apicache.middleware('10 seconds');
const app = express();
const jsonparser = bodyparser.json();
app.use((req, res, next) => {
console.log(`${req.connection.remoteAddress}:${req.connection.remotePort} ${req.method} ${req.path}`);
return next();
});
app.post('/vmq/auth', authcache, (req, res) => {
return res.status(200).json({result: 'ok'});
});
app.post('/vmq/pub', pubcache, jsonparser, (req, res) => {
// this gets ignored most of the time because of caching
if (req.body.topic === 'only/allowed/topic') {
return res.status(200).json({result: 'ok'});
}
return res.status(401).end();
});
app.post('/vmq/sub', subcache, (req, res) => {
return res.status(200).json({result: 'ok'});
});
app.use((req, res, next) => {
return res.status(404).end();
});
app.use((err, res, req, next) => {
console.error(err);
return res.status(500).end();
});
const server = app.listen(3000, 'localhost', () => {
const address = server.address();
return console.log(`listening on ${address.address}:${address.port} ...`);
});
Using mqtt.js I wrote a client (simplified):
const mqtt = require('mqtt');
const client = mqtt.connect('mqtt://localhost');
client.on('connect', () => {
setInterval(() => {
client.publish('only/allowed/topic', 'working');
client.publish('some/disallowed/topic', 'working too :(');
}, 500);
return client.subscribe('some/disallowed/topic');
});
client.on('message', (topic, message) => {
return console.log(`${topic}:${message}`);
});
What happens is that the client successfully authenticates and then publishes to only/allowed/topic, which is allowed and gets cached as successful by VerneMQ. However, since the successful call to /vmq/pub is now cached, publishing to some/disallowed/topic also works. If I change the order of publishing, both will fail.
I would have expected VerneMQ to map the cached results to all parameters in a call, except the payload of course, and not just to a client connection. However that is not the case. What's a possible way to implement ACL via webhooks while using caching? Not using caching is out of the question, as this kills my performance, and caching is recommended by the docs anways.
Also, will someone with 1500+ rep be so nice and create the tag vernemq? :)
I misunderstood how apicache works and what it actually does. All I needed to do was just setting the appropriate header for caching, as stated in the docs of VerneMQ. Apparently apicache stores the actual result and returns that whenever asked within the specified timeframe, no matter what the client is actually requesting.
This is the working code now:
const express = require('express');
const bodyparser = require('body-parser');
const app = express();
// short cache times for demonstration (in seconds)
const authcachetime = 15;
const pubcachetime = 5;
const subcachetime = 10;
const jsonparser = bodyparser.json();
app.use((req, res, next) => {
console.log(`${req.connection.remoteAddress}:${req.connection.remotePort} ${req.method} ${req.path}`);
return next();
});
app.post('/vmq/auth', (req, res) => {
res.set('cache-control', `max-age=${authcachetime}`);
return res.status(200).json({result: 'ok'});
});
app.post('/vmq/pub', jsonparser, (req, res) => {
res.set('cache-control', `max-age=${pubcachetime}`);
if (req.body.topic === 'only/allowed/topic') {
return res.status(200).json({result: 'ok'});
}
return res.status(401).end();
});
app.post('/vmq/sub', (req, res) => {
res.set('cache-control', `max-age=${subcachetime}`);
return res.status(200).json({result: 'ok'});
});
app.use((req, res, next) => {
return res.status(404).end();
});
app.use((err, res, req, next) => {
console.error(err);
return res.status(500).end();
});
const server = app.listen(3000, 'localhost', () => {
const address = server.address();
return console.log(`listening on ${address.address}:${address.port} ...`);
});
As expected the client now gets an error when it tries to publish to an illegal topic.
I'm currently redirecting traffic to https on my node server:
var express = require('express');
var path = require('path');
var app = express();
var port = process.env.PORT || 8080;
app.set('port', port);
app.use(express.static(path.join(__dirname, './src/public')));
app.all('*', function(req, res, next) {
if(req.headers["x-forwarded-proto"] === "https"){
// OK, continue
return next();
};
res.redirect('https://'+req.hostname+req.url);
})
app.get('*', function( req, res ) {
res.sendFile(path.join(__dirname, './src/public/index.html'));
} );
app.use(require('./server/routes/index'));
app.listen(process.env.PORT || 8080, function (){
console.log('Express started on port ' + app.get('port') + '; press Ctrl-C to terminate.');
});
module.exports = app;
In doing so, my AJAX calls aren't getting through: net::ERR_CONNECTION_REFUSED. Of course when I remove the above redirect, my AJAX calls make it through.
I managed to find one other post on SO from someone having this issue but the suggestion was to set Access-Control-Allow-Origin to accept any incoming AJAX requests, which seems like a security issue.
If I want continue to use the https redirect, is there a way to allow the AJAX calls through or do I need to turn of the redirect?
Use req.protocal and req.get('host') to get protocol and host url
app.all('*', function(req, res, next) {
if(req.headers["x-forwarded-proto"] === "https"){
// OK, continue
return next();
};
res.redirect(req.protocol + '://' + req.get('host') + req.originalUrl);
})
next accept another callback like
app.all(path, callback [, callback ...])
let you hava a index page then it should like
var index = require('./routes/index');
app.all('*', function(req, res, next) {
if(req.headers["x-forwarded-proto"] === "https"){
// OK, continue
next();
};
res.redirect(req.protocol + '://' + req.get('host') + req.originalUrl);
}, index)
taking straight from this post:
This code never executes.
var Promise = require("bluebird");
Promise.promisifyAll(require("restify"));
var restify = require("restify");
var http = require('http');
const PORT=7070;
function handleRequest(request, response){
response.end('It Works!! Path Hit: ' + request.url);
}
var server = http.createServer(handleRequest);
server.listen(PORT, function(){
console.log("Server listening on: http://localhost:%s", PORT);
});
var client = restify.createJsonClientAsync({
url: 'http://127.0.0.1:7070'
});
client.get("/foo").spread(function(req, res, obj) {
console.log(obj);
});
I only put together this simple example to prove it to myself after my production code didn't work. I can hit localhost:7070 with curl and I get the expected results.
In a nutshell: I need to execute 3 GET calls to a server before I can create a POST and hence my need for promises.
Anyone can shed some insight? I can't imagine this being simpler.
UPDATE
Apparently i did not read the question correctly, here is a working example of 2 gets using a promisified restify json client. you would just do another spread in the body of the second spread for your post.
var promise = require('bluebird');
var restify = require('restify');
promise.promisifyAll(restify.JsonClient.prototype);
var client = restify.createJsonClient({
url: 'http://localhost:8080',
version: '*'
});
client.getAsync('/api/resource/1').spread(function(req, res, obj) {
console.log('result 1', obj);
return client.getAsync('/api/resource/2').spread(function(req, res, obj) {
console.log('result 2', obj);
});
});
As I stated in my comments, I would not promisify restify itself. Instead I would use either a handler whose body executes promise code or a chain of handlers (which can also have promises in the body). restify should only receive the request and execute the handler.
I will use modified versions of the basic example from the restify page to illustrate each.
Promise in the message body using knex.js which returns a promise
var knex = require('knex')(connectionConfig);
var restify = require('restify');
function promisePost(req, res, next) {
// get 1
knex.select('*')
.from('table1')
.where('id', '=', req.body.table1_id)
.then(function(result1) {
// get 2
return knex.select('*')
.from('table2')
.where('id', '=', req.body.table2_id)
.then(function(result2) {
return knex('table3').insert({
table1_value: result1.value,
table2_value: result2.value
})
.then(function(result3) {
res.send(result3);
return next();
});
});
});
}
var server = restify.createServer();
server.use(restify.bodyParser());
server.post('/myroute', promisePost);
server.listen(8080, function() {
console.log('%s listening at %s', server.name, server.url);
});
now with chained handlers
var knex = require('knex')(connectionConfig);
var restify = require('restify');
function get1(req, res, next) {
knex.select('*').from('table1')
.where('id', '=', req.body.table1_id)
.then(function(result1) {
res.locals.result1 = result1;
return next();
});
}
function get2(req, res, next) {
knex.select('*').from('table2')
.where('id', '=', req.body.table2_id)
.then(function(result2) {
res.locals.result2 = result2;
return next();
});
}
function post(req, res, next) {
knex('table3').insert({
table1_value: res.locals.result1,
table2_value: res.locals.result2
})
.then(function(result3) {
res.send(result3);
return next();
});
}
var server = restify.createServer();
server.use(restify.bodyParser());
server.post('/myroute', get1, get2, post);
server.listen(8080, function() {
console.log('%s listening at %s', server.name, server.url);
});
I'd like to display the artwork image of a soundcloud track I have the Track-URL from.
I know that when I extract the track key/id from the URL, I can request the artwork URL via their API and then inject the retrieved URL into a image tag.
What I'd like to know is if its possible to use some kind of URL schema to make soundcloud forward browsers to the correct artwork URL like its possible with facebook profile images.
For example:
Mark Zuckerbergs current profile picture has the URL http://profile.ak.fbcdn.net/hprofile-ak-prn2/t5/202896_4_1782288297_q.jpg
Thats some cryptic stuff because its hosted on a CDN. Soundcloud artwork URLs look pretty much cryptic as well.
Now, when I know marks facebook id/key ("zuck"), I can simply access his profile image like so:
http://graph.facebook.com/zuck/picture
That URL is automatically forwarded to the profile picture URL by the facebook API.
Using this URL schema you abstract away not only the reason for a additional API request, but they also safe processing time on their side.
Is there some functionality like this for soundcloud track artworks?
I wrote an express app that redirects to largest available image on their CDN, given artwork_url.
FixSoundCloudArtworkUrl.js
It uses their naming scheme and enumerates sizes one by one until some image returns status 200.
Source:
'use strict';
var express = require('express'),
app = express();
require('./config/development')(app, express);
require('./config/production')(app, express);
var redis = require('redis'),
request = require('request'),
Promise = require('bluebird');
Promise.promisifyAll(redis.RedisClient.prototype);
var redisSettings = app.set('redis'),
redisClient = redis.createClient(redisSettings.port, redisSettings.host, redisSettings.options);
app.configure(function () {
app.use(express.bodyParser());
app.use(app.router);
});
function sendError(res, status, error) {
if (!(error instanceof Error)) {
error = new Error(JSON.stringify(error));
}
return res
.status(status || 500)
.end(error && error.message || 'Internal Server Error');
}
function generateCacheHeaders() {
var maxAge = 3600 * 24 * 365;
return {
'Cache-Control': 'public,max-age=' + maxAge,
'Expires': new Date(Date.now() + (maxAge * 1000)).toUTCString()
};
}
function getCacheKey(url) {
return 'soundcloud-thumbnail-proxy:' + url;
}
app.get('/*', function (req, res) {
var originalUrl = req.params[0],
cacheKey = getCacheKey(originalUrl),
urls;
// https://developers.soundcloud.com/docs/api/reference#artwork_url
// This is a ridiculous naming scheme, by the way.
urls = [
originalUrl,
originalUrl.replace('-large', '-t500x500'),
originalUrl.replace('-large', '-crop'), // 400x400
originalUrl.replace('-large', '-t300x300'),
originalUrl.replace('-large', '-large') // 100x100
];
return redisClient.getAsync(cacheKey).then(function (cachedUrl) {
if (cachedUrl) {
return cachedUrl;
}
return Promise.reduce(urls, function (resolvedUrl, url) {
if (resolvedUrl) {
return resolvedUrl;
}
return new Promise(function (resolve) {
request.head(url, function (err, response) {
if (!err && response.statusCode === 200) {
resolve(url);
} else {
resolve(null);
}
});
});
}, null);
}).then(function (url) {
if (!url) {
throw new Error('File not found');
}
var headers = generateCacheHeaders();
for (var key in headers) {
if (headers.hasOwnProperty(key)) {
res.setHeader(key, headers[key]);
}
}
res.redirect(url);
redisClient.set(cacheKey, url);
redisClient.expire(cacheKey, 60 * 60 * 24 * 30);
}).catch(function (err) {
sendError(res, 404, err);
});
});
app.get('/crossdomain.xml', function (req, res) {
req.setEncoding('utf8');
res.writeHead(200, { 'Content-Type': 'text/xml' });
res.end('<?xml version="1.0" ?><cross-domain-policy><allow-access-from domain="*" /></cross-domain-policy>');
});
redisClient.on('ready', function () {
app.listen(app.set('port'));
});
redisClient.on('error', function () {
throw new Error('Could not connect to Redis');
});
module.exports = app;
No. The only documented way is: API Reference for "/tracks"