1
1
import * as http from "http"
2
+ import proxy from "http-proxy"
3
+ import * as net from "net"
2
4
import { HttpCode , HttpError } from "../../common/http"
3
5
import { HttpProvider , HttpProviderOptions , HttpProxyProvider , HttpResponse , Route } from "../http"
4
6
@@ -10,6 +12,7 @@ export class ProxyHttpProvider extends HttpProvider implements HttpProxyProvider
10
12
* Proxy domains are stored here without the leading `*.`
11
13
*/
12
14
public readonly proxyDomains : string [ ]
15
+ private readonly proxy = proxy . createProxyServer ( { } )
13
16
14
17
/**
15
18
* Domains can be provided in the form `coder.com` or `*.coder.com`. Either
@@ -20,22 +23,37 @@ export class ProxyHttpProvider extends HttpProvider implements HttpProxyProvider
20
23
this . proxyDomains = proxyDomains . map ( ( d ) => d . replace ( / ^ \* \. / , "" ) ) . filter ( ( d , i , arr ) => arr . indexOf ( d ) === i )
21
24
}
22
25
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 > {
24
31
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 )
27
35
}
28
- throw new HttpError ( "Unauthorized ", HttpCode . Unauthorized )
36
+ return { redirect : "/login ", query : { to : route . fullPath } }
29
37
}
30
38
31
- const payload = this . proxy ( route . base . replace ( / ^ \/ / , "" ) )
39
+ const payload = this . doProxy ( route . requestPath , request , response , route . base . replace ( / ^ \/ / , "" ) )
32
40
if ( payload ) {
33
41
return payload
34
42
}
35
43
36
44
throw new HttpError ( "Not found" , HttpCode . NotFound )
37
45
}
38
46
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
+
39
57
public getCookieDomain ( host : string ) : string {
40
58
let current : string | undefined
41
59
this . proxyDomains . forEach ( ( domain ) => {
@@ -46,7 +64,26 @@ export class ProxyHttpProvider extends HttpProvider implements HttpProxyProvider
46
64
return current || host
47
65
}
48
66
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 {
50
87
// No proxy until we're authenticated. This will cause the login page to
51
88
// show as well as let our assets keep loading normally.
52
89
if ( ! this . authenticated ( request ) ) {
@@ -67,26 +104,58 @@ export class ProxyHttpProvider extends HttpProvider implements HttpProxyProvider
67
104
return undefined
68
105
}
69
106
70
- return this . proxy ( port )
107
+ return port
71
108
}
72
109
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 ) {
75
132
return {
76
133
code : HttpCode . BadRequest ,
77
134
content : "Port must be provided" ,
78
135
}
79
136
}
80
- const port = parseInt ( portStr , 10 )
137
+
138
+ const port = parseInt ( _portStr , 10 )
81
139
if ( isNaN ( port ) ) {
82
140
return {
83
141
code : HttpCode . BadRequest ,
84
- content : `"${ portStr } " is not a valid number` ,
142
+ content : `"${ _portStr } " is not a valid number` ,
85
143
}
86
144
}
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 } ` ,
90
151
}
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 }
91
160
}
92
161
}
0 commit comments