Skip to content

Commit

Permalink
feat(request): add request.original to access the original request data
Browse files Browse the repository at this point in the history
request body and query string is subjected to be mutated during the HTTP lifecycle. For same reasons
we keep an original frozen copy of request original data
  • Loading branch information
thetutlage committed Oct 16, 2018
1 parent 61a4a36 commit 2d12926
Show file tree
Hide file tree
Showing 2 changed files with 95 additions and 0 deletions.
63 changes: 63 additions & 0 deletions src/Request/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ const nodeCookie = require('node-cookie')
const pathToRegexp = require('path-to-regexp')
const useragent = require('useragent')
const Macroable = require('macroable')
const debug = require('debug')('adonis:request')

const SUBDOMAIN_OFFSET = 'app.http.subdomainOffset'
const TRUST_PROXY = 'app.http.trustProxy'
Expand Down Expand Up @@ -149,8 +150,15 @@ class Request extends Macroable {
* @return {void}
*/
set body (body) {
debug('updated request body')
const hasBody = !!this._body

this._body = body
this._all = _.merge({}, this.get(), body)

if (!hasBody) {
this._updateRequestOriginal()
}
}

/**
Expand All @@ -175,8 +183,50 @@ class Request extends Macroable {
* @return {void}
*/
set qs (qs) {
debug('updated request query string')
const hasQs = !!this._qs

this._qs = qs
this._all = _.merge({}, this.post(), qs)

if (!hasQs) {
this._updateRequestOriginal()
}
}

/**
* Updates the request original payload by tracking the
* amount of mutations made to it. Once `qs` and `body`
* is set for the first time, after that original
* object will be freexed
*
* @method _updateRequestOriginal
*
* @return {void}
*
* @private
*/
_updateRequestOriginal () {
if (Object.isFrozen(this._original)) {
return
}

/**
* Update original value
*/
debug('updated request original data')
this._original = _.merge({}, this._all)

/**
* Once qs and body is stable, we will freeze the original
* object. This is important, since the original request
* body is mutable, however a reference to original is
* must
*/
if (this._qs && this._body) {
debug('freezing request original data')
Object.freeze(this._original)
}
}

/**
Expand Down Expand Up @@ -235,6 +285,19 @@ class Request extends Macroable {
return this.body
}

/**
* Similar to `request.all`, but later mutations are avoided. Use this
* method, when you want to read the values submitted in the original
* HTTP request.
*
* @method original
*
* @return {Object}
*/
original () {
return this._original
}

/**
* Returns an object after merging {{#crossLink "Request/get"}}{{/crossLink}} and
* {{#crossLink "Request/post"}}{{/crossLink}} values
Expand Down
32 changes: 32 additions & 0 deletions test/unit/request.spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -807,4 +807,36 @@ test.group('Request', () => {
assert.deepEqual(res.body.get, { age: '22' })
assert.deepEqual(res.body.post, { username: 'virk' })
})

test('keep a copy of request original data on each request', async (assert) => {
assert.plan(1)

const server = http.createServer((req, res) => {
const request = new Request(req, res, config)
request.body = { username: 'virk' }
assert.deepEqual(request.all(), request.original())
res.writeHead(200, { 'content-type': 'application/json' })
res.end()
})

await supertest(server).post('/').send('username', 'virk').expect(200)
})

test('seal request original after body is set once', async (assert) => {
assert.plan(3)

const server = http.createServer((req, res) => {
const request = new Request(req, res, config)
request.body = { username: 'virk' }
request.body = { username: 'nikk' }
assert.deepEqual(request.all(), { username: 'nikk' })
assert.deepEqual(request.original(), { username: 'virk' })
assert.isTrue(Object.isFrozen(request.original()))

res.writeHead(200, { 'content-type': 'application/json' })
res.end()
})

await supertest(server).post('/').expect(200)
})
})

0 comments on commit 2d12926

Please sign in to comment.