forked from vercel/micro
-
Notifications
You must be signed in to change notification settings - Fork 3
/
Copy pathserve.ts
151 lines (125 loc) · 3.66 KB
/
serve.ts
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
// Native
import { IncomingMessage, ServerResponse, IncomingHttpHeaders, Server, createServer } from 'http';
// Packages
import contentType from 'content-type';
// Utilities
import { MicriHandler } from './types';
import { MicriError } from './errors';
const { NODE_ENV } = process.env;
const DEV = NODE_ENV === 'development';
const jsonStringify = DEV
? (obj: any): string => JSON.stringify(obj, null, 2)
: (obj: any): string => JSON.stringify(obj);
const isStream = (data: any) => !!data && typeof data.pipe === 'function';
export function send(res: ServerResponse, statusCode: number, obj: any = null) {
res.statusCode = statusCode;
if (obj === null) {
res.end();
return;
}
if (Buffer.isBuffer(obj)) {
if (!res.getHeader('Content-Type')) {
res.setHeader('Content-Type', 'application/octet-stream');
}
res.setHeader('Content-Length', obj.length);
res.end(obj);
return;
}
if (isStream(obj)) {
if (!res.getHeader('Content-Type')) {
res.setHeader('Content-Type', 'application/octet-stream');
}
obj.pipe(res);
return;
}
let str: string = obj;
const typeObj = typeof obj;
if (typeObj === 'object' || typeObj === 'number' || typeObj === 'boolean') {
// We stringify before setting the header
// in case `JSON.stringify` throws and a
// 500 has to be sent instead
str = jsonStringify(obj);
if (!res.getHeader('Content-Type')) {
res.setHeader('Content-Type', 'application/json; charset=utf-8');
}
} else {
if (!res.getHeader('Content-Type')) {
res.setHeader('Content-Type', 'text/plain; charset=utf-8');
}
}
res.setHeader('Content-Length', Buffer.byteLength(str));
res.end(str);
}
function isAcceptJson(headers: IncomingHttpHeaders) {
const { accept } = headers;
let type = '*/*';
try {
const ct = contentType.parse(accept || type);
type = ct.type || '*/*';
} catch (err) {
// NOP
}
return type === '*/*' || accept === 'application/json';
}
export const sendError = (req: IncomingMessage, res: ServerResponse, errorObj: MicriError | Error) => {
const acceptJson = isAcceptJson(req.headers);
let statusCode = 500;
let body: any = acceptJson
? {
error: {
code: 'internal_server_error',
message: 'Internal Server Error',
},
}
: 'Internal Server Error';
if (errorObj instanceof MicriError) {
statusCode = errorObj.statusCode || 500;
const code = errorObj.code || 'internal_server_error';
if (acceptJson) {
if (DEV) {
body = {
error: {
code,
message: errorObj.message,
stack: errorObj.stack,
originalError: errorObj.originalError || null,
},
};
} else {
const message = errorObj.message || 'Internal Server Error';
body = {
error: {
code,
message,
},
};
}
} else {
const message = errorObj.message || 'Internal Server Error';
body = DEV ? errorObj.stack : message;
}
} else if (errorObj instanceof Error) {
console.error(errorObj);
} else {
console.warn('thrown error must be an instance Error');
}
send(res, statusCode, body);
};
export function run<OptsType = any>(req: IncomingMessage, res: ServerResponse, fn: MicriHandler<OptsType>) {
return new Promise((resolve) => resolve(fn(req, res)))
.then((val) => {
if (val === null) {
send(res, 204, null);
return;
}
// Send value if it is not undefined, otherwise assume res.end
// will be called later
// eslint-disable-next-line no-undefined
if (val !== undefined) {
send(res, res.statusCode || 200, val);
}
})
.catch((err) => sendError(req, res, err));
}
export const serve = <OptsType = any>(fn: MicriHandler<OptsType>): Server =>
createServer((req: IncomingMessage, res: ServerResponse) => run<OptsType>(req, res, fn));