def proxy_to_wsgi(self, request, wsgi_app): """ Forward a request to an inner wsgi app """ orig_base = url_normalize(request.application_url) ## FIXME: should this be request.copy()? proxy_req = Request(request.environ.copy()) resp = proxy_req.get_response(wsgi_app) return resp, orig_base, None, None
def forward_request(self, environ, start_response): """Forward this request to the remote server, or serve locally. This also applies all the request and response transformations. """ request = Request(environ) prefix = self.match.strip_prefix() if prefix: if prefix.endswith('/'): prefix = prefix[:-1] path_info = request.path_info if not path_info.startswith(prefix + '/') and not path_info == prefix: log = environ['deliverance.log'] log.warn( self, "The match would strip the prefix %r from the request " "path (%r), but they do not match" % (prefix + '/', path_info)) else: request.script_name = request.script_name + prefix request.path_info = path_info[len(prefix):] log = request.environ['deliverance.log'] for modifier in self.request_modifications: request = modifier.modify_request(request, log) if self.dest and self.dest.__next__: raise AbortProxy dest, wsgiapp = None, None if self.dest: dest = self.dest(request, log) log.debug(self, '<proxy> matched; forwarding request to %s' % dest) else: wsgi_app = self.wsgi(request, log) log.debug(self, '<proxy> matched; forwarding request to %s' % wsgi_app) if self.classes: log.debug(self, 'Adding class="%s" to page' % ' '.join(self.classes)) existing_classes = request.environ.setdefault( 'deliverance.page_classes', []) existing_classes.extend(self.classes) if dest is not None: response, orig_base, proxied_base, proxied_url = self.proxy_to_dest( request, dest) else: ## FIXME: proxied_base and proxied_url don't really have a meaning here, ## but the modifier signature expects them response, orig_base, proxied_base, proxied_url = self.proxy_to_wsgi( request, wsgi_app) for modifier in self.response_modifications: response = modifier.modify_response(request, response, orig_base, proxied_base, proxied_url, log) return response(environ, start_response)
def application(self, environ, start_response): """The full application, that routes into the ruleset then out through the proxies itself. """ req = Request(environ) log = SavingLogger(req, self.deliverator) req.environ['deliverance.log'] = log if req.path_info.startswith('/.deliverance/proxy-editor/'): req.path_info_pop() req.path_info_pop() return self.proxy_editor(environ, start_response) return self.deliverator(environ, start_response)
def application(self, environ, start_response): """The full application, that routes into the ruleset then out through the proxies itself. """ req = Request(environ) log = SavingLogger(req, self.deliverator) req.environ["deliverance.log"] = log if req.path_info.startswith("/.deliverance/proxy-editor/"): req.path_info_pop() req.path_info_pop() return self.proxy_editor(environ, start_response) return self.deliverator(environ, start_response)
def forward_request(self, environ, start_response): """Forward this request to the remote server, or serve locally. This also applies all the request and response transformations. """ request = Request(environ) prefix = self.match.strip_prefix() if prefix: if prefix.endswith('/'): prefix = prefix[:-1] path_info = request.path_info if not path_info.startswith(prefix + '/') and not path_info == prefix: log = environ['deliverance.log'] log.warn( self, "The match would strip the prefix %r from the request " "path (%r), but they do not match" % (prefix + '/', path_info)) else: request.script_name = request.script_name + prefix request.path_info = path_info[len(prefix):] log = request.environ['deliverance.log'] for modifier in self.request_modifications: request = modifier.modify_request(request, log) if self.dest and self.dest.next: raise AbortProxy dest, wsgiapp = None, None if self.dest: dest = self.dest(request, log) log.debug(self, '<proxy> matched; forwarding request to %s' % dest) else: wsgi_app = self.wsgi(request, log) log.debug(self, '<proxy> matched; forwarding request to %s' % wsgi_app) if self.classes: log.debug(self, 'Adding class="%s" to page' % ' '.join(self.classes)) existing_classes = request.environ.setdefault('deliverance.page_classes', []) existing_classes.extend(self.classes) if dest is not None: response, orig_base, proxied_base, proxied_url = self.proxy_to_dest(request, dest) else: ## FIXME: proxied_base and proxied_url don't really have a meaning here, ## but the modifier signature expects them response, orig_base, proxied_base, proxied_url = self.proxy_to_wsgi(request, wsgi_app) for modifier in self.response_modifications: response = modifier.modify_response(request, response, orig_base, proxied_base, proxied_url, log) return response(environ, start_response)
def proxy_app(self, environ, start_response): """Implements the proxy, finding the matching `Proxy` object and forwarding the request on to that. """ request = Request(environ) log = environ['deliverance.log'] for index, proxy in enumerate(self.proxies): if proxy.editable: url = request.application_url + '/.deliverance/proxy-editor/%s/' % ( index + 1) name = proxy.editable_name if (url, name) not in log.edit_urls: log.edit_urls.append((url, name)) ## FIXME: obviously this is wonky: if proxy.match(request, None, None, log): try: return proxy.forward_request(environ, start_response) except AbortProxy as e: log.debug(self, '<proxy> aborted (%s), trying next proxy' % e) continue ## FIXME: should also allow for AbortTheme? log.error( self, 'No proxy matched the request; aborting with a 404 Not Found error' ) ## FIXME: better error handling would be nice: resp = exc.HTTPNotFound() return resp(environ, start_response)
def proxy_to_dest(self, request, dest): """Do the actual proxying, without applying any transformations""" # We need to remove caching headers, since the upstream parts of Deliverance # can't handle Not-Modified responses. # Not using request.copy because I don't want to copy wsgi.input request = Request(request.environ.copy()) request.remove_conditional_headers() try: proxy_req = self.construct_proxy_request(request, dest) except TypeError: return self.proxy_to_file(request, dest) proxy_req.path_info += request.path_info if proxy_req.query_string and request.query_string: proxy_req.query_string = '%s&%s' % \ (proxy_req.query_string, request.query_string) elif request.query_string: proxy_req.query_string = request.query_string proxy_req.accept_encoding = None try: resp = proxy_req.get_response(proxy_exact_request) if resp.status_int == 500: print 'Request:' print proxy_req print 'Response:' print resp except socket.error, e: ## FIXME: really wsgiproxy should handle this ## FIXME: which error? ## 502 HTTPBadGateway, 503 HTTPServiceUnavailable, 504 HTTPGatewayTimeout? if isinstance(e.args, tuple) and len(e.args) > 1: error = e.args[1] else: error = str(e) resp = exc.HTTPServiceUnavailable( 'Could not proxy the request to %s:%s : %s' % (proxy_req.server_name, proxy_req.server_port, error))
def modify_request(self, request, log): """Apply the modification to the request""" if self.pyref: if not execute_pyref(request): log.error(self, "Security disallows executing pyref %s" % self.pyref) else: result = self.pyref(request, log) if isinstance(result, dict): request = Request(result) elif isinstance(result, Request): request = result if self.header: request.headers[self.header] = self.content return request
def forward_request(self, environ, start_response): """Forward this request to the remote server, or serve locally. This also applies all the request and response transformations. """ request = Request(environ) prefix = self.match.strip_prefix() if prefix: if prefix.endswith("/"): prefix = prefix[:-1] path_info = request.path_info if not path_info.startswith(prefix + "/") and not path_info == prefix: log = environ["deliverance.log"] log.warn( self, "The match would strip the prefix %r from the request " "path (%r), but they do not match" % (prefix + "/", path_info), ) else: request.script_name = request.script_name + prefix request.path_info = path_info[len(prefix) :] log = request.environ["deliverance.log"] for modifier in self.request_modifications: request = modifier.modify_request(request, log) if self.dest.next: raise AbortProxy dest = self.dest(request, log) log.debug(self, "<proxy> matched; forwarding request to %s" % dest) if self.classes: log.debug(self, 'Adding class="%s" to page' % " ".join(self.classes)) existing_classes = request.environ.setdefault("deliverance.page_classes", []) existing_classes.extend(self.classes) response, orig_base, proxied_base, proxied_url = self.proxy_to_dest(request, dest) for modifier in self.response_modifications: response = modifier.modify_response(request, response, orig_base, proxied_base, proxied_url, log) return response(environ, start_response)
def proxy_to_dest(self, request, dest): """Do the actual proxying, without applying any transformations""" # We need to remove caching headers, since the upstream parts of Deliverance # can't handle Not-Modified responses. # Not using request.copy because I don't want to copy wsgi.input request = Request(request.environ.copy()) request.remove_conditional_headers() try: proxy_req = self.construct_proxy_request(request, dest) except TypeError: return self.proxy_to_file(request, dest) proxy_req.path_info += request.path_info if proxy_req.query_string and request.query_string: proxy_req.query_string = '%s&%s' % \ (proxy_req.query_string, request.query_string) elif request.query_string: proxy_req.query_string = request.query_string proxy_req.accept_encoding = None try: resp = proxy_req.get_response(proxy_exact_request) if resp.status_int == 500: print('Request:') print(proxy_req) print('Response:') print(resp) except socket.error as e: ## FIXME: really wsgiproxy should handle this ## FIXME: which error? ## 502 HTTPBadGateway, 503 HTTPServiceUnavailable, 504 HTTPGatewayTimeout? if isinstance(e.args, tuple) and len(e.args) > 1: error = e.args[1] else: error = str(e) resp = exc.HTTPServiceUnavailable( 'Could not proxy the request to %s:%s : %s' % (proxy_req.server_name, proxy_req.server_port, error)) dest = url_normalize(dest) orig_base = url_normalize(request.application_url) proxied_url = url_normalize( '%s://%s%s' % (proxy_req.scheme, proxy_req.host, proxy_req.path_qs)) return resp, orig_base, dest, proxied_url
def proxy_app(self, environ, start_response): """Implements the proxy, finding the matching `Proxy` object and forwarding the request on to that. """ request = Request(environ) log = environ['deliverance.log'] for index, proxy in enumerate(self.proxies): if proxy.editable: url = request.application_url + '/.deliverance/proxy-editor/%s/' % (index+1) name = proxy.editable_name if (url, name) not in log.edit_urls: log.edit_urls.append((url, name)) ## FIXME: obviously this is wonky: if proxy.match(request, None, None, log): try: return proxy.forward_request(environ, start_response) except AbortProxy, e: log.debug( self, '<proxy> aborted (%s), trying next proxy' % e) continue
def construct_proxy_request(self, request, dest): """ returns a new Request object constructed by copying `request` and replacing its url with the url passed in as `dest` @raises TypeError if `dest` is a file:// url; this can be caught by the caller and handled accordingly """ dest = url_normalize(dest) scheme, netloc, path, query, fragment = urllib.parse.urlsplit(dest) path = urllib.parse.unquote(path) assert not fragment, ("Unexpected fragment: %r" % fragment) proxy_req = Request(request.environ.copy()) proxy_req.path_info = path proxy_req.server_name = netloc.split(':', 1)[0] if ':' in netloc: proxy_req.server_port = netloc.split(':', 1)[1] elif scheme == 'http': proxy_req.server_port = '80' elif scheme == 'https': proxy_req.server_port = '443' elif scheme == 'file': raise TypeError ## FIXME: is TypeError too general? else: assert 0, "bad scheme: %r (from %r)" % (scheme, dest) if not self.keep_host: proxy_req.host = netloc proxy_req.query_string = query proxy_req.scheme = scheme proxy_req.headers['X-Forwarded-For'] = request.remote_addr proxy_req.headers['X-Forwarded-Scheme'] = request.scheme proxy_req.headers['X-Forwarded-Server'] = request.host ## FIXME: something with path? proxy_req.headers['X-Forwarded-Path'] ## (now we are only doing it with strip_script_name) if self.strip_script_name: proxy_req.headers['X-Forwarded-Path'] = proxy_req.script_name proxy_req.script_name = '' return proxy_req
def proxy_editor(self, environ, start_response): req = Request(environ) proxy = self.proxies[int(req.path_info_pop()) - 1] return proxy.edit_app(environ, start_response)
def construct_proxy_request(self, request, dest): """ returns a new Request object constructed by copying `request` and replacing its url with the url passed in as `dest` @raises TypeError if `dest` is a file:// url; this can be caught by the caller and handled accordingly """ dest = url_normalize(dest) scheme, netloc, path, query, fragment = urlparse.urlsplit(dest) path = urllib.unquote(path) assert not fragment, "Unexpected fragment: %r" % fragment proxy_req = Request(request.environ.copy()) proxy_req.path_info = path proxy_req.server_name = netloc.split(":", 1)[0] if ":" in netloc: proxy_req.server_port = netloc.split(":", 1)[1] elif scheme == "http": proxy_req.server_port = "80" elif scheme == "https": proxy_req.server_port = "443" elif scheme == "file": raise TypeError ## FIXME: is TypeError too general? else: assert 0, "bad scheme: %r (from %r)" % (scheme, dest) if not self.keep_host: proxy_req.host = netloc proxy_req.query_string = query proxy_req.scheme = scheme proxy_req.headers["X-Forwarded-For"] = request.remote_addr proxy_req.headers["X-Forwarded-Scheme"] = request.scheme proxy_req.headers["X-Forwarded-Server"] = request.host ## FIXME: something with path? proxy_req.headers['X-Forwarded-Path'] ## (now we are only doing it with strip_script_name) if self.strip_script_name: proxy_req.headers["X-Forwarded-Path"] = proxy_req.script_name proxy_req.script_name = "" return proxy_req