简体   繁体   English

如何在 django rest 框架中的分页 URL 中更改下一个键中的主机?

[英]How to change the host in next key in a paginated URL in django rest framework?

I have a ModelSerializer in Django Rest Framework with paginated responses.我在 Django Rest 框架中有一个带有分页响应的 ModelSerializer。 So I have deployed it with gunicorn in a docker container.所以我用 gunicorn 将它部署在 docker 容器中。

gunicorn -c gunicorn_config.py app.wsgi --bind 0.0.0.0:5000

Now the problem is in the paginated responses.现在问题出在分页响应中。 The next key is something like. next键是这样的。

next: "http://0.0.0.0:5000/admin/users/?page=2&per_page=10"

In my client-side where I am consuming these APIs, I just check the next key and fetch the next response.在我使用这些 API 的客户端中,我只需检查next键并获取下一个响应。 But since the next key has the host as 0.0.0.0:5000 hence it will cause API call failure.但由于next键的主机为0.0.0.0:5000 ,因此会导致 API 调用失败。 And the purpose is not served for the next key.并且不为next密钥服务。

So at the moment, my API server is running in a separate docker container.所以目前,我的 API 服务器正在一个单独的 docker 容器中运行。 Which is set up via the reverse proxy in nginx .这是通过nginx中的反向代理设置的。

The next link in the DRF paginator is generated using the hostname from the request. DRF 分页器中的next链接是使用请求中的主机名生成的。 This is how the hostname is determined in the request:这是在请求中确定主机名的方式:

def _get_raw_host(self):
    """
    Return the HTTP host using the environment or request headers. Skip
    allowed hosts protection, so may return an insecure host.
    """
    # We try three options, in order of decreasing preference.
    if settings.USE_X_FORWARDED_HOST and (
            'HTTP_X_FORWARDED_HOST' in self.META):
        host = self.META['HTTP_X_FORWARDED_HOST']
    elif 'HTTP_HOST' in self.META:
        host = self.META['HTTP_HOST']
    else:
        # Reconstruct the host using the algorithm from PEP 333.
        host = self.META['SERVER_NAME']
        server_port = self.get_port()
        if server_port != ('443' if self.is_secure() else '80'):
            host = '%s:%s' % (host, server_port)
    return host

So, check if the HTTP_X_FORWARDED_HOST header sets the correct hostname you need and if so set USE_X_FORWARDED_HOST to True in your settings.因此,请检查HTTP_X_FORWARDED_HOST header 是否设置了您需要的正确主机名,如果是,请在您的设置中将USE_X_FORWARDED_HOST设置为True Also make sure that the hostname you need is added to ALLOWED_HOSTS .还要确保将您需要的主机名添加到ALLOWED_HOSTS中。

You could also override the get_next_link() method in the PageNumberPagination class to supply the needed host/domain name您还可以覆盖PageNumberPagination class 中的get_next_link()方法以提供所需的主机/域名

So I made a custom pagination class extending PageNumberPagination所以我做了一个自定义分页 class 扩展PageNumberPagination

from rest_framework.pagination import PageNumberPagination
def replace_query_param(url, key, val):
    """
    Given a URL and a key/val pair, set or replace an item in the query
    parameters of the URL, and return the new URL.
    """
    (scheme, netloc, path, query, fragment) = parse.urlsplit(force_str(url))
    scheme = "https"
    netloc = "api.example.com"
    query_dict = parse.parse_qs(query, keep_blank_values=True)
    query_dict[force_str(key)] = [force_str(val)]
    query = parse.urlencode(sorted(list(query_dict.items())), doseq=True)
    return parse.urlunsplit((scheme, netloc, path, query, fragment))


def remove_query_param(url, key):
    """
    Given a URL and a key/val pair, remove an item in the query
    parameters of the URL, and return the new URL.
    """
    (scheme, netloc, path, query, fragment) = parse.urlsplit(force_str(url))
    scheme = "https"
    netloc = "api.example.com"
    query_dict = parse.parse_qs(query, keep_blank_values=True)
    query_dict.pop(key, None)
    query = parse.urlencode(sorted(list(query_dict.items())), doseq=True)
    return parse.urlunsplit((scheme, netloc, path, query, fragment))

class LargeResultsSetPagination(PageNumberPagination):
    page_size = 1000
    page_size_query_param = 'per_page'
    max_page_size = 1000

    def get_next_link(self):
        if not self.page.has_next():
            return None
        url = self.request.build_absolute_uri()
        page_number = self.page.next_page_number()
        return replace_query_param(url, self.page_query_param, page_number)

    def get_previous_link(self):
        if not self.page.has_previous():
            return None
        url = self.request.build_absolute_uri()
        page_number = self.page.previous_page_number()
        if page_number == 1:
            return remove_query_param(url, self.page_query_param)
        return replace_query_param(url, self.page_query_param, page_number)

Now I am using this pagination class in all my ViewSets现在我在我的所有视图集中使用这个分页ViewSets

class TestViewSet(viewsets.ModelViewSet):
    permission_classes = [permissions.IsAuthenticated]

    queryset = Test.objects.all().order_by("pk")
    serializer_class = test_serializers.TestSerializer
    pagination_class = LargeResultsSetPagination
    search_fields = ['name', 'description', 'follow_up', 'follow_up_type']
    filter_backends = (filters.SearchFilter,)

And it does the job, the original inspiration https://stackoverflow.com/a/62422235/5884045它完成了工作,原始灵感https://stackoverflow.com/a/62422235/5884045

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM