|
| 1 | +import { field, logger } from "@coder/logger" |
1 | 2 | import * as crypto from "crypto"
|
2 |
| -import { Router } from "express" |
| 3 | +import { RequestHandler, Router, static as createStaticRouteHandler } from "express" |
3 | 4 | import { promises as fs } from "fs"
|
4 | 5 | import * as path from "path"
|
| 6 | +import { resolve } from "path" |
| 7 | +import { ParsedQs } from "qs" |
| 8 | +import { Readable } from "stream" |
| 9 | +import * as tarFs from "tar-fs" |
| 10 | +import * as zlib from "zlib" |
5 | 11 | import { WorkbenchOptions } from "../../../lib/vscode/src/vs/server/ipc"
|
6 |
| -import { commit, version } from "../constants" |
| 12 | +import { HttpCode, HttpError } from "../../common/http" |
| 13 | +import { commit, rootPath, version } from "../constants" |
7 | 14 | import { authenticated, commonTemplateVars, ensureAuthenticated, redirect } from "../http"
|
8 | 15 | import { getMediaMime, pathToFsPath } from "../util"
|
9 | 16 | import { VscodeProvider } from "../vscode"
|
@@ -96,3 +103,53 @@ wsRouter.ws("/", ensureAuthenticated, async (req) => {
|
96 | 103 | )
|
97 | 104 | await vscode.sendWebsocket(req.ws, req.query)
|
98 | 105 | })
|
| 106 | +router.use( |
| 107 | + "/lib", |
| 108 | + createStaticRouteHandler(resolve(rootPath, "lib"), { |
| 109 | + index: false, |
| 110 | + cacheControl: commit !== "development", |
| 111 | + }), |
| 112 | +) |
| 113 | + |
| 114 | +interface TarHandlerQueryParams extends ParsedQs { |
| 115 | + filePath?: string | string[] |
| 116 | +} |
| 117 | + |
| 118 | +/** |
| 119 | + * Packs and serves requested file with tar. |
| 120 | + * This is commonly used to fetch an extension on the client. |
| 121 | + * |
| 122 | + * @remark See lib/vscode/src/vs/server/browser/mainThreadNodeProxy.ts#L35 |
| 123 | + */ |
| 124 | +const tarHandler: RequestHandler<any, Buffer, null, TarHandlerQueryParams> = (req, res) => { |
| 125 | + const filePath = Array.isArray(req.query.filePath) ? req.query.filePath[0] : req.query.filePath |
| 126 | + |
| 127 | + if (!filePath) { |
| 128 | + throw new HttpError("Missing 'filePath' query param", HttpCode.BadRequest) |
| 129 | + } |
| 130 | + |
| 131 | + const resourcePath = resolve(filePath) |
| 132 | + |
| 133 | + let stream: Readable = tarFs.pack(pathToFsPath(filePath)) |
| 134 | + |
| 135 | + if (req.headers["accept-encoding"] && req.headers["accept-encoding"].includes("gzip")) { |
| 136 | + logger.debug("gzipping tar", field("path", resourcePath)) |
| 137 | + |
| 138 | + const compress = zlib.createGzip() |
| 139 | + |
| 140 | + stream.pipe(compress) |
| 141 | + stream.on("error", (error) => compress.destroy(error)) |
| 142 | + stream.on("close", () => compress.end()) |
| 143 | + |
| 144 | + stream = compress |
| 145 | + |
| 146 | + res.header("content-encoding", "gzip") |
| 147 | + } |
| 148 | + |
| 149 | + res.set("Content-Type", "application/x-tar") |
| 150 | + stream.on("close", () => res.end()) |
| 151 | + |
| 152 | + return stream.pipe(res) |
| 153 | +} |
| 154 | + |
| 155 | +router.get("/extension/tar", tarHandler) |
0 commit comments