@@ -29,6 +29,12 @@ def get(self, *args):
2929 self .redirect (urlunparse (dest ))
3030
3131class LocalProxyHandler (WebSocketHandlerMixin , IPythonHandler ):
32+
33+ def __init__ (self , * args , ** kwargs ):
34+ self .proxy_base = ''
35+ self .absolute_url = kwargs .pop ('absolute_url' , False )
36+ super ().__init__ (* args , ** kwargs )
37+
3238 async def open (self , port , proxied_path = '' ):
3339 """
3440 Called when a client opens a websocket connection.
@@ -39,13 +45,7 @@ async def open(self, port, proxied_path=''):
3945 if not proxied_path .startswith ('/' ):
4046 proxied_path = '/' + proxied_path
4147
42- client_uri = '{uri}:{port}{path}' .format (
43- uri = 'ws://127.0.0.1' ,
44- port = port ,
45- path = proxied_path
46- )
47- if self .request .query :
48- client_uri += '?' + self .request .query
48+ client_uri = self .get_client_uri ('ws' , port , proxied_path )
4949 headers = self .request .headers
5050
5151 def message_cb (message ):
@@ -126,14 +126,63 @@ def _record_activity(self):
126126 """
127127 self .settings ['api_last_activity' ] = utcnow ()
128128
129+ def _get_context_path (self , port ):
130+ """
131+ Some applications need to know where they are being proxied from.
132+ This is either:
133+ - {base_url}/proxy/{port}
134+ - {base_url}/proxy/absolute/{port}
135+ - {base_url}/{proxy_base}
136+ """
137+ if self .proxy_base :
138+ return url_path_join (self .base_url , self .proxy_base )
139+ if self .absolute_url :
140+ return url_path_join (self .base_url , 'proxy' , 'absolute' , str (port ))
141+ else :
142+ return url_path_join (self .base_url , 'proxy' , str (port ))
143+
144+ def get_client_uri (self , protocol , port , proxied_path ):
145+ context_path = self ._get_context_path (port )
146+ if self .absolute_url :
147+ client_path = url_path_join (context_path , proxied_path )
148+ else :
149+ client_path = proxied_path
150+
151+ client_uri = '{protocol}://{host}:{port}{path}' .format (
152+ protocol = protocol ,
153+ host = 'localhost' ,
154+ port = port ,
155+ path = client_path
156+ )
157+ if self .request .query :
158+ client_uri += '?' + self .request .query
159+
160+ return client_uri
161+
162+ def _build_proxy_request (self , port , proxied_path , body ):
163+
164+ headers = self .proxy_request_headers ()
165+
166+ client_uri = self .get_client_uri ('http' , port , proxied_path )
167+ # Some applications check X-Forwarded-Context and X-ProxyContextPath
168+ # headers to see if and where they are being proxied from.
169+ if not self .absolute_url :
170+ context_path = self ._get_context_path (port )
171+ headers ['X-Forwarded-Context' ] = context_path
172+ headers ['X-ProxyContextPath' ] = context_path
173+
174+ req = httpclient .HTTPRequest (
175+ client_uri , method = self .request .method , body = body ,
176+ headers = headers , ** self .proxy_request_options ())
177+ return req
129178
130179 @web .authenticated
131180 async def proxy (self , port , proxied_path ):
132181 '''
133- While self.request.uri is
134- (hub) /user/username/ proxy/([0-9]+)/something.
135- (single) /proxy/([0-9]+)/something
136- This serverextension is given {port }/{everything/after}.
182+ This serverextension handles:
183+ {base_url}/ proxy/{port ([0-9]+)}/{proxied_path}
184+ {base_url} /proxy/absolute/{port ([0-9]+)}/{proxied_path}
185+ {base_url }/{proxy_base}/{proxied_path}
137186 '''
138187
139188 if 'Proxy-Connection' in self .request .headers :
@@ -154,29 +203,9 @@ async def proxy(self, port, proxied_path):
154203 else :
155204 body = None
156205
157- client_uri = '{uri}:{port}{path}' .format (
158- uri = 'http://localhost' ,
159- port = port ,
160- path = proxied_path
161- )
162- if self .request .query :
163- client_uri += '?' + self .request .query
164-
165206 client = httpclient .AsyncHTTPClient ()
166207
167- headers = self .proxy_request_headers ()
168-
169- # Some applications check X-Forwarded-Context and X-ProxyContextPath
170- # headers to see if and where they are being proxied from. We set
171- # them to be {base_url}/proxy/{port}.
172- headers ['X-Forwarded-Context' ] = headers ['X-ProxyContextPath' ] = \
173- url_path_join (self .base_url , 'proxy' , str (port ))
174-
175- req = httpclient .HTTPRequest (
176- client_uri , method = self .request .method , body = body ,
177- headers = headers ,
178- ** self .proxy_request_options ())
179-
208+ req = self ._build_proxy_request (port , proxied_path , body )
180209 response = await client .fetch (req , raise_error = False )
181210 # record activity at start and end of requests
182211 self ._record_activity ()
@@ -378,10 +407,14 @@ def patch(self, path):
378407 def options (self , path ):
379408 return self .proxy (self .port , path )
380409
410+
381411def setup_handlers (web_app ):
382412 host_pattern = '.*$'
383413 web_app .add_handlers ('.*' , [
384- (url_path_join (web_app .settings ['base_url' ], r'/proxy/(\d+)(.*)' ), LocalProxyHandler )
414+ (url_path_join (web_app .settings ['base_url' ], r'/proxy/(\d+)(.*)' ),
415+ LocalProxyHandler , {'absolute_url' : False }),
416+ (url_path_join (web_app .settings ['base_url' ], r'/proxy/absolute/(\d+)(.*)' ),
417+ LocalProxyHandler , {'absolute_url' : True }),
385418 ])
386419
387420# vim: set et ts=4 sw=4:
0 commit comments