uWSGI内部路由¶
更新至1.9
自uWSGI 1.9起,提供了一个可编程的内部路由子系统。 (1.1版本之后的旧版本有一个不大起眼的版本)。你可以使用内部路由子系统来动态改变处理请求的方式。例如,你可以用它来触发对于指定URL的301重定向,或者在指定条件下提供来自于缓存的内容。这个内部路由子系统的灵感来源于Apache的 mod_rewrite
和Linux的 iptables
命令。
在喷它是凌乱、不优雅或者不图灵完备之前,请记住,它必须快并且只是快。如果你需要优雅以及更多的复杂度,那么你可以在自己的代码中实现。
路由链¶
在请求周期内,会经过不同的“链”。每个链包含一个路由表 (见下)。
链可以是“递归的”,一个“递归”链在一个请求周期内可以被多次调用。
这是链的顺序:
request
在请求被传递给插件之前应用
error
一个生成了一个HTTP状态码就会被立即应用(递归链)
response
在生成了最后一个响应头后被应用 (仅在发送响应体之前)
final
在已经发送响应到客户端之后被应用
request
链是 (为了方便起见) 是“默认的”链,因此它的选项没有前缀,而其他的链需要前缀。
例如:
route-user-agent
-> 用于request链
而
response-route-uri
-> 用于response链
内部路由表¶
内部路由表是一个接一个(也允许向前跳跃)执行的“规则”序列。每个规则由一个’‘subject(标题)’‘,一个’‘condition(条件)’‘和一个’‘action(动作)’‘组成。’‘条件’‘通常是一个应用到标题的PCRE正则表达式:如果匹配上了,那么就会触发相应的动作。标题是请求的变量。目前支持以下标题:
host
(检查HTTP_HOST)uri
(检查REQUEST_URI)qs
(检查QUERY_STRING)remote-addr
(检查REMOTE_ADDR)remote-user
(检查REMOTE_USER)referer
(检查HTTP_REFERER)user-agent
(检查HTTP_USER_AGENT)status
(检查HTTP响应状态码,在request链中不可用)default
(默认标题,映射到PATH_INFO)
除此之外,有一个可用的低层次条件可插拔系统。你可以使用 --route-if
选项来访问此系统。目前,支持以下检查:
exists
(检查标题是否存在于文件系统)isfile
(检查标题是否是一个文件)isdir
(检查标题是否是一个目录)isexec
(检查标题是否是一个可执行文件)equal
/isequal
/eq
/==
(检查标题是否等于指定模式)ishigherequal
/>=
ishigher
/>
islower
/<
islowerequal
/<=
startswith
(检查标题是否以指定模式开头)endswith
(检查标题是否以指定模式结尾)regexp
/re (检查标题是否匹配指定的正则表达式)empty
(检查标题是否为空)contains
当一次检查需要一个模式(例如使用’equal’或者’regexp’)时,你可以使用分号将它从标题分隔开:
; never matches
route-if = equal:FOO;BAR log:never here
; matches
route-if = regexp:FOO;^F log:starts with F
动作是当一个规则匹配的时候要运行的汗水。这些动作由插件导出,并有一个返回值。
动作返回值¶
每个动作都有一个返回值,这个返回值告诉路由引擎接下来要做什么。支持以下返回码:
NEXT
(继续下一个规则)CONTINUE
(停止扫描内部路由表并运行请求)BREAK
(停止扫描内部路由表并关闭请求)GOTO x
(跳到规则x
)
当一个规则不匹配的时候,则假设 NEXT
。
第一个例子¶
[uwsgi]
route-user-agent = .*curl.* redirect:http://uwsgi.it
route-remote-addr = ^127\.0\.0\.1$ break:403 Forbidden
route = ^/test log:someone called /test
route = \.php$ rewrite:/index.php
route = .* addheader:Server: my uWSGI server
route-host = ^localhost$ logvar:local=1
route-uri = ^/foo/(.*)\.jpg$ cache:key=$1.jpg
route-if = equal:${PATH_INFO};/bad break:500 Internal Server Error
前面的规则构建以下表:
- 如果
HTTP_USER_AGENT
变量包含’curl’,那么重定向请求到 http://uwsgi.it (状态码为302,动作返回BREAK) - 如果
REMOTE_ADDR
是‘127.0.0.1’,则返回403 Forbidden (动作返回 BREAK) - 如果
PATH_INFO
以/test开头,则在日志中打印字符串’someone called /test’ (动作返回NEXT) - 如果
PATH_INFO
以’.php’结尾,那么将其重写至/index.php (动作返回 NEXT) - 对于所有的
PATH_INFO
,添加HTTP头’Server: my uWSGI server’到响应中 (动作返回NEXT) - 如果
HTTP_HOST
是localhost,那么添加日志变量’local’,并将其设置为‘1’ - 如果
REQUEST_URI
以/foo开头,并且以.jpg结尾,那么使用附带的键(通过正则表达式分组构建)从uWSGI缓存中获取它 (动作返回 BREAK) - 如果
PATH_INFO
等于/bad,那么抛出500错误
访问请求变量¶
除了PCRE占位符/组 (使用$1到$9),你可以使用${VAR}语法来访问请求变量 (PATH_INFO, SCRIPT_NAME, REQUEST_METHOD...)。
[uwsgi]
route-user-agent = .*curl.* redirect:http://uwsgi.it${REQUEST_URI}
访问cookie¶
你可以使用${cookie[name]}语法来访问一个cookie值:
[uwsgi]
route = ^/foo log:${cookie[foobar]}
这将会在日志中记录当前请求的’foobar’ cookie内容
访问查询字符串¶
你可以使用${qs[name]}语法访问HTTP请求字符串的值:
[uwsgi]
route = ^/foo log:${qs[foobar]}
这将会在日志中记录当前请求的查询字符串的’foobar’项的内容
可插拔路由变量¶
cookie和qs变量都是所谓的“路由变量”。它们是可插拔的,因此外部插件可以添加新的变量来添加新特性到你的应用上。 (看看 GeoIP插件 插件,以获取关于这个的一个例子。) 还有一些可用的嵌入式路由变量。
mime
– 返回指定变量的mime类型:${mime[REQUEST_URI]}[uwsgi] route = ^/images/(.+) addvar:MYFILE=$1.jpg route = ^/images/ addheader:Content-Type: ${mime[MYFILE]}
time
– 以多种形式返回时间/日期。唯一支持(目前)的是time[unix],返回时代httptime
– 返回http日期,添加数值参数 (如果指定) 来获得当前时间 (使用空参数来获取当前的服务器时间)
[uwsgi]
; add Date header
route-run = addheader:Date ${httptime[]}
math
– 需要matheval支持。例如:math[CONTENT_LENGTH+1]base64
– 将指定的变量用base64编码hex
– 将指定的变量用hex编码upper
– 将指定变量转换成大写lower
– 将指定变量转换成小写uwsgi
– 返回内部的uWSGI信息,目前支持uwsgi[wid], uwsgi[pid], uwsgi[uuid]和uwsgi[status]
–route-if不够吗?为什么还要–route-uri和其他小伙伴?¶
这是个好问题。你只需要一直记住,uWSGI是关于通用性和 性能 的。可以循环利用总是好的。
--route-if
选项,虽然通用,但是不能够被优化,因为在处理每一个请求的时候都必须重新计算它所有的部分。这显然非常快,但是
--route-uri
选项 (和其他小伙伴)可以被预先优化 (在启动期间) 来直接映射到请求内存区域,因此,如果你可以使用它们,那么你绝对应该用它们。 :)
GOTO¶
是哒,整个信息技术产业(和历史)最具争议的结构就是这里。你可以向前 (只能向前!)跳到内部路由表的特定点。你可以设置标签来标记路由表的特定点,或者如果你勇敢 (或者蠢),直接跳到一个规则变化。规则编号会在服务器启动的时候打印处理,但请用标签。
[uwsgi]
route-host = ^localhost$ goto:localhost
route-host = ^sid\.local$ goto:sid.local
route = .* last:
route-label = sid.local
route-user-agent = .*curl.* redirect:http://uwsgi.it
route-remote-addr = ^192\.168\..* break:403 Forbidden
route = ^/test log:someone called /test
route = \.php$ rewrite:/index.php
route = .* addheader:Server: my sid.local server
route = .* logvar:local=0
route-uri = ^/foo/(.*)\.jpg$ cache:key=$1.jpg
route = .* last:
route-label = localhost
route-user-agent = .*curl.* redirect:http://uwsgi.it
route-remote-addr = ^127\.0\.0\.1$ break:403 Forbidden
route = ^/test log:someone called /test
route = \.php$ rewrite:/index.php
route = .* addheader:Server: my uWSGI server
route = .* logvar:local=1
route-uri = ^/foo/(.*)\.jpg$ cache:key=$1.jpg
route = .* last:
这个例子像前面那个,但域之间存在些不同。看看”last:”的时候,来终端路由表扫描。你可以将前2个规则写成一个:
[uwsgi]
route-host = (.*) goto:$1
收集响应头¶
正如我们已经看到的,每个uWSGI请求都有一组相关的变量。它们一般是由web服务器传递的CGI变量,但是你也可以用其他变量来扩展它们 (看看’addvar’动作)。
uWSGI 1.9.16添加了一个新特性,允许你存储一个响应头的内容到一个请求变量中。这让编写更高级的规则变得更简单。
例如,你可能想要gzip所有的text/html响应:
[uwsgi]
; store Content-Type response header in MY_CONTENT_TYPE var
collect-header = Content-Type MY_CONTENT_TYPE
; if response is text/html, and client supports it, gzip it
response-route-if = equal:${MY_CONTENT_TYPE};text/html goto:gzipme
response-route-run = last:
response-route-label = gzipme
; gzip only if the client support it
response-route-if = contains:${HTTP_ACCEPT_ENCODING};gzip gzip:
可用动作¶
break
¶
返回值: BREAK
停止扫描内部路由表,并关闭该请求。可以选择返回指定的HTTP状态码:
[uwsgi]
route = ^/notfound break:404 Not Found
route = ^/bad break:
route = ^/error break:500
注意: break
并不支持请求变量,因为它旨在通知浏览器有关错误,而不是通知终端用户。也就是说,我们可以判定以下代码将会发送它读取的内容给浏览器 (即 ${REMOTE_ADDR}
并不会被转换成远程IP地址)。
[uwsgi]
route-remote-addr = ^127\.0\.0\.1$ break:403 Forbidden for ip ${REMOTE_ADDR}
如果你真的想要做些古古怪怪的东西,那么可以看看 clearheaders
。
return
/break-with-status
¶
返回值: BREAK
return
使用uWSGI内置的状态码并同时返回状态码和消息体。它类似于 break
,但正如上面提到的, break
并没有错误消息体。 return:403
等价于:
[uwsgi]
route-run = clearheaders:403 Forbidden
route-run = addheader:Content-Type: text/plain
route-run = addheader:Content-Length: 9
route-run = send:Forbidden
route-run = break:
send-crnl
¶
返回值: NEXT
非常高级 (且危险) 的函数,允许你添加原始数据到响应中,以rn作为后缀。
[uwsgi]
route = ^/foo/(.) send-crnl:HTTP/1.0 100 Continue
redirect-permanent
/redirect-301
¶
返回值: BREAK
插件: router_redirect
返回一个HTTP 3301 Permanent Redirect给指定的URL。
rewrite
¶
返回值: NEXT
插件: router_rewrite
一个重写引擎,灵感来自于Apache mod_rewrite。在请求被派发给请求处理函数之前,根据指定的规则重建PATH_INFO和 QUERY_STRING。
[uwsgi]
route-uri = ^/foo/(.*) rewrite:/index.php?page=$1.php
rewrite-last
¶
rewrite的别名,但是返回值是 CONTINUE
, 直接传递请求到请求处理器next。
uwsgi
¶
返回值: BREAK
插件: router_uwsgi
重写一个请求的modifier1, modifier2和可选的 UWSGI_APPID
值,或者路由请求到一个外部的uwsgi服务器。
[uwsgi]
route = ^/psgi uwsgi:127.0.0.1:3031,5,0
这个配置路由所有以 /psgi
开始的请求到在127.0.0.1:3031上运行的uwsgi服务器,设置modifier1为5,设置modifier2为0.如果你只想修改modifier1/modifier2,而不想将请求路由到一个外部服务器,那么使用以下语法。
[uwsgi]
route = ^/psgi uwsgi:,5,0
要设置一个指定的 UWSGI_APPID
值,在其后附加。
[uwsgi]
route = ^/psgi uwsgi:127.0.0.1:3031,5,0,fooapp
子请求是异步型的 (支持诸如gevent或者ugreen这样的引擎),如果offload线程可用,那么将会使用它们。
http
¶
返回值: BREAK
插件: router_http
将请求路由到外部HTTP服务器。
[uwsgi]
route = ^/zope http:127.0.0.1:8181
你可以使用以下语法来替换一个替代的Host头:
[uwsgi]
route = ^/zope http:127.0.0.1:8181,myzope.uwsgi.it
static
¶
返回值: BREAK
插件: router_static
提供来自指定物理路径的静态文件。
[uwsgi]
route = ^/logo static:/var/www/logo.png
basicauth
¶
返回值: NEXT
or BREAK 401
on failed authentication
插件: router_basicauth
支持四种语法。
basicauth:realm,user:password
– 一个简单的用户名:密码映射basicauth:realm,user:
– 只鉴权用户名basicauth:realm,htpasswd
– 使用一个类htpasswd文件。支持所有的POSIX crypt()算法。这与Apache传统的htpasswd文件 _不_ 同,因此,使用htpasswd工具的-d
标志来创建兼容文件。basicauth:realm,
– 用来立即引起一个HTTP 401响应。 由于路由是从上至下解析的,因此,你也许想要引发这个来避免绕过规则。
例如:
[uwsgi]
route = ^/foo basicauth-next:My Realm,foo:bar
route = ^/foo basicauth:My Realm,foo2:bar2
route = ^/bar basicauth:Another Realm,kratos:
例如: 对Trac使用basicauth
[uwsgi]
; load plugins (if required)
plugins = python,router_basicauth
; bind to port 9090 using http protocol
http-socket = :9090
; set trac instance path
env = TRAC_ENV=myinstance
; load trac
module = trac.web.main:dispatch_request
; trigger authentication on /login
route = ^/login basicauth-next:Trac Realm,pippo:pluto
route = ^/login basicauth:Trac Realm,foo:bar
;high performance file serving
static-map = /chrome/common=/usr/local/lib/python2.7/dist-packages/trac/htdocs
basicauth-next
¶
与 basicauth
相同,但在鉴权失败的时候返回 NEXT
。
ldapauth
¶
返回值:鉴权失败的时候返回 NEXT
或者 BREAK 401
插件: ldap
这个鉴权路由器是LDAP插件的一部分,因此为了让这个能用,必须加载它。它就像basicauth路由器,但是使用一个LDAP服务器来进行鉴权,语法: ldapauth:realm,options
可用选项:
url
- LDAP服务器URI (必填)binddn
- 用于绑定的DN。如果LDAP服务器不允许匿名搜索,那么该选项必填。bindpw
-binddn
用户的密码basedn
- 搜索用户时使用的基本DN (必填)filter
- 搜索用户时使用的过滤器 (默认是 “(objectClass=*)”)attr
- LDAP属性,保存用户登录 (默认是”uid”)loglevel
- 0 - 不记录任何绑定,1 - 记录鉴权错误,2 - 同时记录成功和失败的绑定
例如:
route = ^/protected ldapauth:LDAP auth realm,url=ldap://ldap.domain.com;basedn=ou=users,dc=domain;binddn=uid=proxy,ou=users,dc=domain;bindpw=password;loglevel=1;filter=(objectClass=posixAccount)
ldapauth-next
¶
与ldapauth相同,但是鉴权失败的时候返回 NEXT
。
cachestore
/cache-store
¶
cachevar
¶
cacheset
¶
memcached
¶
rpc
¶
“rpc”路由指令让你可以直接调用来自路由子系统的uWSGI RPC函数,并且将它们的输出转发到客户端。
[uwsgi]
http-socket = :9090
route = ^/foo addheader:Content-Type: text/html
route = ^/foo rpc:hello ${REQUEST_URI} ${HTTP_USER_AGENT}
route = ^/bar/(.+)$ rpc:test $1 ${REMOTE_ADDR} uWSGI %V
route = ^/pippo/(.+)$ rpc:[email protected]:4141 $1 ${REMOTE_ADDR} uWSGI %V
import = funcs.py
call
¶
插件: rpc
rpcraw
¶
插件: rpc
access
¶
spnego
¶
开发中……
radius
¶
开发中……
donotlog
¶
chdir
¶
seturi
¶
更新 REQUEST_URI
setapp
¶
setuser
¶
sethome
¶
setfile
¶
setscriptname
¶
setprocname
¶
alarm
¶
flush
¶
fixcl
¶
cgi
¶
插件: cgi
cgihelper
¶
插件: cgi
access
¶
插件: router_access
cache-continue
¶
插件: router_cache
cachevar
¶
插件: router_cache
cacheinc
¶
插件: router_cache
cachedec
¶
插件: router_cache
cachemul
¶
插件: router_cache
cachediv
¶
插件: router_cache
proxyhttp
¶
插件: router_http
memcached
¶
插件: router_memcached
memcached-continue
¶
插件: router_memcached
memcachedstore
¶
插件: router_memcached
memcached-store
¶
插件: router_memcached
redis
¶
插件: router_redis
redis-continue
¶
插件: router_redis
redisstore
¶
插件: router_redis
redis-store
¶
插件: router_redis
proxyuwsgi
¶
插件: router_uwsgi
harakiri
¶
为当前请求设置harakiri。
file
¶
在 不 使用加速(sendfile, offloading等等)的情况下直接转移指定的文件名。
[uwsgi]
http-socket = :9090
route-run = file:filename=/var/www/${PATH_INFO}
clearheaders
¶
清除响应头,设置一个新的HTTP状态码,可用于重置响应
[uwsgi]
http-socket = :9090
response-route = ^/foo goto:foobar
response-route-run = last:
response-route-label = foobar
response-route-run = clearheaders:404 Not Found
response-route-run = addheader:Content-Type: text/html
resetheaders
¶
clearheaders的别名
xattr
¶
插件: xattr
xattr插件允许你在内部路由子系统中引用文件扩展属性:
[uwsgi]
...
route-run = addvar:MYATTR=user.uwsgi.foo.bar
route-run = log:The attribute is ${xattr[/tmp/foo:MYATTR]}
或者 (带2个变量的变体)
[uwsgi]
...
route-run = addvar:MYFILE=/tmp/foo
route-run = addvar:MYATTR=user.uwsgi.foo.bar
route-run = log:The attribute is ${xattr2[MYFILE:MYATTR]}
仅在Linux平台上可用。