1 |
|
2 | const server = require('http').Server
|
3 |
|
4 |
|
5 | const getRawBody = require('raw-body')
|
6 | const typer = require('media-typer')
|
7 | const isStream = require('isstream')
|
8 | const Promise = require('bluebird')
|
9 |
|
10 | const {resolve} = Promise
|
11 |
|
12 | const DEV = process.env.NODE_ENV === 'development'
|
13 | const TESTING = process.env.NODE_ENV === 'test'
|
14 |
|
15 | const serve = fn => server((req, res) => exports.run(req, res, fn))
|
16 |
|
17 | module.exports = exports = serve
|
18 |
|
19 | exports.send = send
|
20 | exports.sendError = sendError
|
21 | exports.createError = createError
|
22 |
|
23 | exports.run = (req, res, fn) =>
|
24 | resolve(fn(req, res))
|
25 | .then(val => {
|
26 | if (val === null) {
|
27 | send(res, 204, null)
|
28 | }
|
29 |
|
30 |
|
31 | if (undefined !== val) {
|
32 | send(res, res.statusCode || 200, val)
|
33 | }
|
34 | })
|
35 | .catch(err => sendError(req, res, err))
|
36 |
|
37 |
|
38 |
|
39 | const rawBodyMap = new WeakMap()
|
40 |
|
41 | const returnJSON = (resolve, reject, str) => {
|
42 | try {
|
43 | resolve(JSON.parse(str))
|
44 | } catch (err) {
|
45 | reject(createError(400, 'Invalid JSON', err))
|
46 | }
|
47 | }
|
48 |
|
49 | exports.json = (req, {limit = '1mb'} = {}) => new Promise((resolve, reject) => {
|
50 | const type = req.headers['content-type']
|
51 | const length = req.headers['content-length']
|
52 | const encoding = typer.parse(type).parameters.charset
|
53 |
|
54 | let str = rawBodyMap.get(req)
|
55 |
|
56 | if (str) {
|
57 | returnJSON(resolve, reject, str)
|
58 | return
|
59 | }
|
60 |
|
61 | getRawBody(req, {limit, length, encoding}).then(buf => {
|
62 | str = buf
|
63 | rawBodyMap.set(req, str)
|
64 |
|
65 | returnJSON(resolve, reject, str)
|
66 | }).catch(err => {
|
67 | if (err.type === 'entity.too.large') {
|
68 | throw createError(413, `Body exceeded ${limit} limit`, err)
|
69 | } else {
|
70 | throw createError(400, 'Invalid body', err)
|
71 | }
|
72 | })
|
73 | })
|
74 |
|
75 | function send(res, code, obj = null) {
|
76 | res.statusCode = code
|
77 |
|
78 | if (obj === null) {
|
79 | res.end()
|
80 | return
|
81 | }
|
82 |
|
83 | if (Buffer.isBuffer(obj)) {
|
84 | if (!res.getHeader('Content-Type')) {
|
85 | res.setHeader('Content-Type', 'application/octet-stream')
|
86 | }
|
87 |
|
88 | res.setHeader('Content-Length', obj.length)
|
89 | res.end(obj)
|
90 | return
|
91 | }
|
92 |
|
93 | if (isStream(obj)) {
|
94 | if (!res.getHeader('Content-Type')) {
|
95 | res.setHeader('Content-Type', 'application/octet-stream')
|
96 | }
|
97 |
|
98 | obj.pipe(res)
|
99 | return
|
100 | }
|
101 |
|
102 | let str = obj
|
103 |
|
104 | if (typeof obj === 'object') {
|
105 |
|
106 |
|
107 |
|
108 |
|
109 |
|
110 |
|
111 |
|
112 | if (DEV) {
|
113 | str = JSON.stringify(obj, null, 2)
|
114 | } else {
|
115 | str = JSON.stringify(obj)
|
116 | }
|
117 | res.setHeader('Content-Type', 'application/json')
|
118 | }
|
119 |
|
120 | res.setHeader('Content-Length', Buffer.byteLength(str))
|
121 | res.end(str)
|
122 | }
|
123 |
|
124 | function sendError(req, res, {statusCode, status, message, stack}) {
|
125 | if (statusCode) {
|
126 | send(res, statusCode || status, DEV ? stack : message)
|
127 | } else {
|
128 | send(res, 500, DEV ? stack : 'Internal Server Error')
|
129 | }
|
130 |
|
131 | if (!TESTING) {
|
132 | console.error(stack)
|
133 | }
|
134 | }
|
135 |
|
136 | function createError(code, msg, orig) {
|
137 | const err = new Error(msg)
|
138 | err.statusCode = code
|
139 | err.originalError = orig
|
140 | return err
|
141 | }
|