-
Notifications
You must be signed in to change notification settings - Fork 5.9k
/
Copy pathapp.ts
116 lines (101 loc) · 3.35 KB
/
app.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
import { logger } from "@coder/logger"
import compression from "compression"
import express, { Express } from "express"
import { promises as fs } from "fs"
import http from "http"
import * as httpolyglot from "httpolyglot"
import { Disposable } from "../common/emitter"
import * as util from "../common/util"
import { DefaultedArgs } from "./cli"
import { disposer } from "./http"
import { isNodeJSErrnoException } from "./util"
import { handleUpgrade } from "./wsRouter"
type ListenOptions = Pick<DefaultedArgs, "socket-mode" | "socket" | "port" | "host">
export interface App extends Disposable {
/** Handles regular HTTP requests. */
router: Express
/** Handles websocket requests. */
wsRouter: Express
/** The underlying HTTP server. */
server: http.Server
}
export const listen = async (server: http.Server, { host, port, socket, "socket-mode": mode }: ListenOptions) => {
if (socket) {
try {
await fs.unlink(socket)
} catch (error: any) {
handleArgsSocketCatchError(error)
}
}
await new Promise<void>(async (resolve, reject) => {
server.on("error", reject)
const onListen = () => {
// Promise resolved earlier so this is an unrelated error.
server.off("error", reject)
server.on("error", (err) => util.logError(logger, "http server error", err))
resolve()
}
if (socket) {
server.listen(socket, onListen)
} else {
// [] is the correct format when using :: but Node errors with them.
server.listen(port, host.replace(/^\[|\]$/g, ""), onListen)
}
})
// NOTE@jsjoeio: we need to chmod after the server is finished
// listening. Otherwise, the socket may not have been created yet.
if (socket && mode) {
await fs.chmod(socket, mode)
}
}
/**
* Create an Express app and an HTTP/S server to serve it.
*/
export const createApp = async (args: DefaultedArgs): Promise<App> => {
const router = express()
router.use(compression())
const server = args.cert
? httpolyglot.createServer(
{
cert: args.cert && (await fs.readFile(args.cert.value)),
key: args["cert-key"] && (await fs.readFile(args["cert-key"])),
},
router,
)
: http.createServer(router)
const dispose = disposer(server)
await listen(server, args)
const wsRouter = express()
handleUpgrade(wsRouter, server)
return { router, wsRouter, server, dispose }
}
/**
* Get the address of a server as a string (protocol *is* included) while
* ensuring there is one (will throw if there isn't).
*
* The address might be a URL or it might be a pipe or socket path.
*/
export const ensureAddress = (server: http.Server, protocol: string): URL | string => {
const addr = server.address()
if (!addr) {
throw new Error("Server has no address")
}
if (typeof addr !== "string") {
const host = addr.family === "IPv6" ? `[${addr.address}]` : addr.address
return new URL(`${protocol}://${host}:${addr.port}`)
}
// If this is a string then it is a pipe or Unix socket.
return addr
}
/**
* Handles the error that occurs in the catch block
* after we try fs.unlink(args.socket).
*
* We extracted into a function so that we could
* test this logic more easily.
*/
export const handleArgsSocketCatchError = (error: any) => {
if (!isNodeJSErrnoException(error) || error.code !== "ENOENT") {
throw Error(error.message ? error.message : error)
}
}