diff --git a/src/browser/pages/home.html b/src/browser/pages/home.html
index 08643f48..542f3b6e 100644
--- a/src/browser/pages/home.html
+++ b/src/browser/pages/home.html
@@ -17,7 +17,7 @@
href="{{BASE}}/static/{{COMMIT}}/src/browser/media/manifest.json"
crossorigin="use-credentials"
/>
-
+
diff --git a/src/node/app/proxy.ts b/src/node/app/proxy.ts
index 1afdd1ce..3023ad79 100644
--- a/src/node/app/proxy.ts
+++ b/src/node/app/proxy.ts
@@ -6,6 +6,10 @@ import * as querystring from "querystring"
import { HttpCode, HttpError } from "../../common/http"
import { HttpProvider, HttpProviderOptions, HttpProxyProvider, HttpResponse, Route } from "../http"
+interface Request extends http.IncomingMessage {
+ base?: string
+}
+
/**
* Proxy HTTP provider.
*/
@@ -24,6 +28,12 @@ export class ProxyHttpProvider extends HttpProvider implements HttpProxyProvider
super(options)
this.proxyDomains = proxyDomains.map((d) => d.replace(/^\*\./, "")).filter((d, i, arr) => arr.indexOf(d) === i)
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(
@@ -41,14 +51,15 @@ export class ProxyHttpProvider extends HttpProvider implements HttpProxyProvider
}
// Ensure there is a trailing slash so relative paths work correctly.
- const base = route.base.replace(/^\//, "")
- if (isRoot && !route.originalPath.endsWith("/")) {
+ const port = route.base.replace(/^\//, "")
+ const base = `${this.options.base}/${port}`
+ if (isRoot && !route.fullPath.endsWith("/")) {
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) {
return payload
}
@@ -63,7 +74,9 @@ export class ProxyHttpProvider extends HttpProvider implements HttpProxyProvider
head: Buffer,
): Promise {
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 {
@@ -84,7 +97,7 @@ export class ProxyHttpProvider extends HttpProvider implements HttpProxyProvider
response: http.ServerResponse,
): HttpResponse | undefined {
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(
@@ -94,7 +107,7 @@ export class ProxyHttpProvider extends HttpProvider implements HttpProxyProvider
head: Buffer,
): HttpResponse | undefined {
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 {
@@ -121,57 +134,55 @@ export class ProxyHttpProvider extends HttpProvider implements HttpProxyProvider
}
private doProxy(
- path: string,
- query: querystring.ParsedUrlQuery,
+ route: Route,
request: http.IncomingMessage,
response: http.ServerResponse,
portStr: string,
+ base?: string,
): HttpResponse
private doProxy(
- path: string,
- query: querystring.ParsedUrlQuery,
+ route: Route,
request: http.IncomingMessage,
- socket: net.Socket,
- head: Buffer,
+ response: { socket: net.Socket; head: Buffer },
portStr: string,
+ base?: string,
): HttpResponse
private doProxy(
- path: string,
- query: querystring.ParsedUrlQuery,
+ route: Route,
request: http.IncomingMessage,
- responseOrSocket: http.ServerResponse | net.Socket,
- headOrPortStr: Buffer | string,
- portStr?: string,
+ response: http.ServerResponse | { socket: net.Socket; head: Buffer },
+ portStr: string,
+ base?: string,
): HttpResponse {
- const _portStr = typeof headOrPortStr === "string" ? headOrPortStr : portStr
- if (!_portStr) {
- return {
- code: HttpCode.BadRequest,
- content: "Port must be provided",
- }
- }
-
- const port = parseInt(_portStr, 10)
+ const port = parseInt(portStr, 10)
if (isNaN(port)) {
return {
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 = {
- autoRewrite: true,
changeOrigin: true,
ignorePath: true,
- target: `http://127.0.0.1:${port}${path}${
- Object.keys(query).length > 0 ? `?${querystring.stringify(query)}` : ""
+ target: `${hxxp ? "http" : "ws"}://127.0.0.1:${port}${path}${
+ Object.keys(route.query).length > 0 ? `?${querystring.stringify(route.query)}` : ""
}`,
+ ws: !hxxp,
}
- if (responseOrSocket instanceof net.Socket) {
- this.proxy.ws(request, responseOrSocket, headOrPortStr, options)
+ if (response instanceof http.ServerResponse) {
+ this.proxy.web(request, response, options)
} else {
- this.proxy.web(request, responseOrSocket, options)
+ this.proxy.ws(request, response.socket, response.head, options)
}
return { handled: true }
diff --git a/src/node/http.ts b/src/node/http.ts
index 52503308..a3a6ed93 100644
--- a/src/node/http.ts
+++ b/src/node/http.ts
@@ -596,7 +596,7 @@ export class HttpServer {
`Path=${normalize(payload.cookie.path || "/", true)}`,
domain ? `Domain=${(this.proxy && this.proxy.getCookieDomain(domain)) || domain}` : undefined,
// "HttpOnly",
- "SameSite=strict",
+ "SameSite=lax",
]
.filter((l) => !!l)
.join(";"),
@@ -633,9 +633,11 @@ export class HttpServer {
if (error.code === "ENOENT" || error.code === "EISDIR") {
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
+ 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)
write({
code,