Fix redirects through subpath proxy
This commit is contained in:
parent
fd339a7433
commit
e7e7b0ffb7
@ -17,7 +17,7 @@
|
|||||||
href="{{BASE}}/static/{{COMMIT}}/src/browser/media/manifest.json"
|
href="{{BASE}}/static/{{COMMIT}}/src/browser/media/manifest.json"
|
||||||
crossorigin="use-credentials"
|
crossorigin="use-credentials"
|
||||||
/>
|
/>
|
||||||
<link rel="apple-touch-icon" href="{{BASE}}/static/{{COMMIT}}/src/browser/media/pwa-icon-384.pnggg" />
|
<link rel="apple-touch-icon" href="{{BASE}}/static/{{COMMIT}}/src/browser/media/pwa-icon-384.png" />
|
||||||
<link href="{{BASE}}/static/{{COMMIT}}/dist/pages/app.css" rel="stylesheet" />
|
<link href="{{BASE}}/static/{{COMMIT}}/dist/pages/app.css" rel="stylesheet" />
|
||||||
<meta id="coder-options" data-settings="{{OPTIONS}}" />
|
<meta id="coder-options" data-settings="{{OPTIONS}}" />
|
||||||
</head>
|
</head>
|
||||||
|
@ -6,6 +6,10 @@ import * as querystring from "querystring"
|
|||||||
import { HttpCode, HttpError } from "../../common/http"
|
import { HttpCode, HttpError } from "../../common/http"
|
||||||
import { HttpProvider, HttpProviderOptions, HttpProxyProvider, HttpResponse, Route } from "../http"
|
import { HttpProvider, HttpProviderOptions, HttpProxyProvider, HttpResponse, Route } from "../http"
|
||||||
|
|
||||||
|
interface Request extends http.IncomingMessage {
|
||||||
|
base?: string
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Proxy HTTP provider.
|
* Proxy HTTP provider.
|
||||||
*/
|
*/
|
||||||
@ -24,6 +28,12 @@ export class ProxyHttpProvider extends HttpProvider implements HttpProxyProvider
|
|||||||
super(options)
|
super(options)
|
||||||
this.proxyDomains = proxyDomains.map((d) => d.replace(/^\*\./, "")).filter((d, i, arr) => arr.indexOf(d) === i)
|
this.proxyDomains = proxyDomains.map((d) => d.replace(/^\*\./, "")).filter((d, i, arr) => arr.indexOf(d) === i)
|
||||||
this.proxy.on("error", (error) => logger.warn(error.message))
|
this.proxy.on("error", (error) => logger.warn(error.message))
|
||||||
|
// Intercept the response to rewrite absolute redirects against the base path.
|
||||||
|
this.proxy.on("proxyRes", (response, request: Request) => {
|
||||||
|
if (response.headers.location && response.headers.location.startsWith("/") && request.base) {
|
||||||
|
response.headers.location = request.base + response.headers.location
|
||||||
|
}
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
public async handleRequest(
|
public async handleRequest(
|
||||||
@ -41,14 +51,15 @@ export class ProxyHttpProvider extends HttpProvider implements HttpProxyProvider
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Ensure there is a trailing slash so relative paths work correctly.
|
// Ensure there is a trailing slash so relative paths work correctly.
|
||||||
const base = route.base.replace(/^\//, "")
|
const port = route.base.replace(/^\//, "")
|
||||||
if (isRoot && !route.originalPath.endsWith("/")) {
|
const base = `${this.options.base}/${port}`
|
||||||
|
if (isRoot && !route.fullPath.endsWith("/")) {
|
||||||
return {
|
return {
|
||||||
redirect: `/proxy/${base}/`,
|
redirect: `${base}/`,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const payload = this.doProxy(route.requestPath, route.query, request, response, base)
|
const payload = this.doProxy(route, request, response, port, base)
|
||||||
if (payload) {
|
if (payload) {
|
||||||
return payload
|
return payload
|
||||||
}
|
}
|
||||||
@ -63,7 +74,9 @@ export class ProxyHttpProvider extends HttpProvider implements HttpProxyProvider
|
|||||||
head: Buffer,
|
head: Buffer,
|
||||||
): Promise<void> {
|
): Promise<void> {
|
||||||
this.ensureAuthenticated(request)
|
this.ensureAuthenticated(request)
|
||||||
this.doProxy(route.requestPath, route.query, request, socket, head, route.base.replace(/^\//, ""))
|
const port = route.base.replace(/^\//, "")
|
||||||
|
const base = `${this.options.base}/${port}`
|
||||||
|
this.doProxy(route, request, { socket, head }, port, base)
|
||||||
}
|
}
|
||||||
|
|
||||||
public getCookieDomain(host: string): string {
|
public getCookieDomain(host: string): string {
|
||||||
@ -84,7 +97,7 @@ export class ProxyHttpProvider extends HttpProvider implements HttpProxyProvider
|
|||||||
response: http.ServerResponse,
|
response: http.ServerResponse,
|
||||||
): HttpResponse | undefined {
|
): HttpResponse | undefined {
|
||||||
const port = this.getPort(request)
|
const port = this.getPort(request)
|
||||||
return port ? this.doProxy(route.fullPath, route.query, request, response, port) : undefined
|
return port ? this.doProxy(route, request, response, port) : undefined
|
||||||
}
|
}
|
||||||
|
|
||||||
public maybeProxyWebSocket(
|
public maybeProxyWebSocket(
|
||||||
@ -94,7 +107,7 @@ export class ProxyHttpProvider extends HttpProvider implements HttpProxyProvider
|
|||||||
head: Buffer,
|
head: Buffer,
|
||||||
): HttpResponse | undefined {
|
): HttpResponse | undefined {
|
||||||
const port = this.getPort(request)
|
const port = this.getPort(request)
|
||||||
return port ? this.doProxy(route.fullPath, route.query, request, socket, head, port) : undefined
|
return port ? this.doProxy(route, request, { socket, head }, port) : undefined
|
||||||
}
|
}
|
||||||
|
|
||||||
private getPort(request: http.IncomingMessage): string | undefined {
|
private getPort(request: http.IncomingMessage): string | undefined {
|
||||||
@ -121,57 +134,55 @@ export class ProxyHttpProvider extends HttpProvider implements HttpProxyProvider
|
|||||||
}
|
}
|
||||||
|
|
||||||
private doProxy(
|
private doProxy(
|
||||||
path: string,
|
route: Route,
|
||||||
query: querystring.ParsedUrlQuery,
|
|
||||||
request: http.IncomingMessage,
|
request: http.IncomingMessage,
|
||||||
response: http.ServerResponse,
|
response: http.ServerResponse,
|
||||||
portStr: string,
|
portStr: string,
|
||||||
|
base?: string,
|
||||||
): HttpResponse
|
): HttpResponse
|
||||||
private doProxy(
|
private doProxy(
|
||||||
path: string,
|
route: Route,
|
||||||
query: querystring.ParsedUrlQuery,
|
|
||||||
request: http.IncomingMessage,
|
request: http.IncomingMessage,
|
||||||
socket: net.Socket,
|
response: { socket: net.Socket; head: Buffer },
|
||||||
head: Buffer,
|
|
||||||
portStr: string,
|
portStr: string,
|
||||||
|
base?: string,
|
||||||
): HttpResponse
|
): HttpResponse
|
||||||
private doProxy(
|
private doProxy(
|
||||||
path: string,
|
route: Route,
|
||||||
query: querystring.ParsedUrlQuery,
|
|
||||||
request: http.IncomingMessage,
|
request: http.IncomingMessage,
|
||||||
responseOrSocket: http.ServerResponse | net.Socket,
|
response: http.ServerResponse | { socket: net.Socket; head: Buffer },
|
||||||
headOrPortStr: Buffer | string,
|
portStr: string,
|
||||||
portStr?: string,
|
base?: string,
|
||||||
): HttpResponse {
|
): HttpResponse {
|
||||||
const _portStr = typeof headOrPortStr === "string" ? headOrPortStr : portStr
|
const port = parseInt(portStr, 10)
|
||||||
if (!_portStr) {
|
|
||||||
return {
|
|
||||||
code: HttpCode.BadRequest,
|
|
||||||
content: "Port must be provided",
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const port = parseInt(_portStr, 10)
|
|
||||||
if (isNaN(port)) {
|
if (isNaN(port)) {
|
||||||
return {
|
return {
|
||||||
code: HttpCode.BadRequest,
|
code: HttpCode.BadRequest,
|
||||||
content: `"${_portStr}" is not a valid number`,
|
content: `"${portStr}" is not a valid number`,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// REVIEW: Absolute redirects need to be based on the subpath but I'm not
|
||||||
|
// sure how best to get this information to the `proxyRes` event handler.
|
||||||
|
// For now I'm sticking it on the request object which is passed through to
|
||||||
|
// the event.
|
||||||
|
;(request as Request).base = base
|
||||||
|
|
||||||
|
const hxxp = response instanceof http.ServerResponse
|
||||||
|
const path = base ? route.fullPath.replace(base, "") : route.fullPath
|
||||||
const options: proxy.ServerOptions = {
|
const options: proxy.ServerOptions = {
|
||||||
autoRewrite: true,
|
|
||||||
changeOrigin: true,
|
changeOrigin: true,
|
||||||
ignorePath: true,
|
ignorePath: true,
|
||||||
target: `http://127.0.0.1:${port}${path}${
|
target: `${hxxp ? "http" : "ws"}://127.0.0.1:${port}${path}${
|
||||||
Object.keys(query).length > 0 ? `?${querystring.stringify(query)}` : ""
|
Object.keys(route.query).length > 0 ? `?${querystring.stringify(route.query)}` : ""
|
||||||
}`,
|
}`,
|
||||||
|
ws: !hxxp,
|
||||||
}
|
}
|
||||||
|
|
||||||
if (responseOrSocket instanceof net.Socket) {
|
if (response instanceof http.ServerResponse) {
|
||||||
this.proxy.ws(request, responseOrSocket, headOrPortStr, options)
|
this.proxy.web(request, response, options)
|
||||||
} else {
|
} else {
|
||||||
this.proxy.web(request, responseOrSocket, options)
|
this.proxy.ws(request, response.socket, response.head, options)
|
||||||
}
|
}
|
||||||
|
|
||||||
return { handled: true }
|
return { handled: true }
|
||||||
|
@ -596,7 +596,7 @@ export class HttpServer {
|
|||||||
`Path=${normalize(payload.cookie.path || "/", true)}`,
|
`Path=${normalize(payload.cookie.path || "/", true)}`,
|
||||||
domain ? `Domain=${(this.proxy && this.proxy.getCookieDomain(domain)) || domain}` : undefined,
|
domain ? `Domain=${(this.proxy && this.proxy.getCookieDomain(domain)) || domain}` : undefined,
|
||||||
// "HttpOnly",
|
// "HttpOnly",
|
||||||
"SameSite=strict",
|
"SameSite=lax",
|
||||||
]
|
]
|
||||||
.filter((l) => !!l)
|
.filter((l) => !!l)
|
||||||
.join(";"),
|
.join(";"),
|
||||||
@ -633,9 +633,11 @@ export class HttpServer {
|
|||||||
if (error.code === "ENOENT" || error.code === "EISDIR") {
|
if (error.code === "ENOENT" || error.code === "EISDIR") {
|
||||||
e = new HttpError("Not found", HttpCode.NotFound)
|
e = new HttpError("Not found", HttpCode.NotFound)
|
||||||
}
|
}
|
||||||
logger.debug("Request error", field("url", request.url))
|
|
||||||
logger.debug(error.stack)
|
|
||||||
const code = typeof e.code === "number" ? e.code : HttpCode.ServerError
|
const code = typeof e.code === "number" ? e.code : HttpCode.ServerError
|
||||||
|
logger.debug("Request error", field("url", request.url), field("code", code))
|
||||||
|
if (code >= HttpCode.ServerError) {
|
||||||
|
logger.error(error.stack)
|
||||||
|
}
|
||||||
const payload = await route.provider.getErrorRoot(route, code, code, e.message)
|
const payload = await route.provider.getErrorRoot(route, code, code, e.message)
|
||||||
write({
|
write({
|
||||||
code,
|
code,
|
||||||
|
Reference in New Issue
Block a user