Search code examples
djangohttp-redirectmiddlewaredispatch

Check url status without opening it


At Now when url is opened (without a slash - example.com/blog), a slash is automatically added at the end (there are 301 redirects). The question is, can I somehow do it so that the check first goes to see if the page exists (without a slash - example.com/blog). If so, open it. If not, then check whether the page exists with a slash (only without 301 - example.com/blog/). If so, then redirect 301, and if not, then throw 404.

Now just if there is no page (example.com/blog), then a slash is added to the end first (example.com/blog/), 301 redirects go and only then a 404 error is thrown. In this case, the 404 error must be thrown immediately, without a 301 redirect.

The dispatch was rewritten as follows.

def is_normal_slash_count(url):
    temp_url = url
    slash_count = 0
    while temp_url.endswith('/'):
        slash_count += 1
        temp_url = temp_url[:-1]
    return (slash_count == 1, slash_count)


def replace_bad_slash(url, slash_count):
    if slash_count == 2:
        return url.replace('//', '/')
    return url.replace('/'*(slash_count-1), '')


def normalize_url(url):
    if len(url) > 1:
        if not url.endswith('/'):
            return url + '/'
        # replace the url like /contacts//// to /contacts/
        good_slash, slash_count = is_normal_slash_count(url)
        if not good_slash:
            url = replace_bad_slash(url, slash_count)
    return url

def is_bad_url(url):
    if len(url) > 1:
        good_slash, slash_count = is_normal_slash_count(url)
        if not good_slash:
            return True
    return False

class RedirectMixinView:

    def dispatch(self, *args, **kwargs):
        url = self.request.path

        redirect_setting = RedirectSettings.objects.filter(url_from=url).first()
        if redirect_setting:
            return redirect(redirect_setting.url_to, permanent=True)

        if is_bad_url(url):
            return redirect(normalize_url(url), permanent=True)
        return super(RedirectMixinView, self).dispatch(*args, **kwargs)

Is this realistic?

I think in the direction of writing middleware.

enter image description here

Updated

projects.urls

url(r'^page/', include('pages.urls')),

pages.urls

url(r'^$', PageView.as_view(), name='page'),

test

try:
    resolve('/page/')
except:
    raise Http404
return redirect('/page/')

I'm tried /page/, /page, page/, page, http://127.0.0.1:8000/page/, http://127.0.0.1:8000/page


Solution

    1. You need to remove RedirectMixinView from LandingView.
    2. Comment out the middleware CommonMiddleware.
    3. Add RedirectMiddleware to the list of middleware (preferably in the top).
    4. Create RedirectMiddleware

    The code is written jointly with @dirkgroten (most of his contribution).

    import re
    from django.http import HttpResponsePermanentRedirect
    
    class RedirectMiddleware(object):
        response_redirect_class = HttpResponsePermanentRedirect
    
        def __init__(self, get_response):
            self.get_response = get_response
    
        def __call__(self, request):
    
            response = self.get_response(request)
    
            path = re.sub("/+", "/", request.path)
    
            if response.status_code == 404:
                if not path.endswith('/'):
                    request.path = path  # to force using the cleaned path
                else:
                    request.path = path[:-1]  # to force using the cleaned path
                try:
                    full_path = request.get_full_path(force_append_slash=True) # add the slash, keeping query parameters
                    r = resolve(full_path)
                    new_response = r.func(request, args=r.args, kwargs=r.kwargs)
                    if new_response.status_code == 200:
                        return redirect(full_path)
                except Resolver404:
                    pass  # this will fall through to `return response`
    
            # Add the Content-Length header to non-streaming responses if not
            # already set.
            if not response.streaming and not response.has_header('Content-Length'):
                response['Content-Length'] = str(len(response.content))
            return response
    
    
    1. Add to ngnx config of project
        if ($request_uri ~* "\/\/") {
            rewrite ^/(.*)      $scheme://$host/$1    permanent;
        }
        # merge_slashes off;
    

    It does what you need, and also removes duplicate slashes if this page exists.