adonis-framework
Version:
Adonis framework makes it easy for you to write webapps with less code
702 lines (642 loc) • 15.2 kB
JavaScript
'use strict'
/**
* adonis-framework
*
* (c) Harminder Virk <virk@adonisjs.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
const nodeReq = require('node-req')
const nodeCookie = require('node-cookie')
const File = require('../File')
const pathToRegexp = require('path-to-regexp')
const _ = require('lodash')
const util = require('../../lib/util')
let configInstance = null
/**
* Glued http request object to read values for
* a given request. Instance of this class
* is generated automatically on every
* new request.
* @class
*/
class Request {
constructor (request, response) {
this.request = request
this.response = response
this._body = {}
this._files = []
/**
* secret to parse and decrypt cookies
* @type {String}
*/
this.secret = configInstance.get('app.appKey')
/**
* holding references to cookies once they
* have been parsed. It is required to
* optimize performance as decrypting
* is an expensive operation
* @type {Object}
*/
this.cookiesObject = {}
/**
* flag to find whether cookies have been
* parsed once or not
* @type {Boolean}
*/
this.parsedCookies = false
}
/**
* returns input value for a given key from post
* and get values.
*
* @param {String} key - Key to return value for
* @param {Mixed} defaultValue - default value to return when actual
* value is empty
* @return {Mixed}
*
* @example
* request.input('name')
* request.input('profile.name')
*
* @public
*/
input (key, defaultValue) {
defaultValue = util.existy(defaultValue) ? defaultValue : null
const input = this.all()
const value = _.get(input, key)
return util.existy(value) ? value : defaultValue
}
/**
* returns merged values from get and post methods.
*
* @return {Object}
*
* @public
*/
all () {
return _.merge(this.get(), this.post())
}
/**
* returns all input values except defined keys
*
* @param {Mixed} keys an array of keys or multiple keys to omit values for
* @return {Object}
*
* @example
* request.except('password', 'credit_card')
* request.except(['password', 'credit_card'])
*
* @public
*/
except () {
const args = _.isArray(arguments[0]) ? arguments[0] : _.toArray(arguments)
return _.omit(this.all(), args)
}
/**
* returns all input values for defined keys only
*
* @param {Mixed} keys an array of keys or multiple keys to pick values for
* @return {Object}
*
* @example
* request.only('name', 'email')
* request.only(['name', 'name'])
*
* @public
*/
only () {
const args = _.isArray(arguments[0]) ? arguments[0] : _.toArray(arguments)
return _.pick(this.all(), args)
}
/**
* returns a group of objects with defined keys and values
* corresponding to them. It is helpful when accepting
* an array of values via form submission.
*
* @param {Mixed} keys an array of keys or multiple keys to pick values for
* @return {Array}
*
* @example
* request.collect('name', 'email')
* request.collect(['name', 'email'])
*
* @public
*/
collect () {
const args = _.isArray(arguments[0]) ? arguments[0] : _.toArray(arguments)
const selectedValues = this.only(args)
/**
* need to make sure the values array is in balance to the expected
* array. Otherwise map method will pickup values for wrong keys.
*/
if (_.size(args) > _.size(selectedValues)) {
args.forEach((key) => { selectedValues[key] = selectedValues[key] || [] })
}
const keys = _.keys(selectedValues)
const values = _.zip.apply(_, _.values(selectedValues))
return _.map(values, (item, index) => {
const group = {}
_.each(args, (k, i) => { group[keys[i]] = item[i] || null })
return group
})
}
/**
* returns query parameters from request querystring
*
* @return {Object}
*
* @public
*/
get () {
return nodeReq.get(this.request)
}
/**
* returns post body from request, BodyParser
* middleware needs to be enabled for this to work
*
* @return {Object}
*
* @public
*/
post () {
return this._body || {}
}
/**
* returns header value for a given key
*
* @param {String} key
* @param {Mixed} defaultValue - default value to return when actual
* value is undefined or null
* @return {Mixed}
*
* @example
* request.header('Authorization')
*
* @public
*/
header (key, defaultValue) {
defaultValue = util.existy(defaultValue) ? defaultValue : null
const headerValue = nodeReq.header(this.request, key)
return util.existy(headerValue) ? headerValue : defaultValue
}
/**
* returns all request headers from a given request
*
* @return {Object}
*
* @public
*/
headers () {
return nodeReq.headers(this.request)
}
/**
* tells whether request is fresh or not by
* checking Etag and expires header
*
* @return {Boolean}
*
* @public
*/
fresh () {
return nodeReq.fresh(this.request, this.response)
}
/**
* opposite of fresh
*
* @see fresh
*
* @return {Boolean}
*
* @public
*/
stale () {
return nodeReq.stale(this.request, this.response)
}
/**
* returns most trusted ip address for a given request. Proxy
* headers are trusted only when app.http.trustProxy is
* enabled inside config file.
*
* @uses app.http.subdomainOffset
*
* @return {String}
*
* @public
*/
ip () {
return nodeReq.ip(this.request, configInstance.get('app.http.trustProxy'))
}
/**
* returns an array of ip addresses sorted from most to
* least trusted. Proxy headers are trusted only when
* app.http.trustProxy is enabled inside config file.
*
* @uses app.http.subdomainOffset
*
* @return {Array}
*
* @public
*/
ips () {
return nodeReq.ips(this.request, configInstance.get('app.http.trustProxy'))
}
/**
* tells whether request is on https or not
*
* @return {Boolean}
*
* @public
*/
secure () {
return nodeReq.secure(this.request)
}
/**
* returns an array of subdomains from url. Proxy headers
* are trusted only when app.http.trustProxy is enabled
* inside config file.
*
* @uses app.http.subdomainOffset
* @uses app.http.trustProxy
*
* @return {Array}
*
* @public
*/
subdomains () {
return nodeReq.subdomains(this.request, configInstance.get('app.http.trustProxy'), configInstance.get('app.http.subdomainOffset'))
}
/**
* tells whether request is an ajax request or not
*
* @return {Boolean}
*
* @public
*/
ajax () {
return nodeReq.ajax(this.request)
}
/**
* tells whether request is pjax or
* not based on X-PJAX header
*
* @return {Boolean}
*
* @public
*/
pjax () {
return nodeReq.pjax(this.request)
}
/**
* returns request hostname
*
* @uses app.http.subdomainOffset
*
* @return {String}
*
* @public
*/
hostname () {
return nodeReq.hostname(this.request, configInstance.get('app.http.trustProxy'))
}
/**
* returns request url without query string
*
* @return {String}
*
* @public
*/
url () {
return nodeReq.url(this.request)
}
/**
* returns request original Url with query string
*
* @return {String}
*
* @public
*/
originalUrl () {
return nodeReq.originalUrl(this.request)
}
/**
* tells whether request is of certain type
* based upon Content-type header
*
* @return {Boolean}
*
* @example
* request.is('text/html', 'text/plain')
* request.is(['text/html', 'text/plain'])
*
* @public
*/
is () {
const args = _.isArray(arguments[0]) ? arguments[0] : _.toArray(arguments)
return nodeReq.is(this.request, args)
}
/**
* returns the best response type to be accepted using Accepts header
*
* @return {String}
*
* @example
* request.accepts('text/html', 'application/json')
* request.accepts(['text/html', 'application/json'])
*
* @public
*/
accepts () {
const args = _.isArray(arguments[0]) ? arguments[0] : _.toArray(arguments)
return nodeReq.accepts(this.request, args)
}
/**
* returns request method or verb in HTTP terms
*
* @return {String}
*
* @public
*/
method () {
if (!configInstance.get('app.http.allowMethodSpoofing')) {
return nodeReq.method(this.request)
}
return this.input('_method', this.intended())
}
/**
* Returns the original HTTP request method, regardless
* of the spoofing input.
*
* @returns {String}
*/
intended () {
return nodeReq.method(this.request)
}
/**
* returns cookie value for a given key
*
* @param {String} key - Key for which value should be returnd
* @param {Mixed} defaultValue - default value to return when actual
* value is undefined or null
* @return {Mixed}
*
* @public
*/
cookie (key, defaultValue) {
defaultValue = util.existy(defaultValue) ? defaultValue : null
const cookies = this.cookies()
return util.existy(cookies[key]) ? cookies[key] : defaultValue
}
/**
* returns all cookies associated to a given request
*
* @return {Object}
*
* @public
*/
cookies () {
const secret = this.secret || null
const decrypt = !!this.secret
/**
* avoiding re-parsing of cookies if done once
*/
if (!this.parsedCookies) {
this.cookiesObject = nodeCookie.parse(this.request, secret, decrypt)
this.parsedCookies = true
}
return this.cookiesObject
}
/**
* Returns an object of plain cookies without decrypting
* or unsigning them. It is required and helpful when
* want to read cookies not set by AdonisJs.
*
* @return {Object}
*/
plainCookies () {
return nodeCookie.parse(this.request)
}
/**
* Returns plain cookie value without decrypting or
* unsigning it. It is required and helpful when
* want to read cookies not set by AdonisJs.
*
* @param {String} key
* @param {Mixed} [defaultValue]
*
* @return {Mixed}
*/
plainCookie (key, defaultValue) {
defaultValue = util.existy(defaultValue) ? defaultValue : null
const cookies = this.plainCookies()
return util.existy(cookies[key]) ? cookies[key] : defaultValue
}
/**
* return route param value for a given key
*
* @param {String} key - key for which the value should be return
* @param {Mixed} defaultValue - default value to be returned with actual
* is null or undefined
* @return {Mixed}
*
* @public
*/
param (key, defaultValue) {
defaultValue = util.existy(defaultValue) ? defaultValue : null
return util.existy(this.params()[key]) ? this.params()[key] : defaultValue
}
/**
* returns all route params
*
* @return {Object}
*
* @public
*/
params () {
return this._params || {}
}
/**
* converts a file object to file instance
* if already is not an instance
*
* @param {Object} file
* @param {Object} [options]
* @return {Object}
* @private
*/
_toFileInstance (file, options) {
if (!(file instanceof File)) {
file = new File(file, options)
}
return file
}
/**
* returns uploaded file instance for a given key
* @instance Request.file
*
* @param {String} key
* @param {Objecr} [options]
* @return {Object}
*
* @example
* request.file('avatar')
* @public
*/
file (key, options) {
/**
* if requested file was not uploaded return an
* empty instance of file object.
*/
if (!this._files[key]) {
return null
}
/**
* grabbing file from uploaded files and
* converting them to file instance
*/
const fileToReturn = this._files[key]
/**
* if multiple file upload , convert them to
* file instances
*/
if (_.isArray(fileToReturn)) {
return _.map(fileToReturn, (file) => this._toFileInstance(file.toJSON(), options))
}
return this._toFileInstance(fileToReturn.toJSON(), options)
}
/**
* returns all uploded files by converting
* them to file instances
*
* @return {Array}
*
* @public
*/
files () {
return _.map(this._files, (file, index) => {
return this.file(index)
})
}
/**
* tells whether a given pattern matches the current url or not
*
* @param {String} pattern
* @return {Boolean}
*
* @example
* request.match('/user/:id', 'user/(+.)')
* request.match(['/user/:id', 'user/(+.)'])
*
* @public
*/
match () {
const args = _.isArray(arguments[0]) ? arguments[0] : _.toArray(arguments)
const url = this.url()
const pattern = pathToRegexp(args, [])
return pattern.test(url)
}
/**
* returns request format enabled by using
* .formats on routes
*
* @return {String}
*
* @example
* request.format()
*
* @public
*/
format () {
return this.param('format') ? this.param('format').replace('.', '') : null
}
/**
* tells whether or not request has body. It can be
* used by bodyParsers to decide whether or not to parse body
*
* @return {Boolean}
*
* @public
*/
hasBody () {
return nodeReq.hasBody(this.request)
}
/**
* Returns the request language set via Accept-Language
* header.
*
* @param {Array} [languages]
*
* @return {String}
*/
language (languages) {
return nodeReq.language(this.request, languages)
}
/**
* Returns list of request languages set via Accept-Language
* header.
*
* @return {Array}
*/
languages () {
return nodeReq.languages(this.request)
}
/**
* Returns the request encoding set via Accept-Encoding
* header.
*
* @param {Array} [encodings]
*
* @return {String}
*/
encoding (encodings) {
return nodeReq.encoding(this.request, encodings)
}
/**
* Returns list of request encodings set via Accept-Encoding
* header.
*
* @return {Array}
*/
encodings () {
return nodeReq.encodings(this.request)
}
/**
* Returns the request charset set via Accept-Charset
* header.
*
* @param {Array} [encodings]
*
* @return {String}
*/
charset (charsets) {
return nodeReq.charset(this.request)
}
/**
* Returns list of request charsets set via Accept-Charset
* header.
*
* @return {Array}
*/
charsets () {
return nodeReq.charsets(this.request)
}
/**
* adds a new method to the request prototype
*
* @param {String} name
* @param {Function} callback
*
* @public
*/
static macro (name, callback) {
this.prototype[name] = callback
}
}
class RequestBuilder {
constructor (Config) {
configInstance = Config
return Request
}
}
module.exports = RequestBuilder