node-req
Version:
I/O parser for nodejs http request
638 lines (590 loc) • 13.4 kB
JavaScript
/**
* node-req
* Copyright(c) 2015-2015 Harminder Virk
* MIT Licensed
*/
const parseurl = require('parseurl')
const qs = require('qs')
const fresh = require('fresh')
const proxyaddr = require('proxy-addr')
const isIP = require('net').isIP
const accepts = require('accepts')
const is = require('type-is')
const compileTrust = function (value) {
/**
* If value is a function, return it right away.
*/
if (typeof (value) === 'function') {
return value
}
/**
* Wrap a boolean true inside a function
* and return true
*/
if (value === true) {
return function () {
return true
}
}
/**
* Wrap number inside a function and perform
* required operations.
*/
if (typeof (value) === 'number') {
return function (a, i) {
return i < value
}
}
/**
* Support comma-separated values
*/
if (typeof value === 'string') {
value = value.split(/ *, */)
}
/**
* Finally let proxyaddr understand
* and compile the input.
*/
return proxyaddr.compile(value || [])
}
/**
* Facade over Node.js http.IncomingMessage request
*
* @module Request
*/
let Request = exports = module.exports = {}
/**
* Parses query string from url an returns an object.
*
* @method get
*
* @param {http.IncomingMessage} req
* @param {Object} [options] Options are passed to https://www.npmjs.com/package/qs
*
* @return {Object}
*
* @example
* ```js
* const queryString = nodeReq.get(req)
* ```
*/
Request.get = (req, options = {}) => qs.parse(parseurl(req).query, options)
/**
* Returns the exact copy of `request.method`. Defined
* [here](https://nodejs.org/api/http.html#http_message_method)
*
* @method method
*
* @param {http.IncomingMessage} req
*
* @return {String}
*
* @example
* ```js
* const method = nodeReq.method(req)
* ```
*/
Request.method = (req) => req.method
/**
* Returns an object of headers for a given
* request.
*
* @method headers
*
* @param {http.IncomingMessage} req
*
* @return {Object}
*
* @example
* ```js
* const headers = nodeReq.headers(req)
* ```
*/
Request.headers = (req) => req.headers
/**
* Returns header value for a given key. Also
* it will handle the inconsistencies between
* `referer` and `referrer` header.
*
* @method header
*
* @param {http.IncomingMessage} req
* @param {String} key
* @return {String}
*
* @example
* ```js
* const authHeader = nodeReq.header(req, 'Authorization')
* ```
*/
Request.header = function (req, key) {
key = key.toLowerCase()
const headers = Request.headers(req)
switch (key) {
case 'referer':
case 'referrer':
return headers.referrer || headers.referer || ''
default:
return headers[key] || ''
}
}
/**
* Returns the freshness of a response inside the client
* cache. If client cache has the latest response, this
* method will return `true`, otherwise it will return
* `false`.
*
* Also when HTTP header `Cache-Control: no-cache` is present
* this method will return false everytime.
*
* @method fresh
*
* @param {http.IncomingMessage} req
* @param {http.ServerResponse} res
*
* @return {Boolean}
*
* @example
* ```js
* if (nodeReq.fresh(req, res)) {
* res.writeHead(304)
* }
* ```
*/
Request.fresh = function (req, res) {
const method = Request.method(req)
const status = res.statusCode
/**
* only for GET and HEAD
*/
if (method !== 'GET' && method !== 'HEAD') {
return false
}
/**
* 2xx or 304 as per rfc2616 14.26
*/
if ((status >= 200 && status < 300) || status === 304) {
const responseHeaders = typeof (res.getHeaders) === 'function' ? res.getHeaders() : (res._headers || {})
return fresh(req.headers, responseHeaders)
}
return false
}
/**
* This method is the opposite of the `nodeReq.fresh`
*
* @method stale
*
* @param {http.IncomingMessage} req
* @param {http.ServerResponse} res
*
* @return {Boolean}
*
* @example
* ```js
* if (!nodeReq.stale(req, res)) {
* res.writeHead(304)
* }
* ```
*/
Request.stale = (req, res) => !Request.fresh(req, res)
/**
* Returns the most trusted ip address for the HTTP
* request. It will handle the use cases where your
* server is behind a proxy.
*
* Make sure to check [proxy-addr](https://www.npmjs.com/package/proxy-addr)
* for the available options for `trust`.
*
* @method ip
*
* @param {http.IncomingMessage} req
* @param {Mixed} [trust]
*
* @return {String}
*
* @example
* ```js
* nodeReq.ip(req, '127.0.0.1')
* nodeReq.ip(req, ['::1/128', 'fe80::/10'])
* ```
*/
Request.ip = (req, trust) => proxyaddr(req, compileTrust(trust))
/**
* Returns list of all remote addresses ordered with
* most trusted on the top of the list.
*
* Make sure to check [proxy-addr](https://www.npmjs.com/package/proxy-addr)
* for the available options for `trust`.
*
* @method ips
*
* @param {http.IncomingMessage} req
* @param {Mixed} [trust]
*
* @return {Array}
*
* @example
* ```
* nodeReq.ips(req, '127.0.0.1')
* nodeReq.ips(req, ['::1/128', 'fe80::/10'])
* ```
*/
Request.ips = function (req, trust) {
const addresses = proxyaddr.all(req, compileTrust(trust))
return addresses.slice(1).reverse()
}
/**
* Returns request protocol based upon encrypted
* connection or X-Forwaded-Proto header.
*
* Make sure to check [proxy-addr](https://www.npmjs.com/package/proxy-addr)
* for the available options for `trust`.
*
* @method protocol
*
* @param {http.IncomingMessage} req
* @param {Mixed} [trust]
*
* @return {String}
*
* @example
* ```
* const protocol = nodeReq.protocol(req)
* ```
*/
Request.protocol = function (req, trust) {
let proto = req.connection.encrypted ? 'https' : 'http'
trust = compileTrust(trust)
if (!trust(req.connection.remoteAddress, 0)) {
return proto
}
proto = Request.header(req, 'X-Forwarded-Proto') || proto
return proto.split(/\s*,\s*/)[0]
}
/**
* Looks for request protocol to check for
* https existence or returns false.
*
* @method secure
*
* @param {http.IncomingMessage} req
*
* @return {Boolean}
*
* @example
* ```
* const isHttps = nodeReq.secure(req)
* ```
*/
Request.secure = (req) => Request.protocol(req) === 'https'
/**
* Returns the request subdomains as an array. Also
* it will make sure to exclude `www` from the
* subdomains list.
*
* Make sure to check [proxy-addr](https://www.npmjs.com/package/proxy-addr)
* for the available options for `trust`.
*
* @method subdomains
*
* @param {http.IncomingMessage} req
* @param {Mixed} [trust]
* @param {Number} [offset = 2] subdomain offset
*
* @return {Array}
*
* @example
* ```js
* const subdomains = nodeReq.subdomains(req)
* ```
*/
Request.subdomains = function (req, trust, offset = 2) {
const hostname = Request.hostname(req, trust)
if (!hostname || isIP(hostname)) {
return []
}
const subdomains = hostname.split('.').reverse().slice(offset)
/**
* remove www if is the last subdomain
* after reverse
*/
if (subdomains[subdomains.length - 1] === 'www') {
subdomains.splice(subdomains.length - 1, 1)
}
return subdomains
}
/**
* Determines whether request is an ajax request
* or not, based on X-Requested-With header.
*
* @method ajax
*
* @param {http.IncomingMessage} req
*
* @return {Boolean}
*
* @example
* ```js
* if (nodeReq.ajax(req)) {
* res.writeHead(200, {"Content-type": "application/json"})
* } else {
* res.writeHead(200, {"Content-type": "text/html"})
* }
* ```
*/
Request.ajax = (req) => {
return (Request.header(req, 'X-Requested-With') || '').toLowerCase() === 'xmlhttprequest'
}
/**
* Tells whether request has X-Pjax
* header or not.
*
* @method pjax
*
* @param {http.IncomingMessage} req
*
* @return {Boolean}
*
* @example
* ```js
* if (nodeReq.pjax(req)) {
* // return partial content
* } else {
* // full page refresh
* }
* ```
*/
Request.pjax = (request) => !!Request.header(request, 'X-Pjax')
/**
* Returns the hostname of HTTP request.
*
* Make sure to check [proxy-addr](https://www.npmjs.com/package/proxy-addr)
* for the available options for `trust`.
*
* @method hostname
*
* @param {http.IncomingMessage} req
* @param {Mixed} [trust]
*
* @return {String}
*
* @example
* ```js
* const hostname = nodeReq.hostname(request)
* ```
*/
Request.hostname = function (request, trust) {
trust = compileTrust(trust)
let host = Request.header(request, 'X-Forwarded-Host')
/**
* grabbing host header if trust proxy is disabled or host
* does not exists on forwared headers
*/
if (!host || !trust(request.connection.remoteAddress, 0)) {
host = Request.header(request, 'Host')
}
if (!host) {
return null
}
/**
* Support for IPv6
*/
const offset = host[0] === '[' ? host.indexOf(']') + 1 : 0
const index = host.indexOf(':', offset)
return index !== -1 ? host.substring(0, index) : host
}
/**
* Returns request url after removing the query
* string.
*
* @method url
*
* @param {http.IncomingMessage} req
*
* @return {String}
*
* @example
* ```js
* const url = nodeReq.url(request)
* ```
*/
Request.url = (req) => parseurl(req).pathname
/**
* Returns the untouched url.
*
* @method originalUrl
*
* @param {http.IncomingMessage} req
*
* @return {String}
*
* @example
* ```js
* const url = nodeReq.originalUrl(request)
* ```
*/
Request.originalUrl = (req) => parseurl(req).href
/**
* Tells whether request accept content of a given
* type or not (based on **Content-type**) header.
*
* @method is
*
* @param {http.IncomingMessage} req
* @param {Mixed} keys
*
* @return {String}
*
* @example
* ```js
* // req.headers.content-type = 'application/json'
*
* nodeReq.is(req, ['json']) // json
* nodeReq.is(req, ['json', 'html']) // json
* nodeReq.is(req, ['application/*']) // application/json
*
* nodeReq.is(req, ['html']) // '<empty string>'
* ```
*/
Request.is = (request, keys) => is(request, keys) || ''
/**
* Return the best possible response accepted by the
* client. This is based on the `Accept` header.
* [Learn more about it](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Accept)
*
* @method accepts
*
* @param {http.IncomingMessage} req
* @param {Mixed} keys
*
* @return {String}
*
* @example
* ```js
* const type = nodeReq.accepts(req, ['json', 'html'])
*
* switch(type) {
* case 'json':
* res.setHeader('Content-Type', 'application/json')
* res.write('{"hello":"world!"}')
* break
*
* case 'html':
* res.setHeader('Content-Type', 'text/html')
* res.write('<b>hello, world!</b>')
* break
*
* default:
* res.setHeader('Content-Type', 'text/plain')
* res.write('hello, world!')
* }
* ```
*/
Request.accepts = (req, keys) => accepts(req).type(keys)
/**
* This method is similar to {{#crossLink "Request/accepts"}}{{/crossLink}},
* instead it will return an array of types from most to least preferred
* one.
*
* @method types
*
* @param {http.IncomingMessage} req
*
* @return {Array}
*/
Request.types = (req) => accepts(req).types()
/**
* Returns one of the most preferrable language.
*
* @method language
*
* @param {http.IncomingMessage} req
* @param {Array} accepted
*
* @return {String}
*/
Request.language = function (req, accepted) {
const acceptedLangs = accepts(req).language(accepted)
return (acceptedLangs instanceof Array) ? acceptedLangs[0] : acceptedLangs
}
/**
* Returns list of all accepted languages from most
* to least preferred one.
*
* @method languages
*
* @param {http.IncomingMessage} req
*
* @return {Array}
*/
Request.languages = (req) => accepts(req).languages()
/**
* Returns the best maching encoding
*
* @method encoding
*
* @param {http.IncomingMessage} req
* @param {Array} accepted
*
* @return {String}
*/
Request.encoding = function (req, accepted) {
const acceptedEncoding = accepts(req).encoding(accepted)
return (acceptedEncoding instanceof Array) ? acceptedEncoding[0] : acceptedEncoding
}
/**
* Returns list of all encodings from most
* to least preferred one.
*
* @method encodings
*
* @param {http.IncomingMessage} req
*
* @return {Array}
*/
Request.encodings = (req) => accepts(req).encodings()
/**
* Returns the best maching charset based upon
* `Accept-Charset` header.
*
* @method charset
*
* @param {http.IncomingMessage} req
* @param {Array} accepted
*
* @return {String}
*/
Request.charset = function (req, accepted) {
const acceptedCharsets = accepts(req).charset(accepted)
return (acceptedCharsets instanceof Array) ? acceptedCharsets[0] : acceptedCharsets
}
/**
* Returns a list of all charsets from most
* to least preferred one based upon
* `Accept-Charset` header.
*
* @method charsets
*
* @param {http.IncomingMessage} req
*
* @return {Array}
*/
Request.charsets = (req) => accepts(req).charsets()
/**
* Tells whether request has body or
* not to be read by any body parser.
*
* @method hasBody
*
* @param {http.IncomingMessage} req
* @return {Boolean}
*
* @example
* ```js
* if (nodeReq.hasBody(request)) {
* // use body parser
* }
* ```
*/
Request.hasBody = (req) => is.hasBody(req)