Skip to content

Commit

Permalink
feat: support app.config.proxy to identify app is behind a proxy (#231)
Browse files Browse the repository at this point in the history
* feat: support app.config.proxy to identify app is behind a proxy

* test: fix cookie test
  • Loading branch information
dead-horse authored Jan 11, 2017
1 parent 1c65bde commit 96d5360
Show file tree
Hide file tree
Showing 5 changed files with 113 additions and 54 deletions.
98 changes: 55 additions & 43 deletions app/extend/request.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ const accepts = require('accepts');
const _querycache = Symbol('_querycache');
const _queriesCache = Symbol('_queriesCache');
const PROTOCOL = Symbol('PROTOCOL');
const HOST = Symbol('HOST');
const ACCEPTS = Symbol('ACCEPTS');
const IPS = Symbol('IPS');
const RE_ARRAY_KEY = /[^\[\]]+\[\]$/;
Expand All @@ -29,15 +30,18 @@ module.exports = {
* ```
*/
get host() {
const host = this.get('x-forwarded-host') || this.get('host');
if (!host) {
return 'localhost';
if (this[HOST]) return this[HOST];

let host;
if (this.app.config.proxy) {
host = getFromHeaders(this, this.app.config.hostHeaders);
}
return host.split(/\s*,\s*/)[0];
host = host || this.get('host') || '';
this[HOST] = host.split(/\s*,\s*/)[0];
return this[HOST];
},

/**
* 由于部署在 Nginx 后面,Koa 原始的实现无法取到正确的 protocol
* @member {String} Request#protocol
* @example
* ```js
Expand All @@ -46,28 +50,48 @@ module.exports = {
* ```
*/
get protocol() {
if (this[PROTOCOL]) {
return this[PROTOCOL];
}

if (this[PROTOCOL]) return this[PROTOCOL];
// detect encrypted socket
if (this.socket && this.socket.encrypted) {
this[PROTOCOL] = 'https';
return 'https';
return this[PROTOCOL];
}

if (typeof this.app.config.protocolHeaders === 'string' && this.app.config.protocolHeaders) {
const protocolHeaders = this.app.config.protocolHeaders.split(/\s*,\s*/);
for (const header of protocolHeaders) {
let proto = this.get(header);
if (proto) {
proto = this[PROTOCOL] = proto.split(/\s*,\s*/)[0];
return proto;
}
// get from headers specified in `app.config.protocolHeaders`
if (this.app.config.proxy) {
const proto = getFromHeaders(this, this.app.config.protocolHeaders);
if (proto) {
this[PROTOCOL] = proto.split(/\s*,\s*/)[0];
return this[PROTOCOL];
}
}
// use protocol specified in `app.conig.protocol`
this[PROTOCOL] = this.app.config.protocol || 'http';
return this[PROTOCOL];
},

/**
* 从请求头获取请求中所有设备的 ip 列表,第一个 ip 为请求发起方的 ip
* 只有在 `app.config.proxy = true` 时有效
*
* @member {Array} Request#ips
* @example
* ```js
* this.request.ips
* => ['100.23.1.2', '201.10.10.2']
* ```
*/
get ips() {
if (this[IPS]) return this[IPS];

// return empty array when proxy=false
if (!this.app.config.proxy) {
this[IPS] = [];
return this[IPS];
}

const proto = this[PROTOCOL] = this.app.config.protocol || 'http';
return proto;
const val = getFromHeaders(this, this.app.config.ipHeaders) || '';
this[IPS] = val ? val.split(/\s*,\s*/) : [];
return this[IPS];
},

/**
Expand Down Expand Up @@ -99,28 +123,6 @@ module.exports = {
this._ip = ip;
},

/**
* 从请求头获取所有 ip
* 1. 先从 `X-Forwarded-For` 获取,这个值是从 spanner 传递过来的,如果前置没有 spanner 返回为空
* 2. 再从 `X-Real-IP` 获取,这个值为请求 nginx 的客户端 ip,如果前置是非 spanner 的服务器,那么 ip 可能不准确
*
* @member {String} Request#ips
*/
get ips() {
let ips = this[IPS];
if (ips) {
return ips;
}

// TODO: should trust these headers after trust proxy config set
const val = this.get('x-forwarded-for') || this.get('x-real-ip');
ips = this[IPS] = val
? val.split(/ *, */)
: [];

return ips;
},

/**
* 判断当前请求是否 AJAX 请求, 具体判断规则:
* - HTTP 包含 `X-Requested-With: XMLHttpRequest` header
Expand Down Expand Up @@ -269,3 +271,13 @@ function arrayValue(value) {
}
return value;
}

function getFromHeaders(ctx, names) {
if (!names) return '';
names = names.split(/\s*,\s*/);
for (const name of names) {
const value = ctx.get(name);
if (value) return value;
}
return '';
}
31 changes: 28 additions & 3 deletions config/config.default.js
Original file line number Diff line number Diff line change
Expand Up @@ -30,12 +30,37 @@ module.exports = appInfo => {
keys: '',

/**
* Detect request, protocol header, case sensitive.
* If your app behind a proxy, like nginx, maybe you should set it to `x-forwarded-proto`
* is application deployed after reverse proxy
* when true proxy header fields will be trusted
* @member Config#proxy
* @since 1.0.0
* @type {Boolean}
*/
proxy: false,

/**
* Detect request' protocol from specified headers, not case-sensitive.
* Only worked when config.proxy set to true.
* @member {String} Config#protocolHeaders
* @since 1.0.0
*/
protocolHeaders: '',
protocolHeaders: 'x-forwarded-proto',

/**
* Detect request' ip from specified headers, not case-sensitive.
* Only worked when config.proxy set to true.
* @member {String} Config#ipHeaders
* @since 1.0.0
*/
ipHeaders: 'x-forwarded-for',

/**
* Detect request' host from specified headers, not case-sensitive.
* Only worked when config.proxy set to true.
* @member {String} Config#hostHeaders
* @since 1.0.0
*/
hostHeaders: 'x-forwarded-host',

/**
* package.json object
Expand Down
33 changes: 28 additions & 5 deletions test/app/extend/request.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -29,16 +29,24 @@ describe('test/app/extend/request.test.js', () => {
req.hostname.should.equal('foo.com');
});

it('should return "localhost" when no host present', function* () {
it('should return "" when no host present', function* () {
req.host.should.be.a.String;
req.host.should.equal('localhost');
req.host.should.equal('');
});

it('should return host from X-Forwarded-Host header', function* () {
mm(req.header, 'x-forwarded-host', 'foo.com');
req.host.should.be.a.String;
req.host.should.equal('foo.com');
});

it('should return host from Host header when proxy=false', function* () {
mm(app.config, 'proxy', false);
mm(req.header, 'x-forwarded-host', 'foo.com');
mm(req.header, 'host', 'bar.com');
req.host.should.be.a.String;
req.host.should.equal('bar.com');
});
});

describe('req.hostname', () => {
Expand All @@ -47,9 +55,9 @@ describe('test/app/extend/request.test.js', () => {
req.hostname.should.equal('foo.com');
});

it('should return "localhost" when no host present', function* () {
it('should return "" when no host present', function* () {
req.hostname.should.be.a.String;
req.hostname.should.equal('localhost');
req.hostname.should.equal('');
});
});

Expand All @@ -68,6 +76,7 @@ describe('test/app/extend/request.test.js', () => {
});

it('should used x-real-ip', function* () {
mm(app.config, 'ipHeaders', 'X-Forwarded-For, X-Real-IP');
mm(req.header, 'x-forwarded-for', '');
mm(req.header, 'x-real-ip', '127.0.0.1,127.0.0.2');
req.ips.should.eql([ '127.0.0.1', '127.0.0.2' ]);
Expand All @@ -78,12 +87,18 @@ describe('test/app/extend/request.test.js', () => {
mm(req.header, 'x-real-ip', '');
req.ips.should.eql([]);
});

it('should return [] when proxy=false', function* () {
mm(app.config, 'proxy', false);
mm(req.header, 'x-forwarded-for', '127.0.0.1,127.0.0.2');
req.ips.should.eql([]);
});
});

describe('req.protocol', () => {

it('should return http when it not config and no protocol header', () => {
mm(app.config, 'protocl', null);
mm(app.config, 'protocol', null);
return request(app.callback())
.get('/protocol')
.expect('http');
Expand Down Expand Up @@ -112,6 +127,14 @@ describe('test/app/extend/request.test.js', () => {
.expect('https');
});

it('should ignore X-Forwarded-Proto when proxy=false', () => {
mm(app.config, 'proxy', false);
return request(app.callback())
.get('/protocol')
.set('x-forwarded-proto', 'https')
.expect('http');
});

it('should ignore X-Forwarded-Proto', () => {
mm(app.config, 'protocolHeaders', '');
return request(app.callback())
Expand Down
2 changes: 1 addition & 1 deletion test/fixtures/apps/demo/config/config.default.js
Original file line number Diff line number Diff line change
@@ -1,2 +1,2 @@
exports.keys = 'foo';
exports.protocolHeaders = 'X-Forwarded-Proto';
exports.proxy = true;
3 changes: 1 addition & 2 deletions test/fixtures/apps/secure-app/config/config.default.js
Original file line number Diff line number Diff line change
@@ -1,3 +1,2 @@
exports.keys = 'foo';

exports.protocolHeaders = 'X-Forwarded-Proto';
exports.proxy = true;

0 comments on commit 96d5360

Please sign in to comment.