Commit bfcecbff authored by Malcolm Tredinnick's avatar Malcolm Tredinnick
Browse files

Changed/fixed the way Django handles SCRIPT_NAME and PATH_INFO (or

equivalents). Basically, URL resolving will only use the PATH_INFO and the
SCRIPT_NAME will be prepended by reverse() automatically. Allows for more
portable development and installation. Also exposes SCRIPT_NAME in the
HttpRequest instance.

There are a number of cases where things don't work completely transparently,
so mod_python and fastcgi users should read the relevant docs.

Fixed #285, #1516, #3414.


git-svn-id: http://code.djangoproject.com/svn/django/trunk@8015 bcc190cf-cafb-0310-a4f2-bffc1f526a37
parent ca7ee4be
Loading
Loading
Loading
Loading
+3 −0
Original line number Diff line number Diff line
@@ -188,6 +188,9 @@ APPEND_SLASH = True
# Whether to prepend the "www." subdomain to URLs that don't have it.
PREPEND_WWW = False

# Override the server-derived value of SCRIPT_NAME
FORCE_SCRIPT_NAME = None

# List of compiled regular expression objects representing User-Agent strings
# that are not allowed to visit any page, systemwide. Use this for bad
# robots/crawlers. Here are a few examples:
+27 −1
Original line number Diff line number Diff line
@@ -3,6 +3,7 @@ import sys
from django import http
from django.core import signals
from django.dispatch import dispatcher
from django.utils.encoding import force_unicode

class BaseHandler(object):
    # Changes that are always applied to a response (in this order).
@@ -73,7 +74,8 @@ class BaseHandler(object):

        resolver = urlresolvers.RegexURLResolver(r'^/', urlconf)
        try:
            callback, callback_args, callback_kwargs = resolver.resolve(request.path)
            callback, callback_args, callback_kwargs = resolver.resolve(
                    request.path_info)

            # Apply view middleware
            for middleware_method in self._view_middleware:
@@ -170,3 +172,27 @@ class BaseHandler(object):
            response = func(request, response)
        return response

def get_script_name(environ):
    """
    Returns the equivalent of the HTTP request's SCRIPT_NAME environment
    variable. If Apache mod_rewrite has been used, returns what would have been
    the script name prior to any rewriting (so it's the script name as seen
    from the client's perspective), unless DJANGO_USE_POST_REWRITE is set (to
    anything).
    """
    from django.conf import settings
    if settings.FORCE_SCRIPT_NAME is not None:
        return force_unicode(settings.FORCE_SCRIPT_NAME)

    # If Apache's mod_rewrite had a whack at the URL, Apache set either
    # SCRIPT_URL or REDIRECT_URL to the full resource URL before applying any
    # rewrites. Unfortunately not every webserver (lighttpd!) passes this
    # information through all the time, so FORCE_SCRIPT_NAME, above, is still
    # needed.
    script_url = environ.get('SCRIPT_URL', u'')
    if not script_url:
        script_url = environ.get('REDIRECT_URL', u'')
    if script_url:
        return force_unicode(script_url[:-len(environ.get('PATH_INFO', ''))])
    return force_unicode(environ.get('SCRIPT_NAME', u''))
+18 −2
Original line number Diff line number Diff line
@@ -4,6 +4,7 @@ from pprint import pformat
from django import http
from django.core import signals
from django.core.handlers.base import BaseHandler
from django.core.urlresolvers import set_script_prefix
from django.dispatch import dispatcher
from django.utils import datastructures
from django.utils.encoding import force_unicode, smart_str
@@ -15,7 +16,21 @@ from django.utils.encoding import force_unicode, smart_str
class ModPythonRequest(http.HttpRequest):
    def __init__(self, req):
        self._req = req
        # FIXME: This isn't ideal. The request URI may be encoded (it's
        # non-normalized) slightly differently to the "real" SCRIPT_NAME
        # and PATH_INFO values. This causes problems when we compute path_info,
        # below. For now, don't use script names that will be subject to
        # encoding/decoding.
        self.path = force_unicode(req.uri)
        root = req.get_options().get('django.root', '')
        self.django_root = root
        # req.path_info isn't necessarily computed correctly in all
        # circumstances (it's out of mod_python's control a bit), so we use
        # req.uri and some string manipulations to get the right value.
        if root and req.uri.startswith(root):
            self.path_info = force_unicode(req.uri[len(root):])
        else:
            self.path_info = self.path

    def __repr__(self):
        # Since this is called as part of error handling, we need to be very
@@ -100,7 +115,7 @@ class ModPythonRequest(http.HttpRequest):
                'CONTENT_LENGTH':    self._req.clength, # This may be wrong
                'CONTENT_TYPE':      self._req.content_type, # This may be wrong
                'GATEWAY_INTERFACE': 'CGI/1.1',
                'PATH_INFO':         self._req.path_info,
                'PATH_INFO':         self.path_info,
                'PATH_TRANSLATED':   None, # Not supported
                'QUERY_STRING':      self._req.args,
                'REMOTE_ADDR':       self._req.connection.remote_ip,
@@ -108,7 +123,7 @@ class ModPythonRequest(http.HttpRequest):
                'REMOTE_IDENT':      self._req.connection.remote_logname,
                'REMOTE_USER':       self._req.user,
                'REQUEST_METHOD':    self._req.method,
                'SCRIPT_NAME':       None, # Not supported
                'SCRIPT_NAME':       self.django_root,
                'SERVER_NAME':       self._req.server.server_hostname,
                'SERVER_PORT':       self._req.server.port,
                'SERVER_PROTOCOL':   self._req.protocol,
@@ -153,6 +168,7 @@ class ModPythonHandler(BaseHandler):
        if self._request_middleware is None:
            self.load_middleware()

        set_script_prefix(req.get_options().get('django.root', ''))
        dispatcher.send(signal=signals.request_started)
        try:
            try:
+10 −3
Original line number Diff line number Diff line
@@ -7,7 +7,8 @@ except ImportError:

from django import http
from django.core import signals
from django.core.handlers.base import BaseHandler
from django.core.handlers import base
from django.core.urlresolvers import set_script_prefix
from django.dispatch import dispatcher
from django.utils import datastructures
from django.utils.encoding import force_unicode
@@ -74,9 +75,14 @@ def safe_copyfileobj(fsrc, fdst, length=16*1024, size=0):

class WSGIRequest(http.HttpRequest):
    def __init__(self, environ):
        script_name = base.get_script_name(environ)
        path_info = force_unicode(environ.get('PATH_INFO', '/'))
        self.environ = environ
        self.path = force_unicode(environ['PATH_INFO'])
        self.path_info = path_info
        self.path = '%s%s' % (script_name, path_info)
        self.META = environ
        self.META['PATH_INFO'] = path_info
        self.META['SCRIPT_NAME'] = script_name
        self.method = environ['REQUEST_METHOD'].upper()

    def __repr__(self):
@@ -178,7 +184,7 @@ class WSGIRequest(http.HttpRequest):
    REQUEST = property(_get_request)
    raw_post_data = property(_get_raw_post_data)

class WSGIHandler(BaseHandler):
class WSGIHandler(base.BaseHandler):
    initLock = Lock()
    request_class = WSGIRequest

@@ -194,6 +200,7 @@ class WSGIHandler(BaseHandler):
                self.load_middleware()
            self.initLock.release()

        set_script_prefix(base.get_script_name(environ))
        dispatcher.send(signal=signals.request_started)
        try:
            try:
+30 −3
Original line number Diff line number Diff line
@@ -7,11 +7,13 @@ a string) and returns a tuple in this format:
    (view_function, function_args, function_kwargs)
"""

import re

from django.http import Http404
from django.core.exceptions import ImproperlyConfigured, ViewDoesNotExist
from django.utils.encoding import iri_to_uri, force_unicode, smart_str
from django.utils.functional import memoize
import re
from django.utils.thread_support import currentThread

try:
    reversed
@@ -21,6 +23,11 @@ except NameError:
_resolver_cache = {} # Maps urlconf modules to RegexURLResolver instances.
_callable_cache = {} # Maps view and url pattern names to their view functions.

# SCRIPT_NAME prefixes for each thread are stored here. If there's no entry for
# the current thread (which is the only one we ever access), it is assumed to
# be empty.
_prefixes = {}

class Resolver404(Http404):
    pass

@@ -291,13 +298,33 @@ class RegexURLResolver(object):
def resolve(path, urlconf=None):
    return get_resolver(urlconf).resolve(path)

def reverse(viewname, urlconf=None, args=None, kwargs=None):
def reverse(viewname, urlconf=None, args=None, kwargs=None, prefix=None):
    args = args or []
    kwargs = kwargs or {}
    return iri_to_uri(u'/' + get_resolver(urlconf).reverse(viewname, *args, **kwargs))
    if prefix is None:
        prefix = get_script_prefix()
    return iri_to_uri(u'%s%s' % (prefix, get_resolver(urlconf).reverse(viewname,
            *args, **kwargs)))

def clear_url_caches():
    global _resolver_cache
    global _callable_cache
    _resolver_cache.clear()
    _callable_cache.clear()

def set_script_prefix(prefix):
    """
    Sets the script prefix for the current thread.
    """
    if not prefix.endswith('/'):
        prefix += '/'
    _prefixes[currentThread()] = prefix

def get_script_prefix():
    """
    Returns the currently active script prefix. Useful for client code that
    wishes to construct their own URLs manually (although accessing the request
    instance is normally going to be a lot cleaner).
    """
    return _prefixes.get(currentThread(), u'/')
Loading