def wms_structure(wms_url: Url, host: str, request: pyramid.request.Request) -> Dict[str, List[str]]: """Get a simple serializable structure of the WMS capabilities.""" url = wms_url.clone().add_query({ "SERVICE": "WMS", "VERSION": "1.1.1", "REQUEST": "GetCapabilities" }) # Forward request to target (without Host Header) headers = {} if url.hostname == "localhost" and host is not None: headers["Host"] = host try: response = requests.get(url.url(), headers=headers, **request.registry.settings.get( "http_options", {})) except Exception: LOG.exception("Unable to GetCapabilities from wms_url '%s'", wms_url) raise HTTPBadGateway( # pylint: disable=raise-missing-from "Unable to GetCapabilities, see logs for details") if not response.ok: raise HTTPBadGateway( f"GetCapabilities from wms_url {url.url()} return the error: " f"{response.status_code:d} {response.reason}") try: wms = WebMapService(None, xml=response.content) result: Dict[str, List[str]] = {} def _fill(name: str, parent: ContentMetadata) -> None: if parent is None: return if parent.name not in result: result[parent.name] = [] result[parent.name].append(name) _fill(name, parent.parent) for layer in list(wms.contents.values()): _fill(layer.name, layer.parent) return result except AttributeError: error = "WARNING! an error occurred while trying to read the mapfile and recover the themes." error = f"{error}\nurl: {wms_url}\nxml:\n{response.text}" LOG.exception(error) raise HTTPBadGateway(error) # pylint: disable=raise-missing-from except SyntaxError: error = "WARNING! an error occurred while trying to read the mapfile and recover the themes." error = f"{error}\nurl: {wms_url}\nxml:\n{response.text}" LOG.exception(error) raise HTTPBadGateway(error) # pylint: disable=raise-missing-from
def wms_structure(wms_url: Url, host: str, request: pyramid.request.Request) -> Dict[str, List[str]]: url = wms_url.clone().add_query({"SERVICE": "WMS", "VERSION": "1.1.1", "REQUEST": "GetCapabilities"}) # Forward request to target (without Host Header) headers = dict() if url.hostname == "localhost" and host is not None: headers["Host"] = host try: response = requests.get( url.url(), headers=headers, **request.registry.settings.get("http_options", {}) ) except Exception: raise HTTPBadGateway("Unable to GetCapabilities from wms_url {}".format(wms_url)) if not response.ok: raise HTTPBadGateway( "GetCapabilities from wms_url {} return the error: {:d} {}".format( url.url(), response.status_code, response.reason ) ) try: wms = WebMapService(None, xml=response.content) result: Dict[str, List[str]] = {} def _fill(name: str, parent: ContentMetadata) -> None: if parent is None: return if parent.name not in result: result[parent.name] = [] result[parent.name].append(name) _fill(name, parent.parent) for layer in list(wms.contents.values()): _fill(layer.name, layer.parent) return result except AttributeError: error = "WARNING! an error occurred while trying to read the mapfile and recover the themes." error = "{0!s}\nurl: {1!s}\nxml:\n{2!s}".format(error, wms_url, response.text) LOG.exception(error) raise HTTPBadGateway(error) except SyntaxError: error = "WARNING! an error occurred while trying to read the mapfile and recover the themes." error = "{0!s}\nurl: {1!s}\nxml:\n{2!s}".format(error, wms_url, response.text) LOG.exception(error) raise HTTPBadGateway(error)
def _proxy_callback( self, cache_control: Cache, url: Url, params: Dict[str, str], **kwargs: Any ) -> Response: if self.request.matched_route.name.endswith("_path"): if self.request.matchdict["path"] == ("favicon.ico",): return HTTPFound("/favicon.ico") url = url.clone() url.path = self.request.path response = self._proxy(url=url, params=params, **kwargs) content = response.content if self.lower_params.get("request") == "getcapabilities": content = filter_capabilities( response.text, self.lower_params.get("service") == "wms", url, self.request.headers, self.request, ).encode("utf-8") content_type = response.headers["Content-Type"] return self._build_response(response, content, cache_control, "mapserver", content_type=content_type)
def _proxy( self, url: Url, params: Dict[str, str] = None, method: str = None, cache: bool = False, body: bytes = None, headers: Dict[str, str] = None, ) -> pyramid.response.Response: # Get query string params = dict(self.request.params) if params is None else params url = url.clone().add_query(params, True) LOG.debug("Send query to URL:\n%s.", url) if method is None: method = self.request.method if headers is None: headers = dict(self.request.headers) # Forward request to target (without Host Header) if url.hostname not in self.host_forward_host and "Host" in headers: headers.pop("Host") # Forward the request tracking ID to the other service. This will allow to follow the logs belonging # to a single request coming from the user headers.setdefault("X-Request-ID", self.request.c2c_request_id) # If we releay want to respect the specification, we should chain with the content of the previous # proxy, see also: # https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Forwarded forwarded = { "for": self.request.client_addr, "proto": self.request.scheme } if "Host" in self.request.headers: forwarded["host"] = self.request.headers["Host"] forwarded_str = ";".join(["=".join(e) for e in forwarded.items()]) if "Forwarded" in headers: headers["Forwarded"] = ",".join( [headers["Forwarded"], forwarded_str]) else: headers["Forwarded"] = forwarded_str if not cache: headers["Cache-Control"] = "no-cache" if method in ("POST", "PUT") and body is None: body = self.request.body try: if method in ("POST", "PUT"): response = requests.request(method, url.url(), data=body, headers=headers, **self.http_options) else: response = requests.request(method, url.url(), headers=headers, **self.http_options) except Exception: errors = [ "Error '%s' while getting the URL:", "%s", "Method: %s", "--- With headers ---", "%s" ] args1 = [ sys.exc_info()[0], url, method, "\n".join([ "{}: {}".format( h, v if h not in ("Authorization", "Cookies") else "***") for h, v in list(headers.items()) ]), ] if method in ("POST", "PUT") and body is not None: errors += ["--- Query with body ---", "%s"] args1.append(body.decode("utf-8")) LOG.error("\n".join(errors), *args1, exc_info=True) raise HTTPBadGateway("Error on backend, See logs for detail") if not response.ok: errors = [ "Error '%s' in response of URL:", "%s", "Status: %d", "Method: %s", "--- With headers ---", "%s", ] args2: List[Union[str, int]] = [ response.reason, url.url(), response.status_code, method, "\n".join([ "{}: {}".format( h, v if h not in ("Authorization", "Cookies") else "***") for h, v in list(headers.items()) ]), ] if method in ("POST", "PUT") and body is not None: errors += ["--- Query with body ---", "%s"] args2.append(body.decode("utf-8")) errors += ["--- Return content ---", "%s"] args2.append(response.text) LOG.error("\n".join(errors), *args2) raise exception_response(response.status_code) return response
def _proxy( self, url: Url, params: Optional[Dict[str, str]] = None, method: Optional[str] = None, cache: bool = False, body: Optional[bytes] = None, headers: Optional[Dict[str, str]] = None, ) -> requests.models.Response: # Get query string params = dict(self.request.params) if params is None else params url = url.clone().add_query(params, True) LOG.debug("Send query to URL:\n%s.", url) if method is None: method = self.request.method headers = dict(self.request.headers if headers is None else headers) # Forward request to target (without Host Header). # The original Host will be added back by pyramid. if url.hostname not in self.host_forward_host and "Host" in headers: headers.pop("Host") # Forward the request tracking ID to the other service. This will allow to follow the logs belonging # to a single request coming from the user headers.setdefault("X-Request-ID", self.request.c2c_request_id) # If we really want to respect the specification, we should chain with the content of the previous # proxy, see also: # https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Forwarded forwarded = { "for": self.request.client_addr, "proto": self.request.scheme } if "Host" in self.request.headers: forwarded["host"] = self.request.headers["Host"] forwarded_str = ";".join(["=".join(e) for e in forwarded.items()]) if "Forwarded" in headers: headers["Forwarded"] = ",".join( [headers["Forwarded"], forwarded_str]) else: headers["Forwarded"] = forwarded_str # Set alternative "X-Forwarded" headers for forwarded_elements in reversed(headers["Forwarded"].split(",")): for element in forwarded_elements.split(";"): key, value = element.split("=") header_key = f"X-Forwarded-{key.capitalize()}" header_value = headers.get(header_key) headers[ header_key] = value if header_value is None else ", ".join( [header_value, value]) if not cache: headers["Cache-Control"] = "no-cache" if method in ("POST", "PUT") and body is None: body = self.request.body headers = restrict_headers(headers, self.headers_whitelist, self.headers_blacklist) try: if method in ("POST", "PUT"): response = requests.request(method, url.url(), data=body, headers=headers, **self.http_options) else: response = requests.request(method, url.url(), headers=headers, **self.http_options) except Exception: errors = [ "Error '%s' while getting the URL:", "%s", "Method: %s", "--- With headers ---", "%s" ] args1 = [ sys.exc_info()[0], url, method, "\n".join([ f"{h}: {v if h not in ('Authorization', 'Cookies') else '***'}" for h, v in list(headers.items()) ]), ] if method in ("POST", "PUT") and body is not None: errors += ["--- Query with body ---", "%s"] args1.append(body.decode("utf-8")) LOG.exception("\n".join(errors), *args1) raise HTTPBadGateway( # pylint: disable=raise-missing-from "Error on backend, See logs for detail") if not response.ok: errors = [ "Error '%s' in response of URL:", "%s", "Status: %d", "Method: %s", "--- With headers ---", "%s", ] args2: List[Union[str, int]] = [ response.reason, url.url(), response.status_code, method, "\n".join([ f"{h}: {v if h not in ('Authorization', 'Cookies') else '***'}" for h, v in list(headers.items()) ]), ] if method in ("POST", "PUT") and body is not None: errors += ["--- Query with body ---", "%s"] args2.append(body.decode("utf-8")) errors += ["--- Return content ---", "%s"] args2.append(response.text) LOG.error("\n".join(errors), *args2) raise exception_response(response.status_code) if not response.headers.get("Content-Type", "").startswith("image/"): LOG.debug("Get result for URL: %s:\n%s.", url, body) return response