'use strict';
|
|
const querystring = require('querystring');
|
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 = /[^\[\]]+\[\]$/;
|
|
module.exports = {
|
/**
|
* Parse the "Host" header field host
|
* and support X-Forwarded-Host when a
|
* proxy is enabled.
|
* @member {String} Request#host
|
* @example
|
* ip + port
|
* ```js
|
* this.request.host
|
* => '127.0.0.1:7001'
|
* ```
|
* or domain
|
* ```js
|
* this.request.host
|
* => 'demo.eggjs.org'
|
* ```
|
*/
|
get host() {
|
if (this[HOST]) return this[HOST];
|
|
let host;
|
if (this.app.config.proxy) {
|
host = getFromHeaders(this, this.app.config.hostHeaders);
|
}
|
host = host || this.get('host') || '';
|
this[HOST] = host.split(/\s*,\s*/)[0];
|
return this[HOST];
|
},
|
|
/**
|
* @member {String} Request#protocol
|
* @example
|
* ```js
|
* this.request.protocol
|
* => 'https'
|
* ```
|
*/
|
get protocol() {
|
if (this[PROTOCOL]) return this[PROTOCOL];
|
// detect encrypted socket
|
if (this.socket && this.socket.encrypted) {
|
this[PROTOCOL] = 'https';
|
return this[PROTOCOL];
|
}
|
// 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];
|
},
|
|
/**
|
* Get all pass through ip addresses from the request.
|
* Enable only on `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 val = getFromHeaders(this, this.app.config.ipHeaders) || '';
|
this[IPS] = val ? val.split(/\s*,\s*/) : [];
|
|
if (this.app.config.maxProxyCount > 0) {
|
// if maxProxyCount present, only keep `maxProxyCount + 1` ips
|
// [ illegalIp, clientRealIp, proxyIp1, proxyIp2 ...]
|
this[IPS] = this[IPS].slice(-(this.app.config.maxProxyCount + 1));
|
}
|
return this[IPS];
|
},
|
|
/**
|
* Get the request remote IPv4 address
|
* @member {String} Request#ip
|
* @return {String} IPv4 address
|
* @example
|
* ```js
|
* this.request.ip
|
* => '127.0.0.1'
|
* => '111.10.2.1'
|
* ```
|
*/
|
get ip() {
|
if (this._ip) {
|
return this._ip;
|
}
|
const ip = this.ips[0] || this.socket.remoteAddress;
|
// will be '::ffff:x.x.x.x', should conver to standard IPv4 format
|
// https://zh.wikipedia.org/wiki/IPv6
|
this._ip = ip && ip.indexOf('::ffff:') > -1 ? ip.substring(7) : ip;
|
return this._ip;
|
},
|
|
/**
|
* Set the request remote IPv4 address
|
* @member {String} Request#ip
|
* @param {String} ip - IPv4 address
|
* @example
|
* ```js
|
* this.request.ip
|
* => '127.0.0.1'
|
* => '111.10.2.1'
|
* ```
|
*/
|
set ip(ip) {
|
this._ip = ip;
|
},
|
|
/**
|
* detect if response should be json
|
* 1. url path ends with `.json`
|
* 2. response type is set to json
|
* 3. detect by request accept header
|
*
|
* @member {Boolean} Request#acceptJSON
|
* @since 1.0.0
|
*/
|
get acceptJSON() {
|
if (this.path.endsWith('.json')) return true;
|
if (this.response.type && this.response.type.indexOf('json') >= 0) return true;
|
if (this.accepts('html', 'text', 'json') === 'json') return true;
|
return false;
|
},
|
|
// How to read query safely
|
// https://github.com/koajs/qs/issues/5
|
_customQuery(cacheName, filter) {
|
const str = this.querystring || '';
|
let c = this[cacheName];
|
if (!c) {
|
c = this[cacheName] = {};
|
}
|
let cacheQuery = c[str];
|
if (!cacheQuery) {
|
cacheQuery = c[str] = {};
|
const isQueries = cacheName === _queriesCache;
|
// `querystring.parse` CANNOT parse something like `a[foo]=1&a[bar]=2`
|
const query = str ? querystring.parse(str) : {};
|
for (const key in query) {
|
if (!key) {
|
// key is '', like `a=b&`
|
continue;
|
}
|
const value = filter(query[key]);
|
cacheQuery[key] = value;
|
if (isQueries && RE_ARRAY_KEY.test(key)) {
|
// `this.queries['key'] => this.queries['key[]']` is compatibly supported
|
const subkey = key.substring(0, key.length - 2);
|
|
if (!cacheQuery[subkey]) {
|
cacheQuery[subkey] = value;
|
}
|
}
|
}
|
}
|
return cacheQuery;
|
},
|
|
/**
|
* get params pass by querystring, all values are of string type.
|
* @member {Object} Request#query
|
* @example
|
* ```js
|
* GET http://127.0.0.1:7001?name=Foo&age=20&age=21
|
* this.query
|
* => { 'name': 'Foo', 'age': '20' }
|
*
|
* GET http://127.0.0.1:7001?a=b&a=c&o[foo]=bar&b[]=1&b[]=2&e=val
|
* this.query
|
* =>
|
* {
|
* "a": "b",
|
* "o[foo]": "bar",
|
* "b[]": "1",
|
* "e": "val"
|
* }
|
* ```
|
*/
|
get query() {
|
return this._customQuery(_querycache, firstValue);
|
},
|
|
/**
|
* get params pass by querystring, all value are Array type. {@link Request#query}
|
* @member {Array} Request#queries
|
* @example
|
* ```js
|
* GET http://127.0.0.1:7001?a=b&a=c&o[foo]=bar&b[]=1&b[]=2&e=val
|
* this.queries
|
* =>
|
* {
|
* "a": ["b", "c"],
|
* "o[foo]": ["bar"],
|
* "b[]": ["1", "2"],
|
* "e": ["val"]
|
* }
|
* ```
|
*/
|
get queries() {
|
return this._customQuery(_queriesCache, arrayValue);
|
},
|
|
get accept() {
|
let accept = this[ACCEPTS];
|
if (accept) {
|
return accept;
|
}
|
accept = this[ACCEPTS] = accepts(this.req);
|
return accept;
|
},
|
|
/**
|
* Set query-string as an object.
|
*
|
* @function Request#query
|
* @param {Object} obj set querystring and query object for request.
|
* @return {void}
|
*/
|
set query(obj) {
|
this.querystring = querystring.stringify(obj);
|
},
|
};
|
|
|
function firstValue(value) {
|
if (Array.isArray(value)) {
|
value = value[0];
|
}
|
return value;
|
}
|
|
function arrayValue(value) {
|
if (!Array.isArray(value)) {
|
value = [ 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 '';
|
}
|