Web服务器网关接口(WSGI),又叫Python Web服务器网关接口(Python Web Server Gateway Interface),是用来描述web服务器与网站应用程序通信和应用程序如何处理请求的规范。它的第一个版本于2003年发布,从那时起,wsgi就成为了python web应用的开发标准,目前最新的版本是v1.0.1,于2010年9月26日发布。
使用python做过web开发的对下面的例子应该非常熟悉,运行之后访问则会显示出Hello,web!
from wsgiref.simple_server import make_server def application(environ, start_response): start_response('200 OK', [('Content-Type', 'text/html')]) return '<h1>Hello, web!</h1>' httpd = make_server('', 80, application) print "Serving HTTP on port 80..." httpd.serve_forever()
在上面的例子中,几行代码就可以搭建出一个HTTP服务器,并没有使用像apache,nginx一样的专用web服务器,而是使用wsgiref中的模块。对于这个服务来说,application是用户层的代码,而wsgi对内提供应用接口,对外充当web服务器。
也就是说,wsgi只是提供了一个管道,最终用来处理终端用户请求的还是用户层的代码,这里就有两个问题:
1,如何把用户请求交给application程序来处理?
2,如何把用户的请求数据等信息一并交给application?
wsgi的处理方式很简单巧妙:为每个请求注册一个回调函数,这个回调函数接受两个参数,第一个参数为用户和环境数据,第二个为HTTP头部返回函数。由于这个回调函数是用户层程序,就很容易通过参数的形式获取前端的请求数据等信息:
def application(environ, start_response): start_response('200 OK', [('Content-Type', 'text/html')]) return "Your IP Addr is %s, Your Request Path is %s"%(environ['REMOTE_ADDR'],environ['PATH_INFO'][1:] or '/') from wsgiref.simple_server import make_server httpd = make_server('', 80, application) print "Serving HTTP on port 80..." httpd.serve_forever()
这样一来,网站应用程序就可以通过wsgi传入的environ参数获取用户输入,从而给不同的用户返回不同的内容。
实现一个简单的wsgi服务器
只有能动手实现一个wsgi服务器,才能对它有深入的认识,废话不多说,直接上代码:
#!/usr/bin/env python # -*- coding:utf-8 -*- # 对于wsgi的理解并对其进行实验 # https://www.python.org/dev/peps/pep-3333/ import socket,re,os httpre=re.compile(r"^(\w*)\s*([^\s]*)\s*HTTP\/([.\d]*)[\r\n]*([\s\S]+)[\r\n]*[\r\n]*([\s\S]?)$") headerre=re.compile(r"([^\s]*):\s*([^\r\n]*)[\r\n]") def recvall(link,buflen): buf = link.recv(buflen); if len(buf) < buflen: return buf else: return "%s%s"%(buf,recvall(link,buflen)) def headerdic(str): mydic = {} matches = headerre.findall(str) for line in matches: mydic[line[0]]=line[1] return mydic #http request return #method, uri, httpversion, header, body def httppareser(request): try: raw_re = httpre.findall(request.strip())[0] return raw_re[0], raw_re[1], raw_re[2], raw_re[3], raw_re[4] except: return None class WSGIServer: def __init__(self,host="127.0.0.1",port=8080,keepalive=5): self.host = host self.port = port self.keepalive = keepalive self.environ = os.environ def run(self,app): sockt = socket.socket(socket.AF_INET,socket.SOCK_STREAM) sockt.setsockopt(socket.SOL_SOCKET,socket.SO_REUSEADDR,1) sockt.bind((self.host,self.port)) sockt.listen(5) while True: con,addr = sockt.accept() #con.settimeout(self.keepalive) raw_request = recvall(con,1024) method,uri,httpversion,header,body = httppareser(raw_request) environ = header#,**os.environ) global response_header #start_response #gen http response header def start_response(status,response_header_list): global response_header headers = "" for header in response_header_list: headers = headers+"%s:%s\r\n"%(header[0],header[1]) response_header = "HTTP/1.1 %s\r\n%s\r\n"%(status,headers) response_body = app(environ,start_response) con.send(response_header+response_body) con.close() #This is the application def app(environ,start_response): start_response("404 Not Found",[("Content-Type","text/html"),("User-Agent","Mybody")]) return """<!DOCTYPE HTML> <html> <head lang="zh-cn"> <meta charset="utf-8"> <title>你好世界</title> <body> <h1>Hello World</h1> </body> </html>""" srv = WSGIServer() srv.run(app)
当然,python对静态文件的处理效率还是比较低的,所以通常生产环境的做法是在wsgi前添加一个反向代理服务器,静态文件由服务器直接获取,动态请求则交给python来处理,如下:
我们注意到,在应用程序被调用的时候,wsgi给我们传递了两个参数,一个是组合的字典类型数据environ,另一个是可调用的函数,应用程序通过调用传入的函数来生成HTTP返回的头部信息,通过返回值生成HTTP的body信息。从网络传输层的角度来看,HTTP头部数据和Body数据是一样的数据,只是用两个”\r\n”分隔而已,何不直接返回头部和body信息,徒增一个函数不仅没有带来什么帮助,反而给开发带来更多麻烦。对此python开发了第二代web网关标准,应用程序只传入一个environ参数即可,相关介绍,可以访问PEP0444查看。
评论列表: