import requests from django.conf import settings from django.http import HttpResponse from django.utils.decorators import method_decorator from django.views import View from django.views.decorators.csrf import csrf_exempt from utils.http_fallback import request_with_ssl_fallback # Headers that must not be copied verbatim between hops (RFC 7230 6.1) plus # framing headers that HttpResponse recomputes itself. HOP_BY_HOP_HEADERS = { "connection", "keep-alive", "proxy-authenticate", "proxy-authorization", "te", "trailers", "transfer-encoding", "upgrade", "content-encoding", "content-length", } def _incoming_headers(request): headers = {} for key, value in request.META.items(): if key.startswith("HTTP_"): name = key[5:].replace("_", "-").title() elif key in ("CONTENT_TYPE", "CONTENT_LENGTH") and value: name = key.replace("_", "-").title() else: continue headers[name] = value headers.pop("Host", None) return headers @method_decorator(csrf_exempt, name="dispatch") class ServiceProxyView(View): """Forwards `//` to the configured base URL for `service`.""" def dispatch(self, request, service, rest="", *args, **kwargs): base_url = settings.PROXY_SERVICE_BASE_URLS.get(service) if not base_url: return HttpResponse( f'Unknown or unconfigured service "{service}".', status=404, content_type="text/plain", ) target_url = f"{base_url.rstrip('/')}/{rest.lstrip('/')}" query_string = request.META.get("QUERY_STRING", "") if query_string: target_url = f"{target_url}?{query_string}" try: upstream = request_with_ssl_fallback( request.method, target_url, headers=_incoming_headers(request), data=request.body, timeout=settings.SERVICE_REQUEST_TIMEOUT, allow_redirects=False, ) except requests.RequestException as exc: return HttpResponse( f'Error contacting upstream service "{service}": {exc}', status=502, content_type="text/plain", ) response = HttpResponse(content=upstream.content, status=upstream.status_code) for name, value in upstream.headers.items(): if name.lower() in HOP_BY_HOP_HEADERS: continue response[name] = value return response