11import * as http from "http"
2+ import proxy from "http-proxy"
3+ import * as net from "net"
24import { HttpCode , HttpError } from "../../common/http"
35import { HttpProvider , HttpProviderOptions , HttpProxyProvider , HttpResponse , Route } from "../http"
46
@@ -10,6 +12,7 @@ export class ProxyHttpProvider extends HttpProvider implements HttpProxyProvider
1012 * Proxy domains are stored here without the leading `*.`
1113 */
1214 public readonly proxyDomains : string [ ]
15+ private readonly proxy = proxy . createProxyServer ( { } )
1316
1417 /**
1518 * Domains can be provided in the form `coder.com` or `*.coder.com`. Either
@@ -20,22 +23,37 @@ export class ProxyHttpProvider extends HttpProvider implements HttpProxyProvider
2023 this . proxyDomains = proxyDomains . map ( ( d ) => d . replace ( / ^ \* \. / , "" ) ) . filter ( ( d , i , arr ) => arr . indexOf ( d ) === i )
2124 }
2225
23- public async handleRequest ( route : Route , request : http . IncomingMessage ) : Promise < HttpResponse > {
26+ public async handleRequest (
27+ route : Route ,
28+ request : http . IncomingMessage ,
29+ response : http . ServerResponse ,
30+ ) : Promise < HttpResponse > {
2431 if ( ! this . authenticated ( request ) ) {
25- if ( route . requestPath === "/index.html" ) {
26- return { redirect : "/login" , query : { to : route . fullPath } }
32+ // Only redirect from the root. Other requests get an unauthorized error.
33+ if ( route . requestPath && route . requestPath !== "/index.html" ) {
34+ throw new HttpError ( "Unauthorized" , HttpCode . Unauthorized )
2735 }
28- throw new HttpError ( "Unauthorized ", HttpCode . Unauthorized )
36+ return { redirect : "/login ", query : { to : route . fullPath } }
2937 }
3038
31- const payload = this . proxy ( route . base . replace ( / ^ \/ / , "" ) )
39+ const payload = this . doProxy ( route . requestPath , request , response , route . base . replace ( / ^ \/ / , "" ) )
3240 if ( payload ) {
3341 return payload
3442 }
3543
3644 throw new HttpError ( "Not found" , HttpCode . NotFound )
3745 }
3846
47+ public async handleWebSocket (
48+ route : Route ,
49+ request : http . IncomingMessage ,
50+ socket : net . Socket ,
51+ head : Buffer ,
52+ ) : Promise < void > {
53+ this . ensureAuthenticated ( request )
54+ this . doProxy ( route . requestPath , request , socket , head , route . base . replace ( / ^ \/ / , "" ) )
55+ }
56+
3957 public getCookieDomain ( host : string ) : string {
4058 let current : string | undefined
4159 this . proxyDomains . forEach ( ( domain ) => {
@@ -46,7 +64,26 @@ export class ProxyHttpProvider extends HttpProvider implements HttpProxyProvider
4664 return current || host
4765 }
4866
49- public maybeProxy ( request : http . IncomingMessage ) : HttpResponse | undefined {
67+ public maybeProxyRequest (
68+ route : Route ,
69+ request : http . IncomingMessage ,
70+ response : http . ServerResponse ,
71+ ) : HttpResponse | undefined {
72+ const port = this . getPort ( request )
73+ return port ? this . doProxy ( route . fullPath , request , response , port ) : undefined
74+ }
75+
76+ public maybeProxyWebSocket (
77+ route : Route ,
78+ request : http . IncomingMessage ,
79+ socket : net . Socket ,
80+ head : Buffer ,
81+ ) : HttpResponse | undefined {
82+ const port = this . getPort ( request )
83+ return port ? this . doProxy ( route . fullPath , request , socket , head , port ) : undefined
84+ }
85+
86+ private getPort ( request : http . IncomingMessage ) : string | undefined {
5087 // No proxy until we're authenticated. This will cause the login page to
5188 // show as well as let our assets keep loading normally.
5289 if ( ! this . authenticated ( request ) ) {
@@ -67,26 +104,58 @@ export class ProxyHttpProvider extends HttpProvider implements HttpProxyProvider
67104 return undefined
68105 }
69106
70- return this . proxy ( port )
107+ return port
71108 }
72109
73- private proxy ( portStr : string ) : HttpResponse {
74- if ( ! portStr ) {
110+ private doProxy (
111+ path : string ,
112+ request : http . IncomingMessage ,
113+ response : http . ServerResponse ,
114+ portStr : string ,
115+ ) : HttpResponse
116+ private doProxy (
117+ path : string ,
118+ request : http . IncomingMessage ,
119+ socket : net . Socket ,
120+ head : Buffer ,
121+ portStr : string ,
122+ ) : HttpResponse
123+ private doProxy (
124+ path : string ,
125+ request : http . IncomingMessage ,
126+ responseOrSocket : http . ServerResponse | net . Socket ,
127+ headOrPortStr : Buffer | string ,
128+ portStr ?: string ,
129+ ) : HttpResponse {
130+ const _portStr = typeof headOrPortStr === "string" ? headOrPortStr : portStr
131+ if ( ! _portStr ) {
75132 return {
76133 code : HttpCode . BadRequest ,
77134 content : "Port must be provided" ,
78135 }
79136 }
80- const port = parseInt ( portStr , 10 )
137+
138+ const port = parseInt ( _portStr , 10 )
81139 if ( isNaN ( port ) ) {
82140 return {
83141 code : HttpCode . BadRequest ,
84- content : `"${ portStr } " is not a valid number` ,
142+ content : `"${ _portStr } " is not a valid number` ,
85143 }
86144 }
87- return {
88- code : HttpCode . Ok ,
89- content : `will proxy this to ${ port } ` ,
145+
146+ const options : proxy . ServerOptions = {
147+ autoRewrite : true ,
148+ changeOrigin : true ,
149+ ignorePath : true ,
150+ target : `http://127.0.0.1:${ port } ${ path } ` ,
90151 }
152+
153+ if ( responseOrSocket instanceof net . Socket ) {
154+ this . proxy . ws ( request , responseOrSocket , headOrPortStr , options )
155+ } else {
156+ this . proxy . web ( request , responseOrSocket , options )
157+ }
158+
159+ return { handled : true }
91160 }
92161}
0 commit comments