1
- import { logger } from "@coder/logger"
2
1
import * as http from "http"
3
- import proxy from "http-proxy"
4
- import * as net from "net"
5
- import * as querystring from "querystring"
6
2
import { HttpCode , HttpError } from "../../common/http"
7
- import { HttpProvider , HttpProviderOptions , HttpProxyProvider , HttpResponse , Route } from "../http"
8
-
9
- interface Request extends http . IncomingMessage {
10
- base ?: string
11
- }
3
+ import { HttpProvider , HttpResponse , Route , WsResponse } from "../http"
12
4
13
5
/**
14
6
* Proxy HTTP provider.
15
7
*/
16
- export class ProxyHttpProvider extends HttpProvider implements HttpProxyProvider {
17
- /**
18
- * Proxy domains are stored here without the leading `*.`
19
- */
20
- public readonly proxyDomains : Set < string >
21
- private readonly proxy = proxy . createProxyServer ( { } )
22
-
23
- /**
24
- * Domains can be provided in the form `coder.com` or `*.coder.com`. Either
25
- * way, `<number>.coder.com` will be proxied to `number`.
26
- */
27
- public constructor ( options : HttpProviderOptions , proxyDomains : string [ ] = [ ] ) {
28
- super ( options )
29
- this . proxyDomains = new Set ( proxyDomains . map ( ( d ) => d . replace ( / ^ \* \. / , "" ) ) )
30
- this . proxy . on ( "error" , ( error ) => logger . warn ( error . message ) )
31
- // Intercept the response to rewrite absolute redirects against the base path.
32
- this . proxy . on ( "proxyRes" , ( response , request : Request ) => {
33
- if ( response . headers . location && response . headers . location . startsWith ( "/" ) && request . base ) {
34
- response . headers . location = request . base + response . headers . location
35
- }
36
- } )
37
- }
38
-
39
- public async handleRequest (
40
- route : Route ,
41
- request : http . IncomingMessage ,
42
- response : http . ServerResponse ,
43
- ) : Promise < HttpResponse > {
8
+ export class ProxyHttpProvider extends HttpProvider {
9
+ public async handleRequest ( route : Route , request : http . IncomingMessage ) : Promise < HttpResponse > {
44
10
if ( ! this . authenticated ( request ) ) {
45
11
if ( this . isRoot ( route ) ) {
46
12
return { redirect : "/login" , query : { to : route . fullPath } }
@@ -56,133 +22,22 @@ export class ProxyHttpProvider extends HttpProvider implements HttpProxyProvider
56
22
}
57
23
58
24
const port = route . base . replace ( / ^ \/ / , "" )
59
- const base = `${ this . options . base } /${ port } `
60
- const payload = this . doProxy ( route , request , response , port , base )
61
- if ( payload ) {
62
- return payload
25
+ return {
26
+ proxy : {
27
+ base : `${ this . options . base } /${ port } ` ,
28
+ port,
29
+ } ,
63
30
}
64
-
65
- throw new HttpError ( "Not found" , HttpCode . NotFound )
66
31
}
67
32
68
- public async handleWebSocket (
69
- route : Route ,
70
- request : http . IncomingMessage ,
71
- socket : net . Socket ,
72
- head : Buffer ,
73
- ) : Promise < void > {
33
+ public async handleWebSocket ( route : Route , request : http . IncomingMessage ) : Promise < WsResponse > {
74
34
this . ensureAuthenticated ( request )
75
35
const port = route . base . replace ( / ^ \/ / , "" )
76
- const base = `${ this . options . base } /${ port } `
77
- this . doProxy ( route , request , { socket, head } , port , base )
78
- }
79
-
80
- public getCookieDomain ( host : string ) : string {
81
- let current : string | undefined
82
- this . proxyDomains . forEach ( ( domain ) => {
83
- if ( host . endsWith ( domain ) && ( ! current || domain . length < current . length ) ) {
84
- current = domain
85
- }
86
- } )
87
- // Setting the domain to localhost doesn't seem to work for subdomains (for
88
- // example dev.localhost).
89
- return current && current !== "localhost" ? current : host
90
- }
91
-
92
- public maybeProxyRequest (
93
- route : Route ,
94
- request : http . IncomingMessage ,
95
- response : http . ServerResponse ,
96
- ) : HttpResponse | undefined {
97
- const port = this . getPort ( request )
98
- return port ? this . doProxy ( route , request , response , port ) : undefined
99
- }
100
-
101
- public maybeProxyWebSocket (
102
- route : Route ,
103
- request : http . IncomingMessage ,
104
- socket : net . Socket ,
105
- head : Buffer ,
106
- ) : HttpResponse | undefined {
107
- const port = this . getPort ( request )
108
- return port ? this . doProxy ( route , request , { socket, head } , port ) : undefined
109
- }
110
-
111
- private getPort ( request : http . IncomingMessage ) : string | undefined {
112
- // No proxy until we're authenticated. This will cause the login page to
113
- // show as well as let our assets keep loading normally.
114
- if ( ! this . authenticated ( request ) ) {
115
- return undefined
116
- }
117
-
118
- // Split into parts.
119
- const host = request . headers . host || ""
120
- const idx = host . indexOf ( ":" )
121
- const domain = idx !== - 1 ? host . substring ( 0 , idx ) : host
122
- const parts = domain . split ( "." )
123
-
124
- // There must be an exact match.
125
- const port = parts . shift ( )
126
- const proxyDomain = parts . join ( "." )
127
- if ( ! port || ! this . proxyDomains . has ( proxyDomain ) ) {
128
- return undefined
129
- }
130
-
131
- return port
132
- }
133
-
134
- private doProxy (
135
- route : Route ,
136
- request : http . IncomingMessage ,
137
- response : http . ServerResponse ,
138
- portStr : string ,
139
- base ?: string ,
140
- ) : HttpResponse
141
- private doProxy (
142
- route : Route ,
143
- request : http . IncomingMessage ,
144
- response : { socket : net . Socket ; head : Buffer } ,
145
- portStr : string ,
146
- base ?: string ,
147
- ) : HttpResponse
148
- private doProxy (
149
- route : Route ,
150
- request : http . IncomingMessage ,
151
- response : http . ServerResponse | { socket : net . Socket ; head : Buffer } ,
152
- portStr : string ,
153
- base ?: string ,
154
- ) : HttpResponse {
155
- const port = parseInt ( portStr , 10 )
156
- if ( isNaN ( port ) ) {
157
- return {
158
- code : HttpCode . BadRequest ,
159
- content : `"${ portStr } " is not a valid number` ,
160
- }
161
- }
162
-
163
- // REVIEW: Absolute redirects need to be based on the subpath but I'm not
164
- // sure how best to get this information to the `proxyRes` event handler.
165
- // For now I'm sticking it on the request object which is passed through to
166
- // the event.
167
- ; ( request as Request ) . base = base
168
-
169
- const isHttp = response instanceof http . ServerResponse
170
- const path = base ? route . fullPath . replace ( base , "" ) : route . fullPath
171
- const options : proxy . ServerOptions = {
172
- changeOrigin : true ,
173
- ignorePath : true ,
174
- target : `${ isHttp ? "http" : "ws" } ://127.0.0.1:${ port } ${ path } ${
175
- Object . keys ( route . query ) . length > 0 ? `?${ querystring . stringify ( route . query ) } ` : ""
176
- } `,
177
- ws : ! isHttp ,
178
- }
179
-
180
- if ( response instanceof http . ServerResponse ) {
181
- this . proxy . web ( request , response , options )
182
- } else {
183
- this . proxy . ws ( request , response . socket , response . head , options )
36
+ return {
37
+ proxy : {
38
+ base : `${ this . options . base } /${ port } ` ,
39
+ port,
40
+ } ,
184
41
}
185
-
186
- return { handled : true }
187
42
}
188
43
}
0 commit comments