Make providers endpoint-agnostic

A provider can now be registered on multiple endpoints (or potentially
moved if needed).
This commit is contained in:
Asher 2020-07-22 14:53:15 -05:00
parent 2819fd51e2
commit e8f6d30055
No known key found for this signature in database
GPG Key ID: D63C1EF81242354A
5 changed files with 40 additions and 29 deletions

View File

@ -24,7 +24,7 @@ export class ProxyHttpProvider extends HttpProvider {
const port = route.base.replace(/^\//, "") const port = route.base.replace(/^\//, "")
return { return {
proxy: { proxy: {
base: `${this.options.base}/${port}`, base: `${route.providerBase}/${port}`,
port, port,
}, },
} }
@ -35,7 +35,7 @@ export class ProxyHttpProvider extends HttpProvider {
const port = route.base.replace(/^\//, "") const port = route.base.replace(/^\//, "")
return { return {
proxy: { proxy: {
base: `${this.options.base}/${port}`, base: `${route.providerBase}/${port}`,
port, port,
}, },
} }

View File

@ -131,7 +131,7 @@ export class VscodeHttpProvider extends HttpProvider {
if (!this.isRoot(route)) { if (!this.isRoot(route)) {
throw new HttpError("Not found", HttpCode.NotFound) throw new HttpError("Not found", HttpCode.NotFound)
} else if (!this.authenticated(request)) { } else if (!this.authenticated(request)) {
return { redirect: "/login", query: { to: this.options.base } } return { redirect: "/login", query: { to: route.providerBase } }
} }
try { try {
return await this.getRoot(request, route) return await this.getRoot(request, route)

View File

@ -71,7 +71,7 @@ const main = async (args: Args, cliArgs: Args, configArgs: Args): Promise<void>
} }
const httpServer = new HttpServer(options) const httpServer = new HttpServer(options)
httpServer.registerHttpProvider("/", VscodeHttpProvider, args) httpServer.registerHttpProvider(["/", "/vscode"], VscodeHttpProvider, args)
httpServer.registerHttpProvider("/update", UpdateHttpProvider, false) httpServer.registerHttpProvider("/update", UpdateHttpProvider, false)
httpServer.registerHttpProvider("/proxy", ProxyHttpProvider) httpServer.registerHttpProvider("/proxy", ProxyHttpProvider)
httpServer.registerHttpProvider("/login", LoginHttpProvider, args.config!, envPassword) httpServer.registerHttpProvider("/login", LoginHttpProvider, args.config!, envPassword)

View File

@ -79,9 +79,8 @@ export interface HttpResponse<T = string | Buffer | object> {
*/ */
mime?: string mime?: string
/** /**
* Redirect to this path. Will rewrite against the base path but NOT the * Redirect to this path. This is constructed against the site base (not the
* provider endpoint so you must include it. This allows redirecting outside * provider's base).
* of your endpoint.
*/ */
redirect?: string redirect?: string
/** /**
@ -133,12 +132,16 @@ export interface HttpServerOptions {
export interface Route { export interface Route {
/** /**
* Base path part (in /test/path it would be "/test"). * Provider base path part (for /provider/base/path it would be /provider).
*/
providerBase: string
/**
* Base path part (for /provider/base/path it would be /base).
*/ */
base: string base: string
/** /**
* Remaining part of the route (in /test/path it would be "/path"). It can be * Remaining part of the route after factoring out the base and provider base
* blank. * (for /provider/base/path it would be /path). It can be blank.
*/ */
requestPath: string requestPath: string
/** /**
@ -161,7 +164,6 @@ interface ProviderRoute extends Route {
export interface HttpProviderOptions { export interface HttpProviderOptions {
readonly auth: AuthType readonly auth: AuthType
readonly base: string
readonly commit: string readonly commit: string
readonly password?: string readonly password?: string
} }
@ -518,41 +520,51 @@ export class HttpServer {
/** /**
* Register a provider for a top-level endpoint. * Register a provider for a top-level endpoint.
*/ */
public registerHttpProvider<T extends HttpProvider>(endpoint: string, provider: HttpProvider0<T>): T public registerHttpProvider<T extends HttpProvider>(endpoint: string | string[], provider: HttpProvider0<T>): T
public registerHttpProvider<A1, T extends HttpProvider>(endpoint: string, provider: HttpProvider1<A1, T>, a1: A1): T public registerHttpProvider<A1, T extends HttpProvider>(
endpoint: string | string[],
provider: HttpProvider1<A1, T>,
a1: A1,
): T
public registerHttpProvider<A1, A2, T extends HttpProvider>( public registerHttpProvider<A1, A2, T extends HttpProvider>(
endpoint: string, endpoint: string | string[],
provider: HttpProvider2<A1, A2, T>, provider: HttpProvider2<A1, A2, T>,
a1: A1, a1: A1,
a2: A2, a2: A2,
): T ): T
public registerHttpProvider<A1, A2, A3, T extends HttpProvider>( public registerHttpProvider<A1, A2, A3, T extends HttpProvider>(
endpoint: string, endpoint: string | string[],
provider: HttpProvider3<A1, A2, A3, T>, provider: HttpProvider3<A1, A2, A3, T>,
a1: A1, a1: A1,
a2: A2, a2: A2,
a3: A3, a3: A3,
): T ): T
// eslint-disable-next-line @typescript-eslint/no-explicit-any // eslint-disable-next-line @typescript-eslint/no-explicit-any
public registerHttpProvider(endpoint: string, provider: any, ...args: any[]): any { public registerHttpProvider(endpoint: string | string[], provider: any, ...args: any[]): void {
endpoint = endpoint.replace(/^\/+|\/+$/g, "")
if (this.providers.has(`/${endpoint}`)) {
throw new Error(`${endpoint} is already registered`)
}
if (/\//.test(endpoint)) {
throw new Error(`Only top-level endpoints are supported (got ${endpoint})`)
}
const p = new provider( const p = new provider(
{ {
auth: this.options.auth || AuthType.None, auth: this.options.auth || AuthType.None,
base: `/${endpoint}`,
commit: this.options.commit, commit: this.options.commit,
password: this.options.password, password: this.options.password,
}, },
...args, ...args,
) )
const endpoints = (typeof endpoint === "string" ? [endpoint] : endpoint).map((e) => e.replace(/^\/+|\/+$/g, ""))
endpoints.forEach((endpoint) => {
if (/\//.test(endpoint)) {
throw new Error(`Only top-level endpoints are supported (got ${endpoint})`)
}
const existingProvider = this.providers.get(`/${endpoint}`)
this.providers.set(`/${endpoint}`, p) this.providers.set(`/${endpoint}`, p)
return p if (existingProvider) {
logger.debug(`Overridding existing /${endpoint} provider`)
// If the existing provider isn't registered elsewhere we can dispose.
if (!Array.from(this.providers.values()).find((p) => p === existingProvider)) {
logger.debug(`Disposing existing /${endpoint} provider`)
existingProvider.dispose()
}
}
})
} }
/** /**
@ -759,7 +771,7 @@ export class HttpServer {
// that by shifting the next base out of the request path. // that by shifting the next base out of the request path.
let provider = this.providers.get(base) let provider = this.providers.get(base)
if (base !== "/" && provider) { if (base !== "/" && provider) {
return { ...parse(requestPath), fullPath, query: parsedUrl.query, provider, originalPath } return { ...parse(requestPath), providerBase: base, fullPath, query: parsedUrl.query, provider, originalPath }
} }
// Fall back to the top-level provider. // Fall back to the top-level provider.
@ -767,7 +779,7 @@ export class HttpServer {
if (!provider) { if (!provider) {
throw new Error(`No provider for ${base}`) throw new Error(`No provider for ${base}`)
} }
return { base, fullPath, requestPath, query: parsedUrl.query, provider, originalPath } return { base, providerBase: "/", fullPath, requestPath, query: parsedUrl.query, provider, originalPath }
} }
/** /**

View File

@ -51,7 +51,6 @@ describe("update", () => {
_provider = new UpdateHttpProvider( _provider = new UpdateHttpProvider(
{ {
auth: AuthType.None, auth: AuthType.None,
base: "/update",
commit: "test", commit: "test",
}, },
true, true,