httpserver.ts 2.6 KB
Newer Older
A
Asher 已提交
1
import { logger } from "@coder/logger"
A
Asher 已提交
2
import * as express from "express"
A
Anmol Sethi 已提交
3
import * as http from "http"
4
import nodeFetch, { RequestInit, Response } from "node-fetch"
A
Asher 已提交
5
import Websocket from "ws"
6
import { Disposable } from "../../src/common/emitter"
J
Joe Previte 已提交
7 8
import * as util from "../../src/common/util"
import { ensureAddress } from "../../src/node/app"
9 10
import { disposer } from "../../src/node/http"

J
Joe Previte 已提交
11
import { handleUpgrade } from "../../src/node/wsRouter"
A
Anmol Sethi 已提交
12

13
// Perhaps an abstraction similar to this should be used in app.ts as well.
A
Anmol Sethi 已提交
14
export class HttpServer {
15 16
  private hs: http.Server
  public dispose: Disposable["dispose"]
A
Anmol Sethi 已提交
17

18 19 20 21 22 23 24 25 26
  /**
   * Expects a server and a disposal that cleans up the server (and anything
   * else that may need cleanup).
   *
   * Otherwise a new server is created.
   */
  public constructor(server?: { server: http.Server; dispose: Disposable["dispose"] }) {
    this.hs = server?.server || http.createServer()
    this.dispose = server?.dispose || disposer(this.hs)
27 28
  }

A
Anmol Sethi 已提交
29 30 31 32 33 34 35
  /**
   * listen starts the server on a random localhost port.
   * Use close to cleanup when done.
   */
  public listen(fn: http.RequestListener): Promise<void> {
    this.hs.on("request", fn)

36 37 38
    return new Promise((resolve, reject) => {
      this.hs.on("error", reject)

A
Anmol Sethi 已提交
39
      this.hs.listen(0, "localhost", () => {
40 41
        this.hs.off("error", reject)
        resolve()
A
Anmol Sethi 已提交
42

43
        this.hs.on("error", (err) => {
A
Anmol Sethi 已提交
44
          // Promise resolved earlier so this is some other error.
A
Asher 已提交
45
          util.logError(logger, "http server error", err)
46
        })
A
Anmol Sethi 已提交
47 48 49 50
      })
    })
  }

A
Asher 已提交
51 52 53 54 55 56 57
  /**
   * Send upgrade requests to an Express app.
   */
  public listenUpgrade(app: express.Express): void {
    handleUpgrade(app, this.hs)
  }

A
Anmol Sethi 已提交
58 59 60 61
  /**
   * fetch fetches the request path.
   * The request path must be rooted!
   */
62 63 64 65 66 67 68 69
  public fetch(requestPath: string, opts?: RequestInit): Promise<Response> {
    const address = ensureAddress(this.hs, "http")
    if (typeof address === "string") {
      throw new Error("Cannot fetch socket path")
    }
    address.pathname = requestPath

    return nodeFetch(address.toString(), opts)
A
Anmol Sethi 已提交
70
  }
71

A
Asher 已提交
72
  /**
73
   * Open a websocket against the request path.
A
Asher 已提交
74 75
   */
  public ws(requestPath: string): Websocket {
76 77 78 79 80 81 82
    const address = ensureAddress(this.hs, "ws")
    if (typeof address === "string") {
      throw new Error("Cannot open websocket to socket path")
    }
    address.pathname = requestPath

    return new Websocket(address.toString())
A
Asher 已提交
83 84
  }

85 86
  public port(): number {
    const addr = this.hs.address()
A
Anmol Sethi 已提交
87
    if (addr && typeof addr === "object") {
88 89 90 91
      return addr.port
    }
    throw new Error("server not listening or listening on unix socket")
  }
A
Anmol Sethi 已提交
92
}