Django reverse()在API Gateway/代理后面

huangapple go评论72阅读模式
英文:

Django reverse() behind API Gateway/Proxy

问题

我的Django REST API部署在一个API网关(Kong)后面。
我想在我的API视图(APIViews)中使用reserve()来保留一些URL。
我想要请求帮助以获取正确的URL格式。
基于API网关的基本路径。

通信流程:
客户端(请求基本路径) <-> Kong(转发到上游) <-> Apache(反向代理) <-> Django

Kong定义了一个基本路径和一个上游,以将客户端请求转发给Django。
Kong在HTTP头中包含了X_FORWARDED_HOSTX_FORWARDED_PATH
X_FORWARDED_PATH包含网关的基本路径。
X_FORWARDED_HOST包含网关的URL。

网关基本路径是:
/gateway-basepath

上游路径是:
mydomain.com/py/api/v1

基本上,没有网关,Django的reverse()users端点创建以下URL:
mydomain.com/py/api/v1/users/

在API网关的情况下,Django创建了以下路径:
apigatewayurl.com/gateway-basepath/py/api/v1/users/
Django考虑了X_FORWARDED_HOST,但没有考虑X_FORWARDED_PATH

我需要以下结果:
apigatewayurl.com/gateway-basepath/users
否则,在API网关内无法使用Django URL解析。

我会感激任何帮助。

urls.py

from rest_framework.views import APIView
from rest_framework import routers

from . import views

class APIRootView(APIView):
    def get(self, request, format=None):
        return Response({
            'users': reverse('user-list', request=request, format=format),
        })

router = routers.DefaultRouter()
router.register(r'users', views.UserViewSet)

urlpatterns = [
    path('api/v1/', APIRootView.as_view(), name="api_root"),
]

urlpatterns += router.urls

views.py

from rest_framework import viewsets
from django.contrib.auth import models as django_models

from .serializers import UserSerializer

class UserViewSet(viewsets.ModelViewSet):
    queryset = django_models.User.objects.all()
    serializer_class = UserSerializer

serializers.py

from rest_framework import serializers
from django.contrib.auth.models import User

class UserSerializer(serializers.HyperlinkedModelSerializer):
    class Meta:
        model = User
        fields = ["url", "username", "email", "is_staff"]
英文:

My Django REST API is deployed behind an API Gateway (Kong).
I want to reserve() some urls in my APIViews.
I would like to ask for help to get the right url format.
Based on basepath of the API gateway.

Communication flow:
Client (requesting basepath) <-> Kong (forwarding to upstream) <-> Apache (Reverse Proxy) <-> Django

Kong defines a basepath and an upstream to forward client request to Django.
Kong includes X_FORWARDED_HOST and X_FORWARDED_PATH in the HTTP header.
X_FORWARDED_PATH contains the basepath of the gateway.
X_FORWARDED_HOST contains the gateway-url.

Gateway basepath is:
/gateway-basepath

Upstream path is:
mydomain.com/py/api/v1

Basically, without gateway, Django reverse() creates following url for users endpoint:
mydomain.com/py/api/v1/users/

With the API gateway, the Django creates follow path:
apigatewayurl.com/gateway-basepath/py/api/v1/users/
Django is considering X_FORWARDED_HOST, but not X_FORWARDED_PATH

I need following result:
apigatewayurl.com/gateway-basepath/users
Otherwise the Django url resolving is not usable within the api gateway.

I would appreciate any help.

urls.py

from rest_framework.views import APIView
from rest_framework import routers

from . import views

class APIRootView(APIView):
    def get(self, request, format=None):
        return Response({
            &#39;users&#39;: reverse(&#39;user-list&#39;, request=request, format=format),
        })

router = routers.DefaultRouter()
router.register(r&#39;users&#39;, views.UserViewSet)

urlpatterns = [
    path(&#39;api/v1/&#39;, APIRootView.as_view(), name=&quot;api_root&quot;),
]

urlpatterns += router.urls

views.py

from rest_framework import viewsets
from django.contrib.auth import models as django_models

from .serializers import UserSerializer

class UserViewSet(viewsets.ModelViewSet):
    queryset = django_models.User.objects.all()
    serializer_class = UserSerializer

serializers.py

from rest_framework import serializers
from django.contrib.auth.models import User

class UserSerializer(serializers.HyperlinkedModelSerializer):
    class Meta:
        model = User
        fields = [&quot;url&quot;, &quot;username&quot;, &quot;email&quot;, &quot;is_staff&quot;]

答案1

得分: 1

我找到了一个解决方法。

Django Rest Framework 调用 django.http 中的 [build_absolute_uri()]链接)来构建绝对URL。
变量 location 包含生成的URL路径。

在不更改核心库的情况下,我使用了monkey patching来解决这个问题。
请注意,这是一个破坏性的更改
如果Django被更新,您需要检查这个函数。

settings.py

import django.http
from django.utils.encoding import iri_to_uri
from urllib.parse import urljoin, urlsplit

def build_absolute_uri(self, location=None):
    if location is None:
        location = "//%s" % self.get_full_path()
    else:
        # 强制转换懒惰的位置。
        location = str(location)

    # !!!!!!!!!!!!!!!!!!!!!
    # 我在这里添加了我的自定义内容
    # 修改 `location`
    # !!!!!!!!!!!!!!!!!!!!!
    location = location.replace(self.META["REQUEST_URI"], self.META["X_FORWARDED_PATH"])

    bits = urlsplit(location)
    if not (bits.scheme and bits.netloc):
        if (
            bits.path.startswith("/")
            and not bits.scheme
            and not bits.netloc
            and "/./" not in bits.path
            and "/../" not in bits.path
        ):
            location = self._current_scheme_host + location.removeprefix("//")
        else:
            location = urljoin(self._current_scheme_host + self.path, location)
    return iri_to_uri(location)

# 执行monkey patch并使用自己的函数覆盖核心功能
django.http.request.HttpRequest.build_absolute_uri = build_absolute_uri
英文:

I found a workaround to solve it.

Django Rest Framework calls [build_absolute_uri()] (Link) from django.http to build the absolute url.
The variable location contains the generated url paths.

Without changing the core library, I used monkepatching to solve it.
Be aware, that this is a breaking change.
If Django will be updated, you have to check this function.

settings.py

import django.http
from django.utils.encoding import iri_to_uri
from urllib.parse import urljoin, urlsplit

def build_absolute_uri(self, location=None):
    if location is None:
        location = &quot;//%s&quot; % self.get_full_path()
    else:
        # Coerce lazy locations.
        location = str(location)
    
    # !!!!!!!!!!!!!!!!!!!!!
    # I ADD HERE MY CUSTOMIZATION
    # MODIFYING `location`
    # !!!!!!!!!!!!!!!!!!!!!
    location = location.replace(self.META[&quot;REQUEST_URI&quot;], self.META[&quot;X_FORWARDED_PATH&quot;])

    bits = urlsplit(location)
    if not (bits.scheme and bits.netloc):
        if (
            bits.path.startswith(&quot;/&quot;)
            and not bits.scheme
            and not bits.netloc
            and &quot;/./&quot; not in bits.path
            and &quot;/../&quot; not in bits.path
        ):
            location = self._current_scheme_host + location.removeprefix(&quot;//&quot;)
        else:
            location = urljoin(self._current_scheme_host + self.path, location)
    return iri_to_uri(location)

# do monkeypatch and overwrite core functionality with own function  
django.http.request.HttpRequest.build_absolute_uri = build_absolute_uri 

huangapple
  • 本文由 发表于 2023年6月29日 05:41:24
  • 转载请务必保留本文链接:https://go.coder-hub.com/76576882.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定