Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Sign up
Appearance settings

Commit bfcecbf

Browse files
committed
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
1 parent ca7ee4b commit bfcecbf

File tree

13 files changed

+170
-27
lines changed

13 files changed

+170
-27
lines changed

‎django/conf/global_settings.py‎

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -188,6 +188,9 @@
188188
# Whether to prepend the "www." subdomain to URLs that don't have it.
189189
PREPEND_WWW = False
190190

191+
# Override the server-derived value of SCRIPT_NAME
192+
FORCE_SCRIPT_NAME = None
193+
191194
# List of compiled regular expression objects representing User-Agent strings
192195
# that are not allowed to visit any page, systemwide. Use this for bad
193196
# robots/crawlers. Here are a few examples:

‎django/core/handlers/base.py‎

Lines changed: 27 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
from django import http
44
from django.core import signals
55
from django.dispatch import dispatcher
6+
from django.utils.encoding import force_unicode
67

78
class BaseHandler(object):
89
# Changes that are always applied to a response (in this order).
@@ -73,7 +74,8 @@ def get_response(self, request):
7374

7475
resolver = urlresolvers.RegexURLResolver(r'^/', urlconf)
7576
try:
76-
callback, callback_args, callback_kwargs = resolver.resolve(request.path)
77+
callback, callback_args, callback_kwargs = resolver.resolve(
78+
request.path_info)
7779

7880
# Apply view middleware
7981
for middleware_method in self._view_middleware:
@@ -170,3 +172,27 @@ def apply_response_fixes(self, request, response):
170172
response = func(request, response)
171173
return response
172174

175+
def get_script_name(environ):
176+
"""
177+
Returns the equivalent of the HTTP request's SCRIPT_NAME environment
178+
variable. If Apache mod_rewrite has been used, returns what would have been
179+
the script name prior to any rewriting (so it's the script name as seen
180+
from the client's perspective), unless DJANGO_USE_POST_REWRITE is set (to
181+
anything).
182+
"""
183+
from django.conf import settings
184+
if settings.FORCE_SCRIPT_NAME is not None:
185+
return force_unicode(settings.FORCE_SCRIPT_NAME)
186+
187+
# If Apache's mod_rewrite had a whack at the URL, Apache set either
188+
# SCRIPT_URL or REDIRECT_URL to the full resource URL before applying any
189+
# rewrites. Unfortunately not every webserver (lighttpd!) passes this
190+
# information through all the time, so FORCE_SCRIPT_NAME, above, is still
191+
# needed.
192+
script_url = environ.get('SCRIPT_URL', u'')
193+
if not script_url:
194+
script_url = environ.get('REDIRECT_URL', u'')
195+
if script_url:
196+
return force_unicode(script_url[:-len(environ.get('PATH_INFO', ''))])
197+
return force_unicode(environ.get('SCRIPT_NAME', u''))
198+

‎django/core/handlers/modpython.py‎

Lines changed: 18 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
from django import http
55
from django.core import signals
66
from django.core.handlers.base import BaseHandler
7+
from django.core.urlresolvers import set_script_prefix
78
from django.dispatch import dispatcher
89
from django.utils import datastructures
910
from django.utils.encoding import force_unicode, smart_str
@@ -15,7 +16,21 @@
1516
class ModPythonRequest(http.HttpRequest):
1617
def __init__(self, req):
1718
self._req = req
19+
# FIXME: This isn't ideal. The request URI may be encoded (it's
20+
# non-normalized) slightly differently to the "real" SCRIPT_NAME
21+
# and PATH_INFO values. This causes problems when we compute path_info,
22+
# below. For now, don't use script names that will be subject to
23+
# encoding/decoding.
1824
self.path = force_unicode(req.uri)
25+
root = req.get_options().get('django.root', '')
26+
self.django_root = root
27+
# req.path_info isn't necessarily computed correctly in all
28+
# circumstances (it's out of mod_python's control a bit), so we use
29+
# req.uri and some string manipulations to get the right value.
30+
if root and req.uri.startswith(root):
31+
self.path_info = force_unicode(req.uri[len(root):])
32+
else:
33+
self.path_info = self.path
1934

2035
def __repr__(self):
2136
# Since this is called as part of error handling, we need to be very
@@ -100,15 +115,15 @@ def _get_meta(self):
100115
'CONTENT_LENGTH': self._req.clength, # This may be wrong
101116
'CONTENT_TYPE': self._req.content_type, # This may be wrong
102117
'GATEWAY_INTERFACE': 'CGI/1.1',
103-
'PATH_INFO': self._req.path_info,
118+
'PATH_INFO': self.path_info,
104119
'PATH_TRANSLATED': None, # Not supported
105120
'QUERY_STRING': self._req.args,
106121
'REMOTE_ADDR': self._req.connection.remote_ip,
107122
'REMOTE_HOST': None, # DNS lookups not supported
108123
'REMOTE_IDENT': self._req.connection.remote_logname,
109124
'REMOTE_USER': self._req.user,
110125
'REQUEST_METHOD': self._req.method,
111-
'SCRIPT_NAME': None, # Not supported
126+
'SCRIPT_NAME': self.django_root,
112127
'SERVER_NAME': self._req.server.server_hostname,
113128
'SERVER_PORT': self._req.server.port,
114129
'SERVER_PROTOCOL': self._req.protocol,
@@ -153,6 +168,7 @@ def __call__(self, req):
153168
if self._request_middleware is None:
154169
self.load_middleware()
155170

171+
set_script_prefix(req.get_options().get('django.root', ''))
156172
dispatcher.send(signal=signals.request_started)
157173
try:
158174
try:

‎django/core/handlers/wsgi.py‎

Lines changed: 10 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,8 @@
77

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

7576
class WSGIRequest(http.HttpRequest):
7677
def __init__(self, environ):
78+
script_name = base.get_script_name(environ)
79+
path_info = force_unicode(environ.get('PATH_INFO', '/'))
7780
self.environ = environ
78-
self.path = force_unicode(environ['PATH_INFO'])
81+
self.path_info = path_info
82+
self.path = '%s%s' % (script_name, path_info)
7983
self.META = environ
84+
self.META['PATH_INFO'] = path_info
85+
self.META['SCRIPT_NAME'] = script_name
8086
self.method = environ['REQUEST_METHOD'].upper()
8187

8288
def __repr__(self):
@@ -178,7 +184,7 @@ def _get_raw_post_data(self):
178184
REQUEST = property(_get_request)
179185
raw_post_data = property(_get_raw_post_data)
180186

181-
class WSGIHandler(BaseHandler):
187+
class WSGIHandler(base.BaseHandler):
182188
initLock = Lock()
183189
request_class = WSGIRequest
184190

@@ -194,6 +200,7 @@ def __call__(self, environ, start_response):
194200
self.load_middleware()
195201
self.initLock.release()
196202

203+
set_script_prefix(base.get_script_name(environ))
197204
dispatcher.send(signal=signals.request_started)
198205
try:
199206
try:

‎django/core/urlresolvers.py‎

Lines changed: 30 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -7,11 +7,13 @@
77
(view_function, function_args, function_kwargs)
88
"""
99

10+
import re
11+
1012
from django.http import Http404
1113
from django.core.exceptions import ImproperlyConfigured, ViewDoesNotExist
1214
from django.utils.encoding import iri_to_uri, force_unicode, smart_str
1315
from django.utils.functional import memoize
14-
import re
16+
fromdjango.utils.thread_supportimport currentThread
1517

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

26+
# SCRIPT_NAME prefixes for each thread are stored here. If there's no entry for
27+
# the current thread (which is the only one we ever access), it is assumed to
28+
# be empty.
29+
_prefixes = {}
30+
2431
class Resolver404(Http404):
2532
pass
2633

@@ -291,13 +298,33 @@ def reverse_helper(self, lookup_view, *args, **kwargs):
291298
def resolve(path, urlconf=None):
292299
return get_resolver(urlconf).resolve(path)
293300

294-
def reverse(viewname, urlconf=None, args=None, kwargs=None):
301+
def reverse(viewname, urlconf=None, args=None, kwargs=None, prefix=None):
295302
args = args or []
296303
kwargs = kwargs or {}
297-
return iri_to_uri(u'/' + get_resolver(urlconf).reverse(viewname, *args, **kwargs))
304+
if prefix is None:
305+
prefix = get_script_prefix()
306+
return iri_to_uri(u'%s%s' % (prefix, get_resolver(urlconf).reverse(viewname,
307+
*args, **kwargs)))
298308

299309
def clear_url_caches():
300310
global _resolver_cache
301311
global _callable_cache
302312
_resolver_cache.clear()
303313
_callable_cache.clear()
314+
315+
def set_script_prefix(prefix):
316+
"""
317+
Sets the script prefix for the current thread.
318+
"""
319+
if not prefix.endswith('/'):
320+
prefix += '/'
321+
_prefixes[currentThread()] = prefix
322+
323+
def get_script_prefix():
324+
"""
325+
Returns the currently active script prefix. Useful for client code that
326+
wishes to construct their own URLs manually (although accessing the request
327+
instance is normally going to be a lot cleaner).
328+
"""
329+
return _prefixes.get(currentThread(), u'/')
330+

‎django/http/__init__.py‎

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,7 @@ class HttpRequest(object):
3131
def __init__(self):
3232
self.GET, self.POST, self.COOKIES, self.META, self.FILES = {}, {}, {}, {}, {}
3333
self.path = ''
34+
self.path_info = ''
3435
self.method = None
3536

3637
def __repr__(self):
@@ -442,3 +443,4 @@ def str_to_unicode(s, encoding):
442443
return unicode(s, encoding, 'replace')
443444
else:
444445
return s
446+

‎django/test/client.py‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -190,7 +190,7 @@ def request(self, **request):
190190
'PATH_INFO': '/',
191191
'QUERY_STRING': '',
192192
'REQUEST_METHOD': 'GET',
193-
'SCRIPT_NAME': None,
193+
'SCRIPT_NAME': '',
194194
'SERVER_NAME': 'testserver',
195195
'SERVER_PORT': 80,
196196
'SERVER_PROTOCOL': 'HTTP/1.1',

‎django/utils/thread_support.py‎

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
"""
2+
Code used in a couple of places to work with the current thread's environment.
3+
Current users include i18n and request prefix handling.
4+
"""
5+
6+
try:
7+
import threading
8+
currentThread = threading.currentThread
9+
except ImportError:
10+
def currentThread():
11+
return "no threading"
12+

‎django/utils/translation/trans_real.py‎

Lines changed: 1 addition & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -8,18 +8,7 @@
88
from cStringIO import StringIO
99

1010
from django.utils.safestring import mark_safe, SafeData
11-
12-
try:
13-
import threading
14-
hasThreads = True
15-
except ImportError:
16-
hasThreads = False
17-
18-
if hasThreads:
19-
currentThread = threading.currentThread
20-
else:
21-
def currentThread():
22-
return 'no threading'
11+
from django.utils.thread_support import currentThread
2312

2413
# Translations are cached in a dictionary for every language+app tuple.
2514
# The active translations are stored by threadid to make them thread local.

‎docs/fastcgi.txt‎

Lines changed: 33 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -79,9 +79,9 @@ your ``manage.py`` is), and then run ``manage.py`` with the ``runfcgi`` option::
7979
If you specify ``help`` as the only option after ``runfcgi``, it'll display a
8080
list of all the available options.
8181

82-
You'll need to specify either a ``socket``, ``protocol`` or both ``host`` and ``port``.
83-
Then, when you set up your Web server, you'll just need to point it at the host/port
84-
or socket you specified when starting the FastCGI server.
82+
You'll need to specify either a ``socket``, ``protocol`` or both ``host`` and
83+
``port``. Then, when you set up your Web server, you'll just need to point it
84+
at the host/port or socket you specified when starting the FastCGI server.
8585

8686
Protocols
8787
---------
@@ -209,6 +209,9 @@ This is probably the most common case, if you're using Django's admin site::
209209

210210
.. _mod_rewrite: http://httpd.apache.org/docs/2.0/mod/mod_rewrite.html
211211

212+
Django will automatically use the pre-rewrite version of the URL when
213+
constructing URLs with the ``{% url %}`` template tag (and similar methods).
214+
212215
lighttpd setup
213216
==============
214217

@@ -336,3 +339,30 @@ detailed above.
336339

337340
.. _modpython: ../modpython/#serving-the-admin-files
338341

342+
Forcing the URL prefix to a particular value
343+
============================================
344+
345+
Because many of these fastcgi-based solutions require rewriting the URL at
346+
some point inside the webserver, the path information that Django sees may not
347+
resemble the original URL that was passed in. This is a problem if the Django
348+
application is being served from under a particular prefix and you want your
349+
URLs from the ``{% url %}`` tag to look like the prefix, rather than the
350+
rewritten version, which might contain, for example, ``mysite.fcgi``.
351+
352+
Django makes a good attempt to work out what the real script name prefix
353+
should be. In particular, if the webserver sets the ``SCRIPT_URL`` (specific
354+
to Apache's mod_rewrite), or ``REDIRECT_URL`` (set by a few servers, including
355+
Apache + mod_rewrite in some situations), Django will work out the original
356+
prefix automatically.
357+
358+
In the cases where Django cannot work out the prefix correctly and where you
359+
wan the original value to be used in URLs, you can set the
360+
``FORCE_SCRIPT_NAME`` setting in your main ``settings`` file. This sets the
361+
script name uniformly for every URL served via that settings file. Thus you'll
362+
need to use different settings files is you want different sets of URLs to
363+
have different script names in this case, but that is a rare situation.
364+
365+
As an example of how to use it, if your Django configuration is serving all of
366+
the URLs under ``'/'`` and you wanted to use this setting, you would set
367+
``FORCE_SCRIPT_NAME = ''`` in your settings file.
368+

0 commit comments

Comments
(0)

AltStyle によって変換されたページ (->オリジナル) /