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