feat: introduce --abs-proxy-base-path that allows app proxying while code-server is not server at the root.

This commit is contained in:
rafael_ferreira 2024-08-25 12:01:06 +01:00
parent 39ce82a44d
commit f8a4da7667
6 changed files with 54 additions and 16 deletions

View File

@ -432,3 +432,12 @@ const config = {
3. Access app at `<code-server-root>/absproxy/5173/` e.g. `http://localhost:8080/absproxy/5173/ 3. Access app at `<code-server-root>/absproxy/5173/` e.g. `http://localhost:8080/absproxy/5173/
For additional context, see [this Github Issue](https://github.com/sveltejs/kit/issues/2958) For additional context, see [this Github Issue](https://github.com/sveltejs/kit/issues/2958)
### Prefixing `/absproxy/<port>` with a path
This is a case where you need to serve an application via `absproxy` as explained above while serving `codeserver` itself from a path other than the root in your domain.
For example: `http://my-code-server.com/user/123/workspace/my-app`. To achieve this result:
1. start code server with the switch `--abs-proxy-base-path=/user/123/workspace`
2. Follow one of the instructions above for your framework.

View File

@ -53,6 +53,7 @@ export interface UserProvidedCodeArgs {
"disable-getting-started-override"?: boolean "disable-getting-started-override"?: boolean
"disable-proxy"?: boolean "disable-proxy"?: boolean
"session-socket"?: string "session-socket"?: string
"abs-proxy-base-path"?: string
} }
/** /**
@ -117,18 +118,18 @@ interface Option<T> {
type OptionType<T> = T extends boolean type OptionType<T> = T extends boolean
? "boolean" ? "boolean"
: T extends OptionalString : T extends OptionalString
? typeof OptionalString ? typeof OptionalString
: T extends LogLevel : T extends LogLevel
? typeof LogLevel ? typeof LogLevel
: T extends AuthType : T extends AuthType
? typeof AuthType ? typeof AuthType
: T extends number : T extends number
? "number" ? "number"
: T extends string : T extends string
? "string" ? "string"
: T extends string[] : T extends string[]
? "string[]" ? "string[]"
: "unknown" : "unknown"
export type Options<T> = { export type Options<T> = {
[P in keyof T]: Option<OptionType<T[P]>> [P in keyof T]: Option<OptionType<T[P]>>
@ -279,6 +280,10 @@ export const options: Options<Required<UserProvidedArgs>> = {
short: "w", short: "w",
description: "Text to show on login page", description: "Text to show on login page",
}, },
"abs-proxy-base-path": {
type: "string",
description: "The base path to prefix all absproxy requests",
},
} }
export const optionDescriptions = (opts: Partial<Options<Required<UserProvidedArgs>>> = options): string[] => { export const optionDescriptions = (opts: Partial<Options<Required<UserProvidedArgs>>> = options): string[] => {

View File

@ -121,11 +121,13 @@ export const register = async (app: App, args: DefaultedArgs): Promise<Disposabl
app.router.all("/absproxy/:port/:path(.*)?", async (req, res) => { app.router.all("/absproxy/:port/:path(.*)?", async (req, res) => {
await pathProxy.proxy(req, res, { await pathProxy.proxy(req, res, {
passthroughPath: true, passthroughPath: true,
proxyBasePath: args["abs-proxy-base-path"],
}) })
}) })
app.wsRouter.get("/absproxy/:port/:path(.*)?", async (req) => { app.wsRouter.get("/absproxy/:port/:path(.*)?", async (req) => {
await pathProxy.wsProxy(req as pluginapi.WebsocketRequest, { await pathProxy.wsProxy(req as pluginapi.WebsocketRequest, {
passthroughPath: true, passthroughPath: true,
proxyBasePath: args["abs-proxy-base-path"],
}) })
}) })

View File

@ -5,10 +5,15 @@ import { HttpCode, HttpError } from "../../common/http"
import { ensureProxyEnabled, authenticated, ensureAuthenticated, ensureOrigin, redirect, self } from "../http" import { ensureProxyEnabled, authenticated, ensureAuthenticated, ensureOrigin, redirect, self } from "../http"
import { proxy as _proxy } from "../proxy" import { proxy as _proxy } from "../proxy"
const getProxyTarget = (req: Request): string => { const getProxyTarget = (
req: Request,
opts?: {
proxyBasePath?: string
},
): string => {
// If there is a base path, strip it out. // If there is a base path, strip it out.
const base = (req as any).base || "" const base = (req as any).base || ""
return `http://0.0.0.0:${req.params.port}/${req.originalUrl.slice(base.length)}` return `http://0.0.0.0:${req.params.port}${opts?.proxyBasePath || ""}/${req.originalUrl.slice(base.length)}`
} }
export async function proxy( export async function proxy(
@ -16,6 +21,7 @@ export async function proxy(
res: Response, res: Response,
opts?: { opts?: {
passthroughPath?: boolean passthroughPath?: boolean
proxyBasePath?: string
}, },
): Promise<void> { ): Promise<void> {
ensureProxyEnabled(req) ensureProxyEnabled(req)
@ -38,7 +44,7 @@ export async function proxy(
_proxy.web(req, res, { _proxy.web(req, res, {
ignorePath: true, ignorePath: true,
target: getProxyTarget(req), target: getProxyTarget(req, opts),
}) })
} }
@ -46,6 +52,7 @@ export async function wsProxy(
req: pluginapi.WebsocketRequest, req: pluginapi.WebsocketRequest,
opts?: { opts?: {
passthroughPath?: boolean passthroughPath?: boolean
proxyBasePath?: string
}, },
): Promise<void> { ): Promise<void> {
ensureProxyEnabled(req) ensureProxyEnabled(req)
@ -59,6 +66,6 @@ export async function wsProxy(
_proxy.ws(req, req.ws, req.head, { _proxy.ws(req, req.ws, req.head, {
ignorePath: true, ignorePath: true,
target: getProxyTarget(req), target: getProxyTarget(req, opts),
}) })
} }

View File

@ -106,6 +106,8 @@ describe("parser", () => {
"--disable-proxy", "--disable-proxy",
["--abs-proxy-base-path", "/codeserver/app1"],
["--session-socket", "/tmp/override-code-server-ipc-socket"], ["--session-socket", "/tmp/override-code-server-ipc-socket"],
["--host", "0.0.0.0"], ["--host", "0.0.0.0"],
@ -143,6 +145,7 @@ describe("parser", () => {
version: true, version: true,
"bind-addr": "192.169.0.1:8080", "bind-addr": "192.169.0.1:8080",
"session-socket": "/tmp/override-code-server-ipc-socket", "session-socket": "/tmp/override-code-server-ipc-socket",
"abs-proxy-base-path": "/codeserver/app1",
}) })
}) })

View File

@ -256,6 +256,18 @@ describe("proxy", () => {
expect(spy).toHaveBeenCalledWith([test.expected, test.query]) expect(spy).toHaveBeenCalledWith([test.expected, test.query])
} }
}) })
it("should allow specifying an absproxy path", async () => {
const prefixedPath = `/codeserver/app1${absProxyPath}`
e.get(prefixedPath, (req, res) => {
res.send("app being served behind a prefixed path")
})
codeServer = await integration.setup(["--auth=none", "--abs-proxy-base-path=/codeserver/app1"], "")
const resp = await codeServer.fetch(absProxyPath)
expect(resp.status).toBe(200)
const text = await resp.text()
expect(text).toBe("app being served behind a prefixed path")
})
}) })
// NOTE@jsjoeio // NOTE@jsjoeio