有个常见需求是这样的:一个Django应用,在开发时,URL
是以/
为根目录的;而部署时,需要给它一个前缀,比如叫/prefix/
。 它的使用场景是,在一个域名里托管多个应用,它们仅以前缀区分。
这个需求可以拆解成两个具体的要求:
- 在外面访问时是有
/prefix/
前缀的,而在应用那一层,不知道有这个前缀,仍然以为是/
。 - 在内部进行相对
URL
的生成时,虽然不知道前缀,但是要有前缀。 比如,从外面的首页/prefix/
,希望点击一个链接后跳转到/prefix/home/
; 而里面的应用在不知道前缀的情况下,生成的链接是/prefix/home/
,而非/home/
。
WSGI协议的相关知识
What is WSGI
WSGI is the Web Server Gateway Interface. It is a specification that describes how a web server communicates with web applications, and how web applications can be chained together to process one request.
WSGI协议通过SCRIPT_NAME
和PATH_INFO
,来实现加前缀的操作。
from urllib import quote
url = environ['wsgi.url_scheme']+'://'
if environ.get('HTTP_HOST'):
url += environ['HTTP_HOST']
else:
url += environ['SERVER_NAME']
if environ['wsgi.url_scheme'] == 'https':
if environ['SERVER_PORT'] != '443':
url += ':' + environ['SERVER_PORT']
else:
if environ['SERVER_PORT'] != '80':
url += ':' + environ['SERVER_PORT']
url += quote(environ.get('SCRIPT_NAME', ''))
url += quote(environ.get('PATH_INFO', ''))
if environ.get('QUERY_STRING'):
url += '?' + environ['QUERY_STRING']
从上述代码,源于WSGI的v1.01版本的URL Reconstruction描述,是推荐的URL构建算法。
一个URL的通用形式如下:
scheme:[//[user[:password]@]host[:port]][/path][?query][#fragment]
可以看出,SCRIPT_NAME
和PATH_INFO
,共同构成了[/path]
这部分。 一般情况下,SCRIPT_NAME
相当于为空,/path
就等价于PATH_INFO
。 而如果SCRIPT_NAME=/prefix
,就实现了孤前面的第1个要求; 如果PATH_INFO
等于去掉前缀后的部分,就实现了第2个要求。
WSGI协议参考
- 《Web Server Gateway Interface - Wikipedia》
- 《PEP 333 -- Python Web Server Gateway Interface v1.0》
- 《PEP 3333 -- Python Web Server Gateway Interface v1.0.1》
HTTP服务器方案
现在,主要的两个HTTP服务器------Apache httpd(通常简称Apache)和Nginx,都支持WSGI协议。
Apache httpd
Apache httpd通过模块mod_wsgi对WSGI协议进行支持。 它对这个功能的实现与配置,简单而强大。
WSGIScriptAlias /prefix /PATH/TO/DJANGO/wsgi.py
但由于孤目前对Apache httpd并不太熟,也不实际使用,所以就说到这。
Nginx
旧方案
location ~ ^/prefix/ {
...
uwsgi_param SCRIPT_NAME /prefix;
uwsgi_modifier1 30;
}
这是网上流传最多的设置方式。 然而,由于Nginx不支持修改PATH_INFO
,所以需要uwsgi_modifier1 30
这种丑陋的设置。 否则,内部被访问的应用,实际得到的访问链接是/prefix/prefix/...
这种形式的。
uwsgi_modifier1 30
的机