WSGI详解
WSGI接口
了解了HTTP协议和HTML文档,我们其实就明白了一个Web应用的本质就是:
浏览器发送一个HTTP请求;
服务器收到请求,生成一个HTML文档;
服务器把HTML文档作为HTTP响应的Body发送给浏览器;
浏览器收到HTTP响应,从HTTP Body取出HTML文档并显示。
所以,最简单的Web应用就是先把HTML用文件保存好,用一个现成的HTTP服务器软件,接收用户请求,从文件中读取HTML,返回。Apache、Nginx、Lighttpd等这些常见的静态服务器就是干这件事情的。
如果要动态生成HTML,就需要把上述步骤自己来实现。不过,接受HTTP请求、解析HTTP请求、发送HTTP响应都是苦力活,如果我们自己来写这些底层代码,还没开始写动态HTML呢,就得花个把月去读HTTP规范。
正确的做法是底层代码由专门的服务器软件实现,我们用Python专注于生成HTML文档。因为我们不希望接触到TCP连接、HTTP原始请求和响应格式,所以,需要一个统一的接口,让我们专心用Python编写Web业务。
这个接口就是WSGI:Web Server Gateway Interface。
WSGI接口定义非常简单,它只要求Web开发者实现一个函数,就可以响应HTTP请求。我们来看一个最简单的Web版本的“Hello, web!”:
def application(environ, start_response):
start_response('200 OK', [('Content-Type', 'text/html')])
return '<h1>Hello, web!</h1>'
上面的application()
函数就是符合WSGI标准的一个HTTP处理函数,它接收两个参数:
environ:一个包含所有HTTP请求信息的
dict
对象;start_response:一个发送HTTP响应的函数。
在application()
函数中,调用:
start_response('200 OK', [('Content-Type', 'text/html')])
就发送了HTTP响应的Header,注意Header只能发送一次,也就是只能调用一次start_response()
函数。start_response()
函数接收两个参数,一个是HTTP响应码,一个是一组list
表示的HTTP Header,每个Header用一个包含两个str
的tuple
表示。
通常情况下,都应该把Content-Type
头发送给浏览器。其他很多常用的HTTP Header也应该发送。
然后,函数的返回值'<h1>Hello, web!</h1>'
将作为HTTP响应的Body发送给浏览器。
有了WSGI,我们关心的就是如何从environ
这个dict
对象拿到HTTP请求信息,然后构造HTML,通过start_response()
发送Header,最后返回Body。
整个application()
函数本身没有涉及到任何解析HTTP的部分,也就是说,底层代码不需要我们自己编写,我们只负责在更高层次上考虑如何响应请求就可以了。
不过,等等,这个application()
函数怎么调用?如果我们自己调用,两个参数environ
和start_response
我们没法提供,返回的str
也没法发给浏览器。
所以application()
函数必须由WSGI服务器来调用。有很多符合WSGI规范的服务器,我们可以挑选一个来用。但是现在,我们只想尽快测试一下我们编写的application()
函数真的可以把HTML输出到浏览器,所以,要赶紧找一个最简单的WSGI服务器,把我们的Web应用程序跑起来。
好消息是Python内置了一个WSGI服务器,这个模块叫wsgiref,它是用纯Python编写的WSGI服务器的参考实现。所谓“参考实现”是指该实现完全符合WSGI标准,但是不考虑任何运行效率,仅供开发和测试使用。
运行WSGI服务
我们先编写hello.py
,实现Web应用程序的WSGI处理函数:
# hello.py
def application(environ, start_response):
start_response('200 OK', [('Content-Type', 'text/html')])
return '<h1>Hello, web!</h1>'
然后,再编写一个server.py
,负责启动WSGI服务器,加载application()
函数:
# server.py
# 从wsgiref模块导入:
from wsgiref.simple_server import make_server
# 导入我们自己编写的application函数:
from hello import application
# 创建一个服务器,IP地址为空,端口是8000,处理函数是application:
httpd = make_server('', 8000, application)
print "Serving HTTP on port 8000..."
# 开始监听HTTP请求:
httpd.serve_forever()
确保以上两个文件在同一个目录下,然后在命令行输入python server.py
来启动WSGI服务器:
注意:如果8000
端口已被其他程序占用,启动将失败,请修改成其他端口。
启动成功后,打开浏览器,输入http://localhost:8000/
,就可以看到结果了:
在命令行可以看到wsgiref打印的log信息:
按Ctrl+C
终止服务器。
如果你觉得这个Web应用太简单了,可以稍微改造一下,从environ
里读取PATH_INFO
,这样可以显示更加动态的内容:
# hello.py
def application(environ, start_response):
start_response('200 OK', [('Content-Type', 'text/html')])
return '<h1>Hello, %s!</h1>' % (environ['PATH_INFO'][1:] or 'web')
你可以在地址栏输入用户名作为URL的一部分,将返回Hello, xxx!
:
是不是有点Web App的感觉了?
小结
无论多么复杂的Web应用程序,入口都是一个WSGI处理函数。HTTP请求的所有输入信息都可以通过environ
获得,HTTP响应的输出都可以通过start_response()
加上函数返回值作为Body。
复杂的Web应用程序,光靠一个WSGI函数来处理还是太底层了,我们需要在WSGI之上再抽象出Web框架,进一步简化Web开发。
WSGI是作为Web服务器与Web应用程序或应用框架之间的一种低级别的接口 。WSGI is the Web Server Gateway Interface. It is a specification for web servers and application servers to communicate with web applications (though it can also be used for more than that
WSGI有两方:“服务器 ”或“网关”一方,以及“应用程序”或“应用框架”一方。WSIG架构:
WSGI
中间件
同时实现了API的两方,因此可以在WSGI服务和WSGI应用之间起调解作用,“中间件”组件可以执行以下功能:
- 重写环境变量
后,根据目标URL
,将请求消息路由到不同的应用对象。 - 允许在一个进程
中同时运行多个应用程序或应用框架。 - 负载均衡
和远程处理,通过在网络
上转发请求和响应消息。 - 进行内容后处理,例如应用XSLT
样式表。
用Python
语言写的一个符合WSGI的“Hello World
”应用程序如下所示:
- def hello_world_app(environ, start_response):
- start_response('200 OK', [('Content-Type', 'text/plain')])
- return "Hello world!\n"
- 第一行定义了一个名为app的callable,
接受两个参数,environ和start_response,environ是一个字典包含了CGI中的环境变量,start_response也是一
个callable,接受两个必须的参数,status(HTTP状态)和response_headers(响应消息的头)。 - 第二行调用了start_response,状态指定为“200 OK”,消息头指定为内容类型是“text/plain”
- 第三行将响应消息的消息体返回。
调用这个程序
- from wsgiref.simple_server import make_server
- httpd = make_server('', 8080, hello_world_app)
- print "Serving on port 8080..."
- # Serve until process is killed
- httpd.serve_forever()
完整的代码:
- #/bin/py
- from wsgiref.simple_server import make_server
- def hello_world_app(environ, start_response):
- status = '200 OK' # HTTP Status
- headers = [('Content-type', 'text/plain')] # HTTP Headers
- start_response(status, headers)
- # The returned object is going to be printed
- return ["Hello World"]
- httpd = make_server('', 8080, hello_world_app)
- print "Serving on port 8080..."
- # Serve until process is killed
- httpd.serve_forever()
访问地址: http://localhost:8080/
参考
http://smartzxy.iteye.com/blog/734050
http://zh.wikipedia.org/wiki/Web%E6%9C%8D%E5%8A%A1%E5%99%A8%E7%BD%91%E5%85%B3%E6%8E%A5%E5%8F%A3
更多关于BaseServer的解释:
BaseServer: 定义基础服务器接口,这些功能接口提供给子类继承。同时提供服务处理的骨架
serve_forever() 循环调用 handle_request()
handle_request() 调用子类的get_request() ,在tcpServer时实则进行accept()应答; 验证处理 verify_request();
最终处理请求 process_request(),
verify_request() 虚函数
process_request() 虚函数,这个函数并没有直接在BaseServer的子类TcpServer中被重载,而是在TcpServer的派生类中通过另一个父类来实
现,比如 ThreadingTCPServer的基类ThreadingMixIn.process_request()实现了此功能函数
finish_request(request, client_address)
执行一次完整的socket数据读入处理,如果是ThreadMixInTcpServer产生的request,这个方法内必须实行循环读取
socket数据,直到socket关闭。(此处 request 就是 socket对象)
def finish_request(self, request, client_address):
“”"Finish one request by instantiating RequestHandlerClass.”"”
self.RequestHandlerClass(request, client_address, self)在finish_request里面便将读取socket数据的任务扔给了RequestHandler去处理了,代码可以跳过去看了
##———————————————
TcpServer: tcp服务器
__init__(self, server_address, RequestHandlerClass) 需要提供服务侦听地址和请求处理类对象
server_bind() 绑定服务器地址
server_activate() 激活服务器
server_close() 关闭服务器
fileno() 返回服务器socket的句柄fd编号
get_request() 接收应答accept()
close_request(request) 关闭socket,request即为socket对象
三种输出处理方式: 阻塞方式、线程处理(ThreadingMixIn)、进程处理(ForkingMixIn)
ThreadingMixIn: 线程模型
process_request( request, client_address) 为请求的链接创建新的线程,在创建线程时直接指定线程入口和参数:
import threading
t = threading.Thread(target = self.process_request_thread,
args = (request, client_address))
if self.daemon_threads:
t.setDaemon (1)process_request_thread() 线程处理socket入口,负责接收数据,代码实现有点绕,看看代码
def process_request_thread(self, request, client_address):
try:
self.finish_request(request, client_address)
self.close_request(request)
except:
self.handle_error(request, client_address)
self.close_request(request)ThreadingMixIn其实就是线程代理,
还是调用finish_request()进入处理tcp数据的循环,处理完成便close_request()。但是finish_request和
close_request并未在ThreadingMinxIn内定义,在哪里呢?
通过研读ThreadingTcpServer,原来通过ThreadingTcpServer这个finish_request又跑回了
BaseServer.finish_request()
ThreadingTCPServer(ThreadingMixIn, TCPServer) 装配成线程池处理的tcp服务器
BaseRequestHandler: 请求处理基础对象,提供统一的行为接口实现处理socket数据。
BaseRequestHandler比较好玩,在构造函数内完成了所有的操作,见代码: def __init__(self, request,
client_address, server):
self.request = request
self.client_address = client_address
self.server = server
try:
self.setup()
self.handle()
self.finish()
finally:
sys.exc_traceback = None # Help garbage collectionsetup()对应的子类会进行初始化处理
self.handle() 直接调用子类的处理函数,可以参考 BaseHTTPRequestHandler(SocketServer.StreamRequestHandler)::handle()
StreamRequestHandler(BaseRequestHandler) 流式socket处理类
setup() 设置好socket对象和读写文件句柄 rfile/wfile
HTTPServer(SocketServer.TCPServer) http服务器
BaseHTTPRequestHandler(SocketServer.StreamRequestHandler) 流式的请求处理类
handle() 处理入口,在基类BaseRequestHandle()的构造函数中直接调用
handle_one_request() 如果不是处理一次则返回false。接收一次socket数据,解析parse_request(),调用对应的do_xxx事件
python 的daemon线程:
如果一个进程的主线程运行完毕而子线程还在执行的话,那么进程就不会退出,直到所有子线程结束为止,如何让主线程结束的时候其他子线程也乖乖的跟老
大撤退呢?那就要把那些不听话的人设置为听话的小弟,使用线程对象的setDaemon()方法,参数为bool型。True的话就代表你要听话,我老大
(主线程)扯呼,你也要跟着撤,不能拖后腿。如果是False的话就不用那么听话了,老大允许你们将在外军命有所不受的。需要注意的是
setDaemon()方法必须在线程对象没有调用start()方法之前调用,否则没效果。
分类: Python |
WSGI(Web Server Gateway Interface)是一种规范
参见PEP 333
http://www.python.org/dev/peps/pep-0333/
WSGI Server有哪些
比如 Django、CherryPy 都自带 WSGI server 主要是测试用途, 发布时则使用生产环境的 WSGI
server
而有些 WSGI 下的框架比如 pylons、bfg 等, 自己不实现 WSGI server。使用 paste 作为 WSGI
server
CherryPy's WSGI server.
wsgi有两方,服务器方 和 应用程序
①服务器方:其调用应用程序,给应用程序提供(环境信息)和(回调函数),
这个回调函数是用来将应用程序设置的http header和status等信息传递给服务器方.
②应用程序:用来生成返回的header,body和status,以便返回给服务器方。
用Python语言写的一个符合WSGI的“Hello World”应用程序如下所示:
def app(environ, start_response): start_response('200 OK', [('Content-Type', 'text/plain')]) yield "Hello world!\n"
其中
- 第一行定义了一个名为app的应用程序,接受两个参数,environ和 start_response,environ是一个字典包含了CGI中的环境变量,start_response也是一个callable,接受两个必 须的参数,status(HTTP状态)和response_headers(响应消息的头)。
- 第二行调用了start_response,状态指定为“200 OK”,消息头指定为内容类型是“text/plain”
- 第三行将响应消息的消息体返回。
Python自带的 wsgiref
WSGI application
一个接口与两个参数
application(environ, start_response)
Demo
[python] view plaincopy
#!
/usr/bin/env python
# Our
tutorial's WSGI server
from
wsgiref.simple_server import make_server
def
application(environ, start_response):
# Sorting and stringifying the environment key, value pairs
response_body = ['%s: %s' % (key, value)
for key, value in sorted(environ.items())]
response_body = '\n'.join(response_body)
status = '200 OK'
response_headers = [('Content-Type', 'text/plain'),
('Content-Length', str(len(response_body)))]
start_response(status, response_headers)
return [response_body]
#
Instantiate the WSGI server.
# It will
receive the request, pass it to the application
# and send
the application's response to the client
httpd =
make_server(
'localhost', # The host name.
8051, # A port number where to wait for the request.
application # Our application object name, in this case a function.
)
httpd.serve_forever()
wsgiref的simple_server说明
server的主要作用是接受client的请求,並把的收到的请求交給RequestHandlerClass处理,
RequestHandlerClass处理完成后回传结果给client
WSGIServer继承关系
WSGIServer-->HTTPServer-->SocketServer.TCPServer-->BaseServer
主要处理流程
serve_forever
_handle_request_noblock()
process_request
finish_request--> RequestHandlerClass()
close_request
serve_forever循环接受client请求, 如果有请求来,
经finish_request方法把请求交给RequestHandlerClass处理,
RequestHandlerClass调用handle()方法处理request,
WSGIRequestHandler的handle()方法把request又交給ServerHandler处理,
ServerHandler调用run執行application方法, 回传网页的结果(含http
header及网页内容)给client
WSGIRequestHandler继承关系
WSGIRequestHandler-->BaseHTTPRequestHandler-->StreamRequestHandler-->BaseRequestHandler
BaseRequestHandler主要方法及处理流程
1、setup()
2、handle()
3、finish()
WSGIRequestHandler主要方法及处理流程
1、get_environ 增加env
2、handle (override)
handler = ServerHandler
handler.run(self.server.get_app())
ServerHandler继承关系
ServerHandler-->SimpleHandler-->BaseHandler
run方法
setup_environ
self.result = application(self.environ,
self.start_response)
self.finish_response
过去的这个月,接触的最多的就是Python的WSGI了,WSGI不是框架不是模块,仅仅是一个规范协议,定义了一些接口,却影响着Python网络开发的方方面面。对于WSGI有这么一段定义:WSGI is the Web Server Gateway Interface. It is a specification for web servers and application servers to communicate with web applications (though it can also be used for more than that).我想我这篇文章不是详细介绍WSGI内容的,只是想扯扯我对WSGI相关的学习。
诚如那个WSGI的定义所说的,协议定义了一套接口来实现服务器端与应用端通信的规范化(或者说是统一化)。这是怎样的一套接口呢?很简单,尤其是对于应用端。
应用端只需要实现一个接受两个参数的,含有__call__方法的,返回一个可遍历的含有零个或多个string结果的Python对象(我强调说 Python对象,只是想和Java的对象区别开,在Python里一个方法、一个类型……都是对象,Python是真“一切皆对象”,详见 《Python源码分析》)即可。码农都知道,传入参数的名字可以任意取,这里也不例外,但习惯把第一个参数命名为“environ”,第二个为 “start_response”。至于这个对象的内容怎样,应用自由发挥去吧……
服务器端要做的也不复杂,就是对于每一个来访的请求,调用一次应用端“注册”的那个协议规定应用端必须要实现的对象,然后返回相应的响应消息。这样一次 服务器端与应用端的通信也就完成了,一次对用户请求的处理也随之完成了!当然了,既然协议规定了服务器端在调用的时候要传递两个参数,自然也规定了这两个 参数的一些细节。比如第一个参数其实就是一个字典对象,里面是所有从用户请求和服务器环境变量中获取的信息内容,协议当然会定义一些必须有的值,及这些值 对应的变量名;第二个参数其实就是一个回调函数,它向应用端传递一个用来生成响应内容体的write对象,这个对象也是有__call__方法的。
协议也提到了,还可以设计中间件来连接服务器端与应用端,来实现一些通用的功能,比如session、routing等。
具体怎么应用这个协议呢?Python自带的wsgiref模块有个简单的例子:
- from wsgiref.simple_server import make_server
- def hello_world_app(environ, start_response):
- status = '200 OK' # HTTP Status
- headers = [('Content-type', 'text/plain')] # HTTP Headers
- start_response(status, headers)
- # The returned object is going to be printed
- return ["Hello World"]
- httpd = make_server('', 8000, hello_world_app)
- print "Serving on port 8000..."
- # Serve until process is killed
- httpd.serve_forever()
这个例子更多体现的是应用端的开发方法,很简单的按照协议实现一个了满足规范的方法,这样当浏览器向本机8000端口发起一个请求时,就会得到一个 “Hello World”的字符串文本响应。这个例子虽然简单,但非常清楚的说明了应用端与服务器端的接口应用方式。
你可能会想到:现在对该端口的不同地址的请求都是由这个“hello_world_app”函数处理的,你可以实现一个功能,解析一下请求的PATH信 息,针对不同的地址转发给不同的函数或是类来处理;你可能会觉得使用environ和start_response这两个参数不直观,你可以像Java的 servlet那样自己封装成两个request和response对象来用;你觉得有些常用功能可以提取出来,在具体应用逻辑之外来做……哈哈,那你就 已经在思考怎么做中间件或是Web框架了!其实这些也都有人做过了,比如Routes、WebOb、Beaker……当然你大可以自己造自己独有的轮子, 有时候自己做过一遍了才会对现有的成熟的东西有更好的理解,最重要的是在Python的世界里这些都不难做到!
不知你是不是和我一样,在写应用的时候或多或少的会想一下服务器端是怎么运作的呢?可能最模糊的流程大家都能想得到:服务器开一个socket等待客户端 连接;请求来了,服务器会读出传来的数据,然后根据HTTP协议做一些初步的封装,接着就可以调用事先注册的应用程序了,并将请求的数据塞进去;等响应处 理完毕了再把数据通过socket发出去,over。好在Python的代码简洁,而自带的wsgiref中的simple server也很简单,就让我们探究一下更具体的实现吧!
首先看一下类的继承关系,这个simple server真正的类是WSGIServer,继承自HTTPServer,HTTPServer类又继承自TCPServer,TCPServer又继 承自BaseServer;与server类直接打交道的还有RequestHandler类,从最上层的WSGIRequestHandler —> BaseHTTPRequestHandler —> StreamRequestHandler —> BaseRequestHandler。相对Java而言不是很复杂吧,它们是怎么工作的呢?容我稍微解释一下。
让我们从Server的最基类BaseServer看起。它有一段注释非常清楚的介绍了它定义的方法的用处:
- Methods for the caller:
- - __init__(server_address, RequestHandlerClass)
- - serve_forever()
- - handle_request() # if you do not use serve_forever()
- - fileno() -> int # for select()
- Methods that may be overridden:
- - server_bind()
- - server_activate()
- - get_request() -> request, client_address
- - verify_request(request, client_address)
- - server_close()
- - process_request(request, client_address)
- - close_request(request)
- - handle_error()
- Methods for derived classes:
- - finish_request(request, client_address)
可见,一个server类其实就这么几个方法。
在可以被外部调用的四个方法中,构造方法显然就是用来创建实例的;第四个可能是和构建异步服务器有关的,这里就略过了;从具体的代码可以看到,剩下两个 方法的用处是相同的,就是处理收到的请求,只是serve_forever()方法会在server进程存在期间循环处理,而 handle_request()处理一次就退出了(其实server_forever()就是循环调用了handle_request())。在 handle_request()中说明了具体的从接受到返回一个请求的全部流程,代码也很简单:
- def handle_request(self):
- """Handle one request, possibly blocking."""
- try:
- request, client_address = self.get_request()
- except socket.error:
- return
- if self.verify_request(request, client_address):
- try:
- self.process_request(request, client_address)
- except:
- self.handle_error(request, client_address)
- self.close_request(request)
BaseServer虽然定义了这些内部调用的方法,但内容基本都是空的,留给了具体的Server类去实现。从BaseServer的代码中就可以看到 RequestHandler类的用处了,它是具体的解析了request的内容,它由finish_request()调用,而这个 finsh_request()方法显然应该是在process_request()方法中被调用的。
TCPServer继承BaseServer类,它真正具体化了我们猜测的socket连接的初始化过程。
在与上面两个类相同的源文件中,还有两个主要的类:ThreadingMixIn和ForkingMixIn,这两个类分别重载了 process_request()方法,并且相应使用了新建一个线程或是进程的方式来调用finish_request()方法。这也从应用的角度解释 了为什么要在finish_request()外套一层process_request(),而不是直接在handle_request()的第二个 try块中调用。
HTTPServer其实做的工作很简单,就是记录了socket server的名字。
接下来就该看看WSGIServer了。它做了两件新的工作:设置了一些基本的环境变量值,并且接受应用程序的注册。从这个Server的代码可以看出, 应用端实现的那个接口就是从这里注册到服务器端的,而且只能注册一个哦!所以要有多个应用只能通过routing的方式来转发调用了。而且这个 WSGIServer不是多线程或是多进程的~
至于具体封装请求内容的RequestHandler类就不打算分析了,感兴趣的话,看官们自个看一下源码吧,也很简单哦!下一篇博客打算分享一下我对pylons框架的运行过程的学习。
当你在Python的世界中冒险,突然遭遇一只Web怪兽,你会选择什么武器对付它?在兵器谱上,下列兵器可谓名列前茅:
- Zope,厚重的长枪。较早出现的武器形态。该武器非常之强悍,无坚不摧,无物不破。而且适合在军队中使用,排兵布阵集团作战效果更佳。然而要用好Zope要花上很长的时间,俗话说“月棍年刀一辈子枪”,可见其难度。
- TurboGears,威武的刀。快意江湖必备之物。其打造者熟知江湖规矩,颇有武林盟主之风,遇事通常拉帮结伙,分派任务,决计不会把所有事情都揽在自己身上。
- Django,飘逸的剑。非常内敛,据说使用该武器的高手通常是独行侠,他们的格言是:一剑在手,夫复何求?
- Web.py,小巧的匕首,刺客的最爱。常被用来执行特殊任务。
- pylons,诡异的鞭, 传言是Ruby世界的rails披上了Python的外衣,使用起来一定要小心,因为你不知道它会缠住敌人的脖子还是自己的脖子。
然而,我们今天要说的并不是这些武器,而是一种心法。毕竟武器的使用只是“招法”,而心法是招法的灵魂,心法一通,招法百通。这就是由马里奥创造的“管道心法”,西方大陆称其为WSGI(Python web服务网关接口)。
马里奥是一位水管工,常年钻在水管中苦心研究武术。马里奥发现,其实武器无所谓高下,最重要的是看使用武器的人和你要对付的对象。所谓一寸长,一份 强,如Zope威力强大,用来对付大型怪兽很合适,却难免滞重;而一寸短,一份险,如web.py在应付小型灵敏怪兽时有其独特优势。所以单单评论武器的 优劣根本是空泛之谈。于是乎,马里奥在水管中冥思苦想十余载,终于发现了适用于所有武器的心法。掌握此心法,使用任何武器都能游刃有余。由于马里奥是在水 管中受到了启发,故命名为“管道心法”。本文作者在游历时有幸发现此心法,并在“心内求法”上流传出来。传说上古时期的大神道格拉斯·麦克罗伊在参与创世时,曾经构筑了称为Pipeline的时空奇点,用以连接stdout和stdin。马里奥是否受此启发我们不得而知,但“管道心法”确实与此有类似之处:
WSGI是马里奥在探索管道的时候发现的一种连接件,它非常简单,入口处提供一个start_response,用于回流(回 调,callback),入口会连接到出口的一个函数,并传递environ字典和start_response作为参数;而出口处的函数先是调用 start_response并传递status和header,然后再返回content。由于这段心法有些拗口,马里奥演示了唯一的招式,并声明其实 所有的招式都可以从这招中演化出来:
def application(environ, start_response): status = '200 OK' response_headers = [('Content-type','text/plain')] start_response(status, response_headers) return ['Hello']
马里奥还发现,WSGI其实可以串联起来,为了区分,马里奥将下水管的入口叫做web server,只能接受外界的请求并调用下一段管件的函数;中间的管件叫做middleware,既可以接收上一段管件的请求,又可以调用下一段管件的函数;管道的终点叫做web app,只能被上一段管件调用。
为了避免后人误解,马里奥最后强调:武器是死的,好的武器只有在合适的人手里才能发挥最大的威力,争执于武器的好坏毫无意义。
马里奥最后将这段心法奉献给了PEP,PEP将其编号为333。
Linux环境Apache+Django+wsgi配置
在 Django的开发环境,我们简单的通过“python manage.py runserver 0.0.0.0:8000”就可以启动一个简单的HTTP服务器进行Django的开发。当项目开发完成进行发布的时候,这个简单的应用服务器就不能满足 需求了。这时候一个比较好的方案是把Django应用集成到Apache。
Django已经为我们做了许多工作,所以将Django集成到Apache是非常简单的事情。Django集成到Apache有两种方式:python_mod和wsgi,后者相对于前者更加稳定,所以这里我们通过wsgi的方式来进行集成。
推荐阅读:
Ubuntu Server 12.04 安装Nginx+uWSGI+Django环境 http://www.linuxidc.com/Linux/2012-05/60639.htm
Django实战教程 http://www.linuxidc.com/Linux/2013-09/90277.htm
Django Python MySQL Linux 开发环境搭建 http://www.linuxidc.com/Linux/2013-09/90638.htm
--------------------------------分割线--------------------------------
先介绍一下我的环境:
0.CentOS X64
1.Apache 2.2.3
2.Django 1.6.1
集成第一步:安装mod_wsgi
yum insall python26-mod_wsgi.x86_64
安装完成之后检查Apache目录/etc/httpd/conf.d/会出现python26-mod_wsgi.conf,里边已经自动为我们好了加载mod_wsgi.so的配置:
#################################################################################
# Do not enable mod_python and mod_wsgi in the same apache process.
#################################################################################
#
# NOTE: By default python26-mod_python with not load if mod_wsgi is installed
# and enabled. Only load if mod_python and mod_wsgi are not already loaded.
<IfModule !python_module>
<IfModule !wsgi_module>
LoadModule wsgi_module modules/python26-mod_wsgi.so
</IfModule>
</IfModule>
集成第二步:编辑python26-mod_wsgi.conf
WSGIScriptAlias / "/search/lizhigang/mysite/mysite/wsgi.py"
WSGIPythonPath /search/lizhigang/mysite
<Directory "/search/lizhigang/mysite/mysite">
<Files wsgi.py>
Order deny,allow
Allow from all
</Files>
</Directory>
这里需要说明一下,我的Django工程位于“/search/lizhigang/mysite/”,请根据自己工程的位置进行替换。
集成第三步:重启Apache
service apache restart
在浏览器访问你的Django应用(不是8000端口,而是Apache的端口),正常情况下这时候会提示“500,服务器内部错误”。
检查/etc/httpd/logs/error_log,看是否有如下错误:
[Errno 13] Permission denied: '/var/www/.python-eggs'
这时候需要编辑“/search/lizhigang/mysite/mysite/wsgi.py”,对“PYTHON_EGG_CACHE”进行设置:
import os
os.environ.setdefault("DJANGO_SETTINGS_MODULE", "mysite.settings")
os.environ.setdefault("PYTHON_EGG_CACHE", "/tmp/.python-eggs")
from django.core.wsgi import get_wsgi_application
application = get_wsgi_application()
刷新浏览器,这时候Django就成功集成到Apache了。
但是,如果你的应用使用了静态资源,比如图片、css、js,这些,就需要配置static了。我们假设这些资源均位于mysite/static目录。
集成第四步:配置static
打开python26-mod_wsgi.conf,加入对static/访问的支持:
Alias /static/ /search/lizhigang/mysite/static/
<Directory "/static/">
Order allow,deny
Options Indexes
Allow from all
IndexOptions FancyIndexing
</Directory>
刷新网站试试,是不是所有功能都与8000端口开发相同?
至此,我们就完成了Django到Apache的集成。
WSGI是什么?
WSGI,全称 Web Server Gateway Interface,或者 Python Web Server Gateway Interface ,是为 Python 语言定义的 Web 服务器和 Web 应用程序或框架之间的一种简单而通用的接口。自从 WSGI 被开发出来以后,许多其它语言中也出现了类似接口。
WSGI 的官方定义是,the Python Web Server Gateway Interface。从名字就可以看出来,这东西是一个Gateway,也就是网关。网关的作用就是在协议之间进行转换。
WSGI 是作为 Web 服务器与 Web 应用程序或应用框架之间的一种低级别的接口,以提升可移植 Web 应用开发的共同点。WSGI 是基于现存的 CGI 标准而设计的。
很多框架都自带了 WSGI server ,比如 Flask,webpy,Django、CherryPy等等。当然性能都不好,自带的 web server 更多的是测试用途,发布时则使用生产环境的 WSGI server或者是联合 nginx 做 uwsgi 。
- 也就是说,WSGI就像是一座桥梁,一边连着web服务器,另一边连着用户的应用。但是呢,这个桥的功能很弱,有时候还需要别的桥来帮忙才能进行处理。WSGI 的作用如图所示:
WSGI的作用
WSGI有两方:“服务器”或“网关”一方,以及“应用程序”或“应用框架”一方。服务方调用应用方,提供环境信息,以及一个回调函数(提供给应用程序用来将消息头传递给服务器方),并接收Web内容作为返回值。
所谓的 WSGI中间件同时实现了API的两方,因此可以在WSGI服务和WSGI应用之间起调解作用:从WSGI服务器的角度来说,中间件扮演应用程序,而从应用程序的角度来说,中间件扮演服务器。“中间件”组件可以执行以下功能:
- 重写环境变量后,根据目标URL,将请求消息路由到不同的应用对象。
- 允许在一个进程中同时运行多个应用程序或应用框架。
- 负载均衡和远程处理,通过在网络上转发请求和响应消息。
- 进行内容后处理,例如应用XSLT样式表。
WSGI 的设计确实参考了 Java 的 servlet。http://www.python.org/dev/peps/pep-0333/ 有这么一段话:
By contrast, although Java has just as many web application frameworks available, Java's "servlet" API makes it possible for applications written with any Java web application framework to run in any web server that supports the servlet API.
前面小节《来了解一下WSGI这个概念》已经详细介绍过 WSGI 了。
WSGI is the Web Server Gateway Interface. It is a specification for web servers and application servers to communicate with web applications (though it can also be used for more than that)
WSGI是一种Web服务器网关接口。它是一个Web服务器(如nginx)与应用服务器(如uWSGI服务器)通信的一种规范。
接下来,我们要介绍的是 uWSGI。
uWSGI
uWSGI是一个Web服务器,它实现了WSGI协议、uwsgi、http等协议。Nginx中HttpUwsgiModule的作用是与uWSGI服务器进行交换。
要注意 WSGI / uwsgi / uWSGI 这三个概念的区分。
- WSGI看过前面小节的同学很清楚了,是一种通信协议。
- uwsgi同WSGI一样是一种通信协议。
- 而uWSGI是实现了uwsgi和WSGI两种协议的Web服务器。
uwsgi协议是一个uWSGI服务器自有的协议,它用于定义传输信息的类型(type of information),每一个uwsgi packet前4byte为传输信息类型描述,它与WSGI相比是两样东西。
关于uwsgi协议看这里:The uwsgi protocol。
- 为什么有了uWSGI为什么还需要nginx?因为nginx具备优秀的静态内容处理能力,然后将动态内容转发给uWSGI服务器,这样可以达到很好的客户端响应。
uWSGI 的安装很简单:
1 |
pip install uwsgi |
现在我们试下将 Django 跑起来。我们先在 virtualenv 创建一个 Django Project:
1 |
[root@nowamagic ~] # cd nowamagic_venv |
2 |
[root@nowamagic nowamagic_venv] # source bin/activate |
3 |
(nowamagic_venv)[root@nowamagic nowamagic_venv] # django-admin.py startproject nowamagic_pj |
virtualenv 的路径与目录文件如下:
Django Project 的路径与目录文件如下:
测试uwsgi
在你的服务器上写一个test.py:
1 |
# test.py |
2 |
def application(env, start_response): |
3 |
start_response( '200 OK' , [( 'Content-Type' , 'text/html' )]) |
4 |
return "Hello World" |
我的 test.py 的路径是 /root/nowamagic_venv/nowamagic_pj/test.py,执行以下命令:
1 |
[root@nowamagic ~] # cd nowamagic_venv |
2 |
[root@nowamagic nowamagic_venv] # source bin/activate |
3 |
(nowamagic_venv)[root@nowamagic nowamagic_venv] # uwsgi --http :8001 --wsgi-file /root/nowamagic_venv/nowamagic_pj/test.py |
访问网页 http://115.28.0.89:8001/,OK,显示 Hello World,说明 uwsgi 安装成功。
测试你的 Django 项目
前面我们用 django-admin.py startproject nowamagic_pj 创建了一个项目,现在我们用 Django 自带的 Web 服务器看看我们的项目有没出问题。还是进入我们虚拟环境:
1 |
[root@nowamagic ~] # cd nowamagic_venv |
2 |
[root@nowamagic nowamagic_venv] # source bin/activate |
3 |
(nowamagic_venv)[root@nowamagic nowamagic_venv] # python2.7 /root/nowamagic_venv/nowamagic_pj/manage.py runserver 0.0.0.0:8002 |
执行这个命令报错:No module named django.core.management,原因应该是装了多个版本的Python导致的。命令指定文件路径就行,丑是丑些了:
1 |
(nowamagic_venv)[root@nowamagic nowamagic_venv] # /usr/local/bin/python2.7 /root/nowamagic_venv/nowamagic_pj/manage.py runserver 0.0.0.0:8002 |
OK,启动 Django 自带的服务器了,我们再访问 http://115.28.0.89:8002/,成功显示:
说明 Djanggo 项目也没问题。
连接Django和uwsgi
最后一步了,我们要把uwsgi与Django连接起来。
编写django_wsgi.py文件,将其放在与文件manage.py同一个目录下。我的放在 /root/nowamagic_venv/nowamagic_pj/ 下:
01 |
#!/usr/bin/env python |
02 |
# coding: utf-8 |
03 |
04 |
import os |
05 |
import sys |
06 |
07 |
# 将系统的编码设置为UTF8 |
08 |
reload (sys) |
09 |
sys.setdefaultencoding( 'utf8' ) |
10 |
11 |
os.environ.setdefault( "DJANGO_SETTINGS_MODULE" , "nowamagic_pj.settings" ) |
12 |
13 |
from django.core.handlers.wsgi import WSGIHandler |
14 |
application = WSGIHandler() |
- 注意不要直接 copy,有个地方要改:注意到语句os.environ.setdefault。比如我的项目为nowamagic_pj,则语句应该是 os.environ.setdefault("DJANGO_SETTINGS_MODULE", "nowamagic_pj.settings")
OK,进入虚拟环境执行指令:
1 |
[root@nowamagic ~] # cd nowamagic_venv |
2 |
[root@nowamagic nowamagic_venv] # source bin/activate |
3 |
(nowamagic_venv)[root@nowamagic nowamagic_venv] # uwsgi --http :8000 --chdir /root/nowamagic_venv/nowamagic_pj/ --module django_wsgi |
成功显示 Django It Works 页面。
这样,你就可以在浏览器中访问你的Django程序了。所有的请求都是经过uwsgi传递给Django程序的。
这里我们介绍了如何把uwsgi与Django连接起来,在下一篇将继续介绍如何将uwsgi与Nginx连接。
上一篇介绍了 uWSGI 来部署 Django 程序,但在在生产环境中单单只有 uWSGI 是不够的,Nginx是必不可少的工具。
先安装 Nginx,可以参照前面的小节:使用RPM安装Nginx。
Nginx 配置
在 nginx.conf 上加入/修改,我的 server 配置如下(一切从简……):
01 |
server { |
02 |
listen 80 ; |
03 |
server_name 115.28 . 0.89 ; |
04 |
#server_name localhost; |
05 |
06 |
access_log / home / nowamagic / logs / access.log; |
07 |
error_log / home / nowamagic / logs / error.log; |
08 |
09 |
#root /root/nowamagic_venv/nowamagic_pj; |
10 |
location / { |
11 |
uwsgi_pass 127.0 . 0.1 : 8077 ; |
12 |
#include uwsgi_params; |
13 |
include / etc / nginx / uwsgi_params; |
14 |
#uwsgi_pass 127.0.0.1:8077; |
15 |
#uwsgi_param UWSGI_SCRIPT index; |
16 |
#uwsgi_param UWSGI_PYHOME $document_root; |
17 |
#uwsgi_param UWSGI_CHDIR $document_root; |
18 |
} |
19 |
access_log off; |
20 |
} |
注意保证配置里写的目录 /home/nowamagic/logs/ 和 /home/nowamagic/logs/ 存在,接下来就没啥问题了,Nginx 配置很简单。
uWSGI 配置
前面我们是直接使用命令行来启动 uWSGI,在实际部署环境中,我们常用的是配置文件的方式,而非命令行的方式。
我的 Django 程序目录:/root/nowamagic_venv/nowamagic_pj/
这里让 Nginx 采用 8077 端口与 uWSGI 通讯,请确保此端口没有被其它程序采用。
uWSGI 支持多种配置文件格式,比如 xml,ini,json 等等都可以。
1. xml 配置
请确定你在上一节中的django_wsgi.py文件已经存在了。新建一个XML文件:nowamagic_pj.xml,将它放在 /root/nowamagic_venv/nowamagic_pj 目录下
01 |
<uwsgi> |
02 |
<socket> 127.0 . 0.1 : 8077 < / socket> |
03 |
<listen> 80 < / listen> |
04 |
<master>true< / master> |
05 |
<pythonpath> / root / nowamagic_venv / nowamagic_pj< / pythonpath> |
06 |
<processes> 1 < / processes> |
07 |
<logdate>true< / logdate> |
08 |
<daemonize> / var / log / uwsgi.log< / daemonize> |
09 |
<plugins>python< / plugins> |
10 |
< / uwsgi> |
然后执行命令:
1 |
uwsgi -x /root/nowamagic_venv/nowamagic_pj/nowamagic_pj.xml |
2 |
or |
3 |
/usr/ local /bin/uwsgi -x /root/nowamagic_venv/nowamagic_pj/nowamagic_pj.xml |
加载指定的xml配置文件。当使用命令行参数时,可以使用简化命令“-x”。当然也可以不简写:
1 |
uwsgi --xml /etc/nowamagic.xml |
甚至如果在命令行的最后一个参数以“.xml”结尾,那么就隐含将加载该xml文件作为配置。
1 |
uwsgi /etc/nowamagic.xml |
有时候因各种环境问题,-x --xml 命令识别不了,可以使用下面的 ini 配置方式:
2. ini 配置
01 |
[uwsgi] |
02 |
vhost = false |
03 |
plugins = python |
04 |
socket = 127.0 . 0.1 : 8077 |
05 |
master = true |
06 |
enable - threads = true |
07 |
workers = 1 |
08 |
wsgi - file = / root / nowamagic_venv / nowamagic_pj / nowamagic_pj / wsgi.py |
09 |
virtualenv = / root / nowamagic_venv |
10 |
chdir = / root / nowamagic_venv / nowamagic_pj |
然后执行命令:
1 |
uwsgi --ini /root/nowamagic_venv/nowamagic_pj.ini& |
uwsgi 这样就启动起来了。如果无意外的话,就能在网上访问你的 Python 项目了。
小插曲
我在配置完 Nginx 和 uWSGI 之后,访问时显示 502 错误。查看 uWSGI 启动信息,发现这么一条:ImportError: No module named django.core.wsgi。
然后推断,我的 CentOS 上的 Python 版本是 2.4.3,然后进入 virtualenv,执行:
1 |
python |
2 |
<<< import django |
3 |
<<< from django.core.wsgi import get_wsgi_application |
4 |
<<< |
则没报错,因为我的虚拟环境里的 Python 版本是 2.7.5。推断成立,但是虚拟环境里的 Django 会默认调用外部环境的 Python。解决方法:在虚拟环境里 pip install django。
OK,问题解决,一切正常。
附
一些我在配置时用到的命令,省得你去搜索:
1. 关闭 uWSGI:
1 |
killall -9 uwsgi |
2 |
killall -s HUP /var/www/uwsgi |
3 |
killall -s HUP /usr/ local /bin/uwsgi |
2. 列出端口占用情况:
1 |
netstat -lpnt |
Gunicorn(gunicorn.org)是一个 Python WSGI UNIX 的 HTTP 服务器。这是一个预先叉工人模式,从 Ruby 的独角兽(Unicorn)项目移植。该 Gunicorn 服务器与各种 Web 框架兼容,只需非常简单的执行,轻量级的资源消耗,以及相当迅速。它的特点是与 Django 结合紧密,部署特别方便。 缺点也很多,不支持 HTTP 1.1,并发访问性能不高,与 uWSGI,Gevent 等有一定的性能差距。具体比较可以参看列举一些常见的Python HTTP服务器。
但其实 Gunicorn 从设计上就不是充当直接从外界接受请求的服务器,作者在 FAQ 里明确提到,Gunicorn 应该是在 Nginx 等服务器后面支持应用请求的。
1. Gunicorn设计
Gunicorn 是一个 master 进程,spawn 出数个工作进程的 web 服务器。master 进程控制工作进程的产生与消亡,工作进程只需要接受请求并且处理。这样分离的方式使得 reload 代码非常方便,也很容易增加或减少工作进程。 工作进程这块作者给了很大的扩展余地,它可以支持不同的IO方式,如 Gevent,Sync 同步进程,Asyc 异步进程,Eventlet 等等。master 跟 worker 进程完全分离,使得 Gunicorn 实质上就是一个控制进程的服务。
2. Gunicorn源码结构
从 Application.run() 开始,首先初始化配置,从文件读取,终端读取等等方式完成 configurate。然后启动 Arbiter,Arbiter 是实质上的 master 进程的核心,它首先从配置类中读取并设置,然后初始化信号处理函数,建立 socket。然后就是开始 spawn 工作进程,根据配置的工作进程数进行 spawn。然后就进入了轮询状态,收到信号,处理信号然后继续。这里唤醒进程的方式是建立一个 PIPE,通过信号处理函数往 pipe 里 write,然后 master 从 select.select() 中唤醒。
工作进程在 spawn 后,开始初始化,然后同样对信号进行处理,并且开始轮询,处理 HTTP 请求,调用 WSGI 的应用端,得到 resopnse 返回。然后继续。
Sync 同步进程的好处在于每个 request 都是分离的,每个 request 失败都不会影响其他 request,但这样导致了性能上的瓶颈。
WSGI详解的更多相关文章
- (转)python WSGI框架详解
原文:https://www.cnblogs.com/shijingjing07/p/6407723.html?utm_source=itdadao&utm_medium=referral h ...
- supervisor使用详解
1.什么是supervisorsupervisor是用python写的一个进程管理工具,用来启动,重启,关闭进程. 2.supervisor的安装 pip install supervisor 3.s ...
- Python输出hello world(各行命令详解)
创建main.py文件并粘贴下面代码 点击右键运行Debug 'main'后,下方的Debug窗口会出现ImportError: No module named 'bottle'这样的提示,提示导入b ...
- OpenStack Paste.ini详解(二)
接着OpenStack Paste.ini详解(一),接下来就分析request被paste.ini处理的流程 WSGI server接收到URL形式的request时,这些request首先会被Pa ...
- Python 19 Django 详解
本节概要 Django详解 前言 有一部分原因是,确实djando的课程有点多:并且,最近又在研究利用python做数据分析时间上耽误了.所以楼主讲所有的课程全部重新观看了一遍,再来撰写博客,其实说起 ...
- Nginx安装及配置详解【转】
nginx概述 nginx是一款自由的.开源的.高性能的HTTP服务器和反向代理服务器:同时也是一个IMAP.POP3.SMTP代理服务器:nginx可以作为一个HTTP服务器进行网站的发布处理,另外 ...
- [转帖]Nginx安装及配置详解 From https://www.cnblogs.com/zhouxinfei/p/7862285.html
Nginx安装及配置详解 nginx概述 nginx是一款自由的.开源的.高性能的HTTP服务器和反向代理服务器:同时也是一个IMAP.POP3.SMTP代理服务器:nginx可以作为一个HTTP ...
- openfalcon架构及相关服务配置详解
一:openfalcon组件 1.falcon-agent 数据采集组件 agent内置了一个http接口,会自动采集预先定义的各种采集项,每隔60秒,push到transfer. 2.transfe ...
- supervisor详解
1.什么是supervisor supervisor是用python写的一个进程管理工具,用来启动,重启,关闭进程. 2.supervisor的安装 pip install supervisor 3. ...
随机推荐
- QT学习 之 QwtPlot(数学绘图)
QT对于统计图像.函数图像等的绘制是没有相关组件的帮助的,只有利用手工绘制图片. QwtPlot是用来绘制二维图像的widget,继承自QFrame 和 QwtPlotDict.不过严格的说来,它只是 ...
- 基于visual Studio2013解决C语言竞赛题之前言
前言:关于VS2013 刚装完VS2013,新的IDE给人全新的编程体验,界面比以前更急简洁漂亮,不多说了,先上图吧 第一次启动VS2013
- C# RSA在服务上使用出现拒绝方法错误的解决方法
在做一个快钱接口的时候,遇到了.net RSA加密无法在一台win2008服务器上运行正常,更换到Win2003服务器后出现问题,具体表现如下: “/”应用程序中的服务器错误. ----------- ...
- Handler总结
一.整体工程图 二.activity_handler.xml <?xml version="1.0" encoding="utf-8"?> < ...
- iOS开发中地图开发的简单应用
iOS上使用地图比Android要方便,只需要新建一个MKMapView,addSubView即可.这次要实现的效果如下: 有标注(大头针),定位,地图. 1.添加地图 1.1 新一个Single V ...
- Android存储之SQLiteDatbase
SQLiteDatabase的方式会生成一个数据库文件,每个应用最多只对应一个数据库文件,即.db文件. 可以使用很多第三方工具进行打开,查看数据库里的内容. 昨晚试了好几种工具,如navicat,s ...
- .htaccess文件设置
1.为使.htaccess有效,需要先做httpd.conf设置 #打开改写模式Options FollowSymLinks AllowOverride All#打开重写模式,去掉这行注释LoadMo ...
- ognl--数据运转的催化剂
原文链接:http://struts2.group.iteye.com/group/wiki/1353-ognl-catalyst-for-data-operation-in-struts2 首先让我 ...
- 神奇的矩阵 NOI模拟题
神奇的矩阵 题目大意 有一个矩阵\(A\),第一行是给出的,接下来第\(x\)行,第\(y\)个元素的值为数字\(A_{x-1,y}\)在\(\{A_{x-1,1},A_{x-1,2},A_{x-1, ...
- 凡客副总裁崔晓琦离职 曾负责旗下V+商城项目_科技_腾讯网
凡客副总裁崔晓琦离职 曾负责旗下V+商城项目_科技_腾讯网 凡客副总裁崔晓琦离职 曾负责旗下V+商城项目 腾讯科技[微博]乐天2013年09月18日12:44 分享 微博 空间 微信 新浪微博 邮箱 ...