koa-even-better-http-proxy
Version:
http proxy middleware for Koa
368 lines (315 loc) • 10.3 kB
JavaScript
import assert from 'assert';
import Koa from 'koa';
import { agent } from 'supertest';
import proxy from '..';
function proxyTarget(port) {
const other = new Koa();
other.use((ctx, next) => {
if (ctx.request.url !== '/json') {
return next();
}
ctx.set('content-type', 'application/json');
ctx.body = JSON.stringify({ foo: 'bar' });
});
other.use((ctx) => {
ctx.status = 200;
ctx.set('x-wombat-alliance', 'mammels');
ctx.set('x-custom-header', 'something');
ctx.body = 'Success';
});
return other.listen(port);
}
function gzipProxyTarget(port) {
const other = new Koa();
other.use((ctx) => {
ctx.status = 200;
ctx.set('content-type', 'application/octet-stream');
ctx.set('content-encoding', 'gzip');
ctx.body = '';
});
return other.listen(port);
}
describe('userResDecorator', () => {
let other;
beforeEach(() => {
other = proxyTarget(8080);
});
afterEach(() => {
other.close();
});
it('has access to original response', (done) => {
const app = new Koa();
app.use(
proxy('http://localhost', {
port: 8080,
userResDecorator(proxyRes, proxyResData) {
assert(proxyRes.connection);
assert(proxyRes.socket);
assert(proxyRes.headers);
assert(proxyRes.headers['content-type']);
return proxyResData;
},
})
);
agent(app.callback()).get('/').end(done);
});
it('works with promises', (done) => {
const app = new Koa();
app.use(
proxy('http://localhost', {
port: 8080,
userResDecorator(proxyRes, proxyResData) {
return new Promise((resolve) => {
proxyResData.funkyMessage = 'oi io oo ii';
setTimeout(() => {
resolve(proxyResData);
}, 200);
});
},
})
);
agent(app.callback())
.get('/')
.end((err, res) => {
if (err) {
return done(err);
}
assert((res.body.funkyMessage = 'oi io oo ii'));
done();
});
});
it('can modify the response data', (done) => {
const app = new Koa();
app.use(
proxy('http://localhost', {
port: 8080,
userResDecorator(proxyRes, proxyResData) {
proxyResData = JSON.parse(proxyResData.toString('utf8'));
proxyResData.intercepted = true;
return JSON.stringify(proxyResData);
},
})
);
agent(app.callback())
.get('/json')
.end((err, res) => {
if (err) {
return done(err);
}
assert(res.body.intercepted);
done();
});
});
it('can filter response headers', (done) => {
const proxiedApp = new Koa();
const app = new Koa();
let p1Done;
let p2Done;
const p1 = new Promise((resolve) => {
p1Done = resolve;
});
const p2 = new Promise((resolve) => {
p2Done = resolve;
});
app.use(
proxy('http://localhost', {
port: 8080,
})
);
proxiedApp.use(
proxy('http://localhost', {
port: 8080,
strippedHeaders: ['x-wombat-alliance', 'x-custom-header'],
})
);
agent(app.callback())
.get('/')
.end((err, res) => {
if (err) {
return done(err);
}
assert(typeof res.headers['x-custom-header'] === 'string');
assert(typeof res.headers['x-wombat-alliance'] === 'string');
p1Done();
});
agent(proxiedApp.callback())
.get('/')
.end((err, res) => {
if (err) {
return done(err);
}
assert(typeof res.headers['x-custom-header'] !== 'string');
assert(typeof res.headers['x-wombat-alliance'] !== 'string');
p2Done();
});
Promise.all([p1, p2]).then(() => {
done();
});
});
it('can modify the response headers', (done) => {
const app = new Koa();
app.use(
proxy('http://localhost', {
port: 8080,
userResHeadersDecorator(headers) {
const newHeaders = Object.keys(headers).reduce(
(result, key) => {
result[key] = headers[key];
return result;
},
{}
);
newHeaders['x-transaction-id'] = '12345';
newHeaders['x-entity-id'] = 'abcdef';
return newHeaders;
},
})
);
agent(app.callback())
.get('/ip')
.end((err, res) => {
if (err) {
return done(err);
}
assert(res.headers['x-transaction-id'] === '12345');
assert(res.headers['x-entity-id'] === 'abcdef');
done();
});
});
it('can mutuate an html response', (done) => {
const app = new Koa();
app.use(
proxy('http://localhost', {
port: 8080,
userResDecorator(rsp, data) {
data = data
.toString()
.replace('Success', '<strong>Hey</strong>');
assert(data !== '');
return data;
},
})
);
agent(app.callback())
.get('/')
.end((err, res) => {
if (err) {
return done(err);
}
assert(res.text.indexOf('<strong>Hey</strong>') > -1);
done();
});
});
it('can change the location of a redirect', (done) => {
function redirectingServer(port, origin) {
const app = new Koa();
app.use((ctx) => {
ctx.redirect(`${origin}/proxied/redirect/url`);
});
return app.listen(port);
}
const redirectingServerPort = 8012;
const redirectingServerOrigin = [
'http://localhost',
redirectingServerPort,
].join(':');
const server = redirectingServer(
redirectingServerPort,
redirectingServerOrigin
);
const proxyApp = new Koa();
const preferredPort = 3000;
proxyApp.use(
proxy(redirectingServerOrigin, {
userResDecorator(rsp, data, ctx) {
const proxyReturnedLocation = ctx.response.headers.location;
ctx.set(
'location',
proxyReturnedLocation.replace(
redirectingServerPort,
preferredPort
)
);
return data;
},
})
);
agent(proxyApp.callback())
.get('/')
.expect((res) => {
res.headers.location.match(/localhost:3000/);
})
.end(() => {
server.close();
done();
});
});
});
describe('userResDecorator Gzip compression handling', () => {
let other;
beforeEach(() => {
other = gzipProxyTarget(9090);
});
afterEach(() => {
other.close();
});
it('properly handles an empty gzipped HEAD response from proxy', function(done) {
const app = new Koa();
app.use(proxy('localhost', {
port: 9090
}));
agent(app.callback()).head('/').end((err, res) => {
if (err) {
return done(err);
}
assert(res.statusCode === 200);
assert(res.headers['content-encoding'] === 'gzip');
done();
});
});
it('properly handles an empty gzipped GET response from proxy', function(done) {
const app = new Koa();
app.use(proxy('localhost', {
port: 9090
}));
agent(app.callback()).get('/').end((err, res) => {
if (err) {
return done(err);
}
assert(res.statusCode === 200);
assert(res.headers['content-encoding'] === 'gzip');
done();
});
});
});
describe('test userResDecorator on html response from github', () => {
/*
Github provided a unique situation where the encoding was different than
utf-8 when we didn't explicitly ask for utf-8. This test helped sort out
the issue, and even though its a little too on the nose for a specific
case, it seems worth keeping around to ensure we don't regress on this
issue.
*/
it('is able to read and manipulate the response', function (done) {
this.timeout(15000); // give it some extra time to get response
const app = new Koa();
app.use(
proxy('https://github.com/villadora/express-http-proxy', {
userResDecorator(targetResponse, data) {
data = data.toString().replace('DOCTYPE', 'WINNING');
assert(data !== '');
return data;
},
})
);
agent(app.callback())
.get('/html')
.end((err, res) => {
if (err) {
return done(err);
}
assert(res.text.indexOf('WINNING') > -1);
done();
});
});
});