代码片段
这是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