-
-
Notifications
You must be signed in to change notification settings - Fork 102
/
serve.ts
123 lines (111 loc) · 3.19 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
// Forked from https://deno.land/std@0.72.0/http/file_server.ts
import { path, server } from '../../deps.ts';
const { extname, posix } = path;
const encoder = new TextEncoder();
const MEDIA_TYPES: Record<string, string> = {
'.md': 'text/markdown',
'.html': 'text/html',
'.htm': 'text/html',
'.json': 'application/json',
'.map': 'application/json',
'.txt': 'text/plain',
'.ts': 'text/typescript',
'.tsx': 'text/tsx',
'.js': 'application/javascript',
'.jsx': 'text/jsx',
'.gz': 'application/gzip',
'.css': 'text/css',
'.wasm': 'application/wasm',
'.mjs': 'application/javascript',
'.svg': 'image/svg+xml'
};
/** Returns the content-type based on the extension of a path. */
function contentType(path: string): string | undefined {
return MEDIA_TYPES[extname(path)];
}
async function serveFile(req: server.ServerRequest, filePath: string): Promise<server.Response> {
const [file, fileInfo] = await Promise.all([Deno.open(filePath), Deno.stat(filePath)]);
const headers = new Headers();
headers.set('content-length', fileInfo.size.toString());
const contentTypeValue = contentType(filePath);
if (contentTypeValue) {
headers.set('content-type', contentTypeValue);
}
req.done.then(() => {
file.close();
});
return {
status: 200,
body: file,
headers
};
}
function serveFallback(req: server.ServerRequest, e: Error): Promise<server.Response> {
if (e instanceof Deno.errors.NotFound) {
return Promise.resolve({
status: 404,
body: encoder.encode('Not found')
});
} else {
return Promise.resolve({
status: 500,
body: encoder.encode('Internal server error')
});
}
}
interface ServeOptions {
serveDir?: string;
root?: string;
port?: number;
}
const defaultServeOptions: Required<ServeOptions> = {
serveDir: 'dist',
root: '/',
port: 8000
};
/** Serve dir as static server */
export function serve(options?: ServeOptions) {
const { serveDir, root, port } = { ...defaultServeOptions, ...options };
const handler = async (req: server.ServerRequest) => {
let normalizedUrl = posix.normalize(req.url);
try {
normalizedUrl = decodeURIComponent(normalizedUrl);
} catch (e) {
if (!(e instanceof URIError)) {
throw e;
}
}
let fsPath = posix.join(serveDir, normalizedUrl.replace(root, '/'));
let response: server.Response | undefined;
try {
const fileInfo = await Deno.stat(fsPath);
if (fileInfo.isDirectory) {
fsPath = posix.join(fsPath, 'index.html');
}
response = await serveFile(req, fsPath);
} catch (e) {
response = await serveFallback(req, e);
} finally {
try {
await req.respond(response!);
} catch (e) {
console.error(e.message);
}
}
};
return listenAndServe(`127.0.0.1:${port}`, handler);
}
// https://github.com/denoland/deno/issues/5060
export function listenAndServe(
addr: string | server.HTTPOptions,
handler: (req: server.ServerRequest) => void
): server.Server {
const serverInstance = server.serve(addr);
const handleRequests = async () => {
for await (const request of serverInstance) {
handler(request);
}
};
handleRequests();
return serverInstance;
}