79 lines
2.5 KiB
Python
79 lines
2.5 KiB
Python
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 `/<service>/<rest>` 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
|