DjangoがLocationヘッダを組み立てる辺りの処理を追いかけたときのメモ

djangoのlogoutで、nextパラメータで渡したパスが意図しないURLに変更されてLocationとして返されたので、その辺りの処理を追いかけていたときのメモです。


BaseHandlerのget_responseメソッドの最後の方にMiddlewareを適用する箇所があり、その下でapply_response_fixesメソッドを呼び出してます。

django/core/handlers/base.py

        try:
            # Apply response middleware, regardless of the response
            for middleware_method in self._response_middleware:
                response = middleware_method(request, response)
            response = self.apply_response_fixes(request, response)  #ココ
        except: # Any exception should be gathered and handled
            signals.got_request_exception.send(sender=self.__class__, request=request)
            response = self.handle_uncaught_exception(request, resolver, sys.exc_info())

        return response


apply_response_fixesはresponseに対し、response_fixesを1つずつ適用します

django/core/handlers/base.py

    def apply_response_fixes(self, request, response):
        """
        Applies each of the functions in self.response_fixes to the request and
        response, modifying the response in the process. Returns the new
        response.
        """
        for func in self.response_fixes:
            response = func(request, response)
        return response


response_fixesはこちら

このうちfix_location_headerがその名の通りLocationヘッダの値を変更します

    response_fixes = [
        http.fix_location_header,  # コレ
        http.conditional_content_removal,
        http.fix_IE_for_attach,
        http.fix_IE_for_vary,
    ]


コメントによるとfix_location_headerはLocationの値をabsoluteなurlに変更します

def fix_location_header(request, response):
    """
    Ensures that we always use an absolute URI in any location header in the
    response. This is required by RFC 2616, section 14.30.

    Code constructing response objects is free to insert relative paths, as
    this function converts them to absolute paths.
    """
    if 'Location' in response and request.get_host():
        response['Location'] = request.build_absolute_uri(response['Location'])
    return response


request.build_absolute_uri

    def build_absolute_uri(self, location=None):
        """
        Builds an absolute URI from the location and the variables available in
        this request. If no location is specified, the absolute URI is built on
        ``request.get_full_path()``.
        """
        if not location:
            location = self.get_full_path()
        if not absolute_http_url_re.match(location):
            current_uri = '%s://%s%s' % ('https' if self.is_secure() else 'http',
                                         self.get_host(), self.path)
            location = urljoin(current_uri, location)
        return iri_to_uri(location)


build_absolute_uriは、location(nextパラメータで渡されたパス)がabsolute_urlでない場合、requestのget_host、pathからcontent_uriを取得し、urljoinでlocationとつないだ値をLocationヘッダとします。



以下はurl_joinの動きをいろいろ試したときのコードです。
コード中の1のようなURL、パラメータを渡してしまっていたため、locationが意図しないものになってしまっていました。

# coding=utf-8
from urlparse import urljoin

# 1. URLが/で終わっており、nextパラメータが/で始まっていない
#  http://localhost:8000/logout/?next=mypage

current_uri = u'http://localhost:8000/logout/'
location = 'mypage'
location = urljoin(current_uri, location)
print location
# http://localhost:8000/logout/mypage #ダメ

# 2. URLが/で終わってなく、nextパラメータが/で始まっていない
#  http://localhost:8000/logout?next=mypage

current_uri = u'http://localhost:8000/logout'
location = 'mypage'
location = urljoin(current_uri, location)
print location
# http://localhost:8000/mypage

# 3. URLが/で終わっていて、nextパラメータが/で始まっている
#  http://localhost:8000/logout/?next=/mypage

current_uri = u'http://localhost:8000/logout/'
location = '/mypage'
location = urljoin(current_uri, location)
print location
# http://localhost:8000/mypage