代码片段

这是uWSGI特性的一些最“有趣”的使用的集合。

X-Sendfile模拟

即使你的前端代理/web服务器不支持X-Sendfile (或者不能访问你的静态资源),但是你可以使用uWSGI的内部卸载(你的进程/线程将会委托实际的静态文件服务给卸载线程)来模拟它。

[uwsgi]
...
; load router_static plugin (compiled in by default in monolithic profiles)
plugins = router_static
; spawn 2 offload threads
offload-threads = 2
; files under /private can be safely served
static-safe = /private
; collect the X-Sendfile response header as X_SENDFILE var
collect-header = X-Sendfile X_SENDFILE
; if X_SENDFILE is not empty, pass its value to the "static" routing action (it will automatically use offloading if available)
response-route-if-not = empty:${X_SENDFILE} static:${X_SENDFILE}

强制使用HTTPS

这将会强制整个站点使用HTTPS。

[uwsgi]
...
; load router_redirect plugin (compiled in by default in monolithic profiles)
plugins = router_redirect
route-if-not = equal:${HTTPS};on redirect-permanent:https://${HTTP_HOST}${REQUEST_URI}

而这个只会强制 /admin (使用HTTPS)

[uwsgi]
...
; load router_redirect plugin (compiled in by default in monolithic profiles)
plugins = router_redirect
route = ^/admin goto:https
; stop the chain
route-run = last:

route-label = https
route-if-not = equal:${HTTPS};on redirect-permanent:https://${HTTP_HOST}${REQUEST_URI}

最终,你可能也想要发送HSTS (强制安全传输技术,HTTP Strict Transport Security)头。

[uwsgi]
...
; load router_redirect plugin (compiled in by default in monolithic profiles)
plugins = router_redirect
route-if-not = equal:${HTTPS};on redirect-permanent:https://${HTTP_HOST}${REQUEST_URI}
route-if = equal:${HTTPS};on addheader:Strict-Transport-Security: max-age=31536000

Python自动重载 (DEVELOPMENT ONLY!)

在生产环境中,你可以监控文件/目录的改动来触发重载 (touch-reload, fs-reload...).

在开发期间,拥有一个用于所有的重载/已用python模块的监控器是很方便的。但是请只在开发期间才使用它。

检查是通过一个以指定频率扫描模块列表的线程来完成的:

[uwsgi]
...
py-autoreload = 2

将会每2秒检查一次python模块的改动,并最终重启实例。

再次说明:

警告

只在开发时使用它。

全栈CGI设置

这个例子从一个uWSGI的邮件列表线程生成。

我们在/var/www中有静态文件,在/var/cgi中有cgi。将会使用/cgi-bin挂载点访问cgi。所以将会在到/cgi-bin/foo.lua的请求上运行/var/cgi/foo.lua

[uwsgi]
workdir = /var
ipaddress = 0.0.0.0

; start an http router on port 8080
http = %(ipaddress):8080
; enable the stats server on port 9191
stats = 127.0.0.1:9191
; spawn 2 threads in 4 processes (concurrency level: 8)
processes = 4
threads = 2
; drop privileges
uid = nobody
gid = nogroup

; serve static files in /var/www
static-index = index.html
static-index = index.htm
check-static = %(workdir)/www

; skip serving static files ending with .lua
static-skip-ext = .lua

; route requests to the CGI plugin
http-modifier1 = 9
; map /cgi-bin requests to /var/cgi
cgi = /cgi-bin=%(workdir)/cgi
; only .lua script can be executed
cgi-allowed-ext = .lua
; .lua files are executed with the 'lua' command (it avoids the need of giving execute permission to files)
cgi-helper = .lua=lua
; search for index.lua if a directory is requested
cgi-index = index.lua

不同挂载点中的多个flask应用

写3个flask应用:

#app1.py
from flask import Flask
app = Flask(__name__)

@app.route("/")
def hello():
    return "Hello World! i am app1"
#app2.py
from flask import Flask
app = Flask(__name__)

@app.route("/")
def hello():
    return "Hello World! i am app2"
#app3.py
from flask import Flask
app = Flask(__name__)

@app.route("/")
def hello():
    return "Hello World! i am app3"

每个将会被分别挂载在/app1, /app2, /app3上

要在uWSGI中挂载一个使用指定“键”的应用,使用–mount选项:

` --mount <mountpoint>=<app> `

在我们的例子中,我们想要挂载3个python应用,每个使用WSGI SCRIPT_NAME变量名作为键:

[uwsgi]
plugin = python
mount = /app1=app1.py
mount = /app2=app2.py
mount = /app3=app3.py
; generally flask apps expose the 'app' callable instead of 'application'
callable = app

; tell uWSGI to rewrite PATH_INFO and SCRIPT_NAME according to mount-points
manage-script-name = true

; bind to a socket
socket = /var/run/uwsgi.sock

现在,直接将你的webserver.proxy指向实例socket (无需进行额外的配置)

注意事项:默认情况下,每个应用都会在一个新的python解释器中加载 (那意味着每个应用都有一个相当棒的隔离的名字空间)。如果你想要在相同的python vm中加载所有的应用,那么使用–single-interpreter选项。

另一个注意事项:你或许发现到一个不起眼的”modifier1 30”引用技巧。它已经被弃用了,并且非常丑陋。uWSGI能够以许多种高级方式重写请求变量

最后一个注意事项:默认情况下,第一个加载的应用作为”默认应用”挂载。当没有挂载点匹配上的时候,将会使用那个应用。

OSX上的rbenv (应该也能在其他平台上用)

安装rbenv

brew update
brew install rbenv ruby-build

(不要像经典的howto中描述的那样在.bash_profile中设置魔术行,因为我们希望不破坏环境,并且让uWSGI摆脱它)

获取一个uWSGI压缩包,并且构建’nolang’版本 (它是一个单片版本,其中并未编译任何语言插件)

wget http://projects.unbit.it/downloads/uwsgi-latest.tar.gz
tar zxvf uwsgi-latest.tar.gz
cd uwsgi-xxx
make nolang

现在,开始安装你需要的ruby版本

rbenv install 1.9.3-p551
rbenv install 2.1.5

并且安装你需要的gem (这个例子中是sinatra):

# set the current ruby env
rbenv local 1.9.3-p551
# get the path of the gem binary
rbenv which gem
# /Users/roberta/.rbenv/versions/1.9.3-p551/bin/gem
/Users/roberta/.rbenv/versions/1.9.3-p551/bin/gem install sinatra
# from the uwsgi sources directory, build the rack plugin for 1.9.3-p551, naming it rack_193_plugin.so
# the trick here is changing PATH to find the right ruby binary during the build procedure
PATH=/Users/roberta/.rbenv/versions/1.9.3-p551/bin:$PATH ./uwsgi --build-plugin "plugins/rack rack_193"
# set ruby 2.1.5
rbenv local 2.1.5
rbenv which gem
# /Users/roberta/.rbenv/versions/2.1.5/bin/gem
/Users/roberta/.rbenv/versions/2.1.5/bin/gem install sinatra
PATH=/Users/roberta/.rbenv/versions/2.1.5/bin:$PATH ./uwsgi --build-plugin "plugins/rack rack_215"

现在,从一个ruby切换到另一个,只需修改插件:

[uwsgi]
plugin = rack_193
rack = config.ru
http-socket = :9090

或者

[uwsgi]
plugin = rack_215
rack = config.ru
http-socket = :9090

确保插件存储在当前的工作目录中,或者设置plugins-dir指令,又或者像这样涌绝对路径指定它们

[uwsgi]
plugin = /foobar/rack_215_plugin.so
rack = config.ru
http-socket = :9090

认证的WebSocket代理

应用服务器识别websocket流量,相对于应用自身策略/基础架构,使用任何CGI变量来认证/鉴权用户,然后卸载/代理请求到一个简单的kafka-websocket后端。

首先,创建 auth_kafka.py:

from pprint import pprint

def application(environ, start_response):
    start_response('200 OK', [('Content-Type', 'text/plain')])
    return ['It Works!']

def auth_kafka(request_uri, http_cookie, http_authorization):
    pprint(locals())
    return 'true'

import uwsgi
uwsgi.register_rpc('auth_kafka', auth_kafka)

然后创建 auth_kafka.ini:

[uwsgi]

; setup
http-socket = 127.0.0.1:8000
master = true
module = auth_kafka

; critical! else worker timeouts apply to proxied websocket connections
offload-threads = 2

; match websocket protocol
kafka-ws-upgrade-regex = ^[Ww]eb[Ss]ocket$

; DRY place for websocket check
is-kafka-ws-request =  regexp:${HTTP_UPGRADE};%(kafka-ws-upgrade-regex)

; location of the kafka-ws server
kafka-ws-host = 127.0.0.1:7080

; base endpoint uri for websocket server
kafka-ws-endpoint-uri = /v2/broker/

; call auth_kafka(...); if AUTH_KAFKA gets set, request is good!
route-if = %(is-kafka-ws-request) rpcvar:AUTH_KAFKA auth_kafka ${REQUEST_URI} ${HTTP_COOKIE} ${HTTP_AUTHORIZATION}

; update request uri to websocket endpoint (rewrite only changes PATH_INFO?)
route-if-not = empty:${AUTH_KAFKA} seturi:%(kafka-ws-endpoint-uri)?${QUERY_STRING}

; route the request to our websocket server
route-if-not = empty:${AUTH_KAFKA} httpdumb:%(kafka-ws-host)

启动一个”kafka-websocket”服务器:

nc -l -k -p 7080

现在,在一个web浏览器中访问 http://127.0.0.1:8000 !你应该看到 Hello! 。打开chrome的查看器或者firebug,然后输入:

ws = new WebSocket('ws://127.0.0.1:8000/?subscribe=true')

你应该看到这个请求代理到你的 nc 命令!这个模式允许内部网络托管一个或多或少全开/通用的kafka -> websocket网关,并且委托认证需求给应用服务器。使用 offload-threads 意味着代理请求 不会 阻塞worker;使用 httpdumb 避免了重整请求 (http 动作强制使用 HTTP/1.0)

SELinux和uWSGI

SELinux允许你将web应用进程彼此隔离,并且限制每个程序只用于自身目的。应用可以被放置于高度隔离的独立沙箱中,将它们与其他应用以及底层操作系统分离开来。由于SELinux是在内核中实现的,因此不需要特殊编写或修改应用就能让其在SELinux之下使用。github上有一个 SELinux security policy for web applications ,非常适于uWSGI。这个安全策略也支持运行在一个域中的uWSGI emperor进程,以及运行在一个分隔域中的每个web应用的worker进程,即使使用了Linux名字空间,worker进程也只要求最小的特权。当然,使用SELinux不要求emperor模式,或者Linux名字空间。

在Linux上,有可能使用文件系统、ipc、uts、网络、pid和uid的专有视图来运行每个vassal。然后,每个vassal可以,比方说,修改文件系统布局、网络和主机名,而无需损坏主系统。有了这个设置,特权任务,例如挂载文件系统、设置主机名、配置网络和设置worker进程的gid和uid就可以在修改vassal进程的SELinux安全上下文之前完成了,确保每个worker进程只需要最少的特权。

首先,配置、编译和加载SELinux web应用安全策略。然后,重新标记应用文件。关于如何配置web应用策略的进一步信息可以在 SELinux security policy for web applications 中包含的README.md上找到。最后,在每个vassal的配置文件中,调用libselinux的setcon函数来设置web应用的SELinux安全上下文:

[uwsgi]
...
hook-as-user = callret:setcon system_u:system_r:webapp_id_t:s0

其中,id是域的标识。例如,foo是webapp_foo_t域的标识。

也许需要使用–dlopen选项在uWSGI地址空间内加载libselinux:

/path/to/uwsgi --dlopen /path/to/libselinux.so