Skip to content

Commit a67e09d

Browse files
committed
parsing socket
1 parent 2064b80 commit a67e09d

File tree

11 files changed

+806
-565
lines changed

11 files changed

+806
-565
lines changed

benchmark/benchmark.js

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@ const mockCustomReq = {
2828
},
2929
Request: http.IncomingMessage
3030
}
31+
3132
const mockReqCookies = {
3233
url: 'http://localhost',
3334
method: 'GET',
@@ -59,7 +60,7 @@ suite
5960
new Request(mockReq) // eslint-disable-line no-new
6061
})
6162
.add('Custom Request', function () {
62-
new Request.CustomRequest(mockCustomReq) // eslint-disable-line no-new
63+
new (Request.getCustomRequest(mockCustomReq.Request))(mockCustomReq) // eslint-disable-line no-new
6364
})
6465
.add('Request With Cookies', function () {
6566
new Request(mockReqCookies) // eslint-disable-line no-new

index.js

Lines changed: 7 additions & 143 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,9 @@
11
'use strict'
22

3-
const assert = require('assert')
43
const Request = require('./lib/request')
5-
const Response = require('./lib/response')
6-
7-
const errorMessage = 'The dispatch function has already been invoked'
8-
9-
const optsValidator = require('./lib/config-validator')
4+
const { Response } = require('./lib/response')
5+
const Chain = require('./lib/chain')
6+
const doInject = require('./lib/do-inject')
107

118
function inject (dispatchFunc, options, callback) {
129
if (typeof callback === 'undefined') {
@@ -16,143 +13,6 @@ function inject (dispatchFunc, options, callback) {
1613
}
1714
}
1815

19-
function makeRequest (dispatchFunc, server, req, res) {
20-
req.once('error', function (err) {
21-
if (this.destroyed) res.destroy(err)
22-
})
23-
24-
req.once('close', function () {
25-
if (this.destroyed && !this._error) res.destroy()
26-
})
27-
28-
return req.prepare(() => dispatchFunc.call(server, req, res))
29-
}
30-
31-
function doInject (dispatchFunc, options, callback) {
32-
options = (typeof options === 'string' ? { url: options } : options)
33-
34-
if (options.validate !== false) {
35-
assert(typeof dispatchFunc === 'function', 'dispatchFunc should be a function')
36-
const isOptionValid = optsValidator(options)
37-
if (!isOptionValid) {
38-
throw new Error(optsValidator.errors.map(e => e.message))
39-
}
40-
}
41-
42-
const server = options.server || {}
43-
44-
const RequestConstructor = options.Request
45-
? Request.CustomRequest
46-
: Request
47-
48-
// Express.js detection
49-
if (dispatchFunc.request && dispatchFunc.request.app === dispatchFunc) {
50-
Object.setPrototypeOf(Object.getPrototypeOf(dispatchFunc.request), RequestConstructor.prototype)
51-
Object.setPrototypeOf(Object.getPrototypeOf(dispatchFunc.response), Response.prototype)
52-
}
53-
54-
if (typeof callback === 'function') {
55-
const req = new RequestConstructor(options)
56-
const res = new Response(req, callback)
57-
58-
return makeRequest(dispatchFunc, server, req, res)
59-
} else {
60-
return new Promise((resolve, reject) => {
61-
const req = new RequestConstructor(options)
62-
const res = new Response(req, resolve, reject)
63-
64-
makeRequest(dispatchFunc, server, req, res)
65-
})
66-
}
67-
}
68-
69-
function Chain (dispatch, option) {
70-
if (typeof option === 'string') {
71-
this.option = { url: option }
72-
} else {
73-
this.option = Object.assign({}, option)
74-
}
75-
76-
this.dispatch = dispatch
77-
this._hasInvoked = false
78-
this._promise = null
79-
80-
if (this.option.autoStart !== false) {
81-
process.nextTick(() => {
82-
if (!this._hasInvoked) {
83-
this.end()
84-
}
85-
})
86-
}
87-
}
88-
89-
const httpMethods = [
90-
'delete',
91-
'get',
92-
'head',
93-
'options',
94-
'patch',
95-
'post',
96-
'put',
97-
'trace'
98-
]
99-
100-
httpMethods.forEach(method => {
101-
Chain.prototype[method] = function (url) {
102-
if (this._hasInvoked === true || this._promise) {
103-
throw new Error(errorMessage)
104-
}
105-
this.option.url = url
106-
this.option.method = method.toUpperCase()
107-
return this
108-
}
109-
})
110-
111-
const chainMethods = [
112-
'body',
113-
'cookies',
114-
'headers',
115-
'payload',
116-
'query'
117-
]
118-
119-
chainMethods.forEach(method => {
120-
Chain.prototype[method] = function (value) {
121-
if (this._hasInvoked === true || this._promise) {
122-
throw new Error(errorMessage)
123-
}
124-
this.option[method] = value
125-
return this
126-
}
127-
})
128-
129-
Chain.prototype.end = function (callback) {
130-
if (this._hasInvoked === true || this._promise) {
131-
throw new Error(errorMessage)
132-
}
133-
this._hasInvoked = true
134-
if (typeof callback === 'function') {
135-
doInject(this.dispatch, this.option, callback)
136-
} else {
137-
this._promise = doInject(this.dispatch, this.option)
138-
return this._promise
139-
}
140-
}
141-
142-
Object.getOwnPropertyNames(Promise.prototype).forEach(method => {
143-
if (method === 'constructor') return
144-
Chain.prototype[method] = function (...args) {
145-
if (!this._promise) {
146-
if (this._hasInvoked === true) {
147-
throw new Error(errorMessage)
148-
}
149-
this._hasInvoked = true
150-
this._promise = doInject(this.dispatch, this.option)
151-
}
152-
return this._promise[method](...args)
153-
}
154-
})
155-
15616
function isInjection (obj) {
15717
return (
15818
obj instanceof Request ||
@@ -165,3 +25,7 @@ module.exports = inject
16525
module.exports.default = inject
16626
module.exports.inject = inject
16727
module.exports.isInjection = isInjection
28+
module.exports.errors = {
29+
...Request.errors,
30+
...Response.errors
31+
}

lib/chain.js

Lines changed: 107 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,107 @@
1+
'use strict'
2+
3+
const doInject = require('./do-inject')
4+
5+
const errorMessage = 'The dispatch function has already been invoked'
6+
7+
class Chain {
8+
_hasInvoked = false
9+
_promise = null
10+
option
11+
dispatch
12+
13+
constructor (dispatch, option) {
14+
this.dispatch = dispatch
15+
if (typeof option === 'string') {
16+
this.option = { url: option }
17+
} else {
18+
this.option = Object.assign({}, option)
19+
}
20+
21+
if (this.option.autoStart !== false) {
22+
process.nextTick(() => {
23+
if (!this._hasInvoked) {
24+
this.end()
25+
}
26+
})
27+
}
28+
}
29+
30+
/**
31+
* @private
32+
* @param {string} method
33+
* @param {string} url
34+
*/
35+
wrapHttpMethod (method, url) {
36+
if (this._hasInvoked === true || this._promise) {
37+
throw new Error(errorMessage)
38+
}
39+
this.option.url = url
40+
this.option.method = method.toUpperCase()
41+
return this
42+
}
43+
44+
delete (url) { return this.wrapHttpMethod('delete', url) }
45+
get (url) { return this.wrapHttpMethod('get', url) }
46+
head (url) { return this.wrapHttpMethod('head', url) }
47+
options (url) { return this.wrapHttpMethod('options', url) }
48+
patch (url) { return this.wrapHttpMethod('patch', url) }
49+
post (url) { return this.wrapHttpMethod('post', url) }
50+
put (url) { return this.wrapHttpMethod('put', url) }
51+
trace (url) { return this.wrapHttpMethod('trace', url) }
52+
53+
/**
54+
* @private
55+
* @param {string} method
56+
* @param {string} url
57+
*/
58+
wrapChainMethod (method, value) {
59+
if (this._hasInvoked === true || this._promise) {
60+
throw new Error(errorMessage)
61+
}
62+
this.option[method] = value
63+
return this
64+
}
65+
66+
body (url) { return this.wrapChainMethod('body', url) }
67+
cookies (url) { return this.wrapChainMethod('cookies', url) }
68+
headers (url) { return this.wrapChainMethod('headers', url) }
69+
payload (url) { return this.wrapChainMethod('payload', url) }
70+
query (url) { return this.wrapChainMethod('query', url) }
71+
72+
end (callback) {
73+
if (this._hasInvoked === true || this._promise) {
74+
throw new Error(errorMessage)
75+
}
76+
this._hasInvoked = true
77+
if (typeof callback === 'function') {
78+
doInject(this.dispatch, this.option, callback)
79+
} else {
80+
this._promise = doInject(this.dispatch, this.option)
81+
return this._promise
82+
}
83+
}
84+
85+
/**
86+
* @private
87+
* @template {keyof Promise} T
88+
* @param {T} method
89+
* @param {Parameters<Promise[T]>} args
90+
*/
91+
promisify (method, args) {
92+
if (!this._promise) {
93+
if (this._hasInvoked === true) {
94+
throw new Error(errorMessage)
95+
}
96+
this._hasInvoked = true
97+
this._promise = doInject(this.dispatch, this.option)
98+
}
99+
return this._promise[method](...args)
100+
}
101+
102+
then (...args) { return this.promisify('then', args) }
103+
catch (...args) { return this.promisify('catch', args) }
104+
finally (...args) { return this.promisify('finally', args) }
105+
}
106+
107+
module.exports = Chain

lib/do-inject.js

Lines changed: 77 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,77 @@
1+
'use strict'
2+
3+
const assert = require('assert')
4+
const optsValidator = require('./config-validator')
5+
const Request = require('./request')
6+
const { Response, once } = require('./response')
7+
const { Readable, addAbortSignal } = require('stream')
8+
9+
function promisify (fn) {
10+
if (fn) {
11+
return { ret: Promise.resolve(), cb: once(fn) }
12+
}
13+
let resolve, reject
14+
const ret = new Promise((_resolve, _reject) => {
15+
resolve = _resolve
16+
reject = _reject
17+
})
18+
return {
19+
ret,
20+
cb: (err, res) => {
21+
err ? reject(err) : resolve(res)
22+
}
23+
}
24+
}
25+
26+
function makeRequest (dispatchFunc, server, req, res) {
27+
req.socket.once('close', function () {
28+
res.emit('close')
29+
})
30+
31+
return req.prepare(() => dispatchFunc.call(server, req, res), (err) => { res.emit('error', err) })
32+
}
33+
34+
function doInject (dispatchFunc, options, callback) {
35+
options = (typeof options === 'string' ? { url: options } : options)
36+
37+
if (options.validate !== false) {
38+
assert(typeof dispatchFunc === 'function', 'dispatchFunc should be a function')
39+
const isOptionValid = optsValidator(options)
40+
if (!isOptionValid) {
41+
throw new Error(optsValidator.errors.map(e => e.message))
42+
}
43+
}
44+
45+
const server = options.server || {}
46+
47+
const RequestConstructor = options.Request
48+
? Request.getCustomRequest(options.Request)
49+
: Request
50+
51+
// Express.js detection
52+
if (dispatchFunc.request && dispatchFunc.request.app === dispatchFunc) {
53+
Object.setPrototypeOf(Object.getPrototypeOf(dispatchFunc.request), RequestConstructor.prototype)
54+
Object.setPrototypeOf(Object.getPrototypeOf(dispatchFunc.response), Response.prototype)
55+
}
56+
57+
const { ret, cb } = promisify(callback)
58+
59+
const req = new RequestConstructor(options)
60+
const res = new Response(req, cb)
61+
62+
if (options.signal) {
63+
const r = new Readable()
64+
r.once('error', (err) => {
65+
cb(err)
66+
res.destroy(err)
67+
})
68+
res.once('close', () => {
69+
r.destroy()
70+
})
71+
addAbortSignal(options.signal, r)
72+
}
73+
74+
return Promise.resolve().then(() => makeRequest(dispatchFunc, server, req, res)).then(() => ret)
75+
}
76+
77+
module.exports = doInject

0 commit comments

Comments
 (0)