代码片段

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

X-Sendfile模拟

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

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

强制使用HTTPS

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

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

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

  1. [uwsgi]
  2. ...
  3. ; load router_redirect plugin (compiled in by default in monolithic profiles)
  4. plugins = router_redirect
  5. route = ^/admin goto:https
  6. ; stop the chain
  7. route-run = last:
  8.  
  9. route-label = https
  10. route-if-not = equal:${HTTPS};on redirect-permanent:https://${HTTP_HOST}${REQUEST_URI}

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

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

Python自动重载 (DEVELOPMENT ONLY!)

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

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

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

  1. [uwsgi]
  2. ...
  3. py-autoreload = 2

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

再次说明:

警告

只在开发时使用它。

全栈CGI设置

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

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

  1. [uwsgi]
  2. workdir = /var
  3. ipaddress = 0.0.0.0
  4.  
  5. ; start an http router on port 8080
  6. http = %(ipaddress):8080
  7. ; enable the stats server on port 9191
  8. stats = 127.0.0.1:9191
  9. ; spawn 2 threads in 4 processes (concurrency level: 8)
  10. processes = 4
  11. threads = 2
  12. ; drop privileges
  13. uid = nobody
  14. gid = nogroup
  15.  
  16. ; serve static files in /var/www
  17. static-index = index.html
  18. static-index = index.htm
  19. check-static = %(workdir)/www
  20.  
  21. ; skip serving static files ending with .lua
  22. static-skip-ext = .lua
  23.  
  24. ; route requests to the CGI plugin
  25. http-modifier1 = 9
  26. ; map /cgi-bin requests to /var/cgi
  27. cgi = /cgi-bin=%(workdir)/cgi
  28. ; only .lua script can be executed
  29. cgi-allowed-ext = .lua
  30. ; .lua files are executed with the 'lua' command (it avoids the need of giving execute permission to files)
  31. cgi-helper = .lua=lua
  32. ; search for index.lua if a directory is requested
  33. cgi-index = index.lua

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

写3个flask应用:

  1. #app1.py
  2. from flask import Flask
  3. app = Flask(__name__)
  4.  
  5. @app.route("/")
  6. def hello():
  7. return "Hello World! i am app1"
  1. #app2.py
  2. from flask import Flask
  3. app = Flask(__name__)
  4.  
  5. @app.route("/")
  6. def hello():
  7. return "Hello World! i am app2"
  1. #app3.py
  2. from flask import Flask
  3. app = Flask(__name__)
  4.  
  5. @app.route("/")
  6. def hello():
  7. return "Hello World! i am app3"

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

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

--mount <mountpoint>=<app>

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

  1. [uwsgi]
  2. plugin = python
  3. mount = /app1=app1.py
  4. mount = /app2=app2.py
  5. mount = /app3=app3.py
  6. ; generally flask apps expose the 'app' callable instead of 'application'
  7. callable = app
  8.  
  9. ; tell uWSGI to rewrite PATH_INFO and SCRIPT_NAME according to mount-points
  10. manage-script-name = true
  11.  
  12. ; bind to a socket
  13. socket = /var/run/uwsgi.sock

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

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

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

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

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

安装rbenv

  1. brew update
  2. brew install rbenv ruby-build

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

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

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

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

  1. rbenv install 1.9.3-p551
  2. rbenv install 2.1.5

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

  1. # set the current ruby env
  2. rbenv local 1.9.3-p551
  3. # get the path of the gem binary
  4. rbenv which gem
  5. # /Users/roberta/.rbenv/versions/1.9.3-p551/bin/gem
  6. /Users/roberta/.rbenv/versions/1.9.3-p551/bin/gem install sinatra
  7. # from the uwsgi sources directory, build the rack plugin for 1.9.3-p551, naming it rack_193_plugin.so
  8. # the trick here is changing PATH to find the right ruby binary during the build procedure
  9. PATH=/Users/roberta/.rbenv/versions/1.9.3-p551/bin:$PATH ./uwsgi --build-plugin "plugins/rack rack_193"
  10. # set ruby 2.1.5
  11. rbenv local 2.1.5
  12. rbenv which gem
  13. # /Users/roberta/.rbenv/versions/2.1.5/bin/gem
  14. /Users/roberta/.rbenv/versions/2.1.5/bin/gem install sinatra
  15. PATH=/Users/roberta/.rbenv/versions/2.1.5/bin:$PATH ./uwsgi --build-plugin "plugins/rack rack_215"

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

  1. [uwsgi]
  2. plugin = rack_193
  3. rack = config.ru
  4. http-socket = :9090

或者

  1. [uwsgi]
  2. plugin = rack_215
  3. rack = config.ru
  4. http-socket = :9090

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

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

认证的WebSocket代理

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

首先,创建 auth_kafka.py:

  1. from pprint import pprint
  2.  
  3. def application(environ, start_response):
  4. start_response('200 OK', [('Content-Type', 'text/plain')])
  5. return ['It Works!']
  6.  
  7. def auth_kafka(request_uri, http_cookie, http_authorization):
  8. pprint(locals())
  9. return 'true'
  10.  
  11. import uwsgi
  12. uwsgi.register_rpc('auth_kafka', auth_kafka)

然后创建 auth_kafka.ini:

  1. [uwsgi]
  2.  
  3. ; setup
  4. http-socket = 127.0.0.1:8000
  5. master = true
  6. module = auth_kafka
  7.  
  8. ; critical! else worker timeouts apply to proxied websocket connections
  9. offload-threads = 2
  10.  
  11. ; match websocket protocol
  12. kafka-ws-upgrade-regex = ^[Ww]eb[Ss]ocket$
  13.  
  14. ; DRY place for websocket check
  15. is-kafka-ws-request = regexp:${HTTP_UPGRADE};%(kafka-ws-upgrade-regex)
  16.  
  17. ; location of the kafka-ws server
  18. kafka-ws-host = 127.0.0.1:7080
  19.  
  20. ; base endpoint uri for websocket server
  21. kafka-ws-endpoint-uri = /v2/broker/
  22.  
  23. ; call auth_kafka(...); if AUTH_KAFKA gets set, request is good!
  24. route-if = %(is-kafka-ws-request) rpcvar:AUTH_KAFKA auth_kafka ${REQUEST_URI} ${HTTP_COOKIE} ${HTTP_AUTHORIZATION}
  25.  
  26. ; update request uri to websocket endpoint (rewrite only changes PATH_INFO?)
  27. route-if-not = empty:${AUTH_KAFKA} seturi:%(kafka-ws-endpoint-uri)?${QUERY_STRING}
  28.  
  29. ; route the request to our websocket server
  30. route-if-not = empty:${AUTH_KAFKA} httpdumb:%(kafka-ws-host)

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

  1. nc -l -k -p 7080

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

  1. 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安全上下文:

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

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

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

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