了解CGI、WSGI
上一篇已了解WSGI
(1)CGI
CGI(Common Gateway Interface)通用网关接口,即接口协议,前端向服务器发送一个URL(携带请求类型、参数、cookie等信息)请求,服务器把这个请求的各种参数写进进程的环境变量,比如
REQUEST_METHOD,PATH_INFO之类的,然后开启 cgi模块以后,将其发送给CGI程序,CGI程序(可以由各种语言编写,比如C、C ++、VB 和Delphi 等)从环境变量中解析出各种参数,然后向标准输出输出内容(比如cout了一段HTML代码),这些内容没有被打印到控制台上,而是最终响应给了你的浏览器,渲染出了网页。每一次向CGI发送请求,都会生成一个CGI进程,这就是所谓的fork-and-exec模式,这也通常是导致并发瓶颈的症结,反向代理加上大型的的分布式系统可以一定程度上减轻这些压力。
2)WSGI
WSGI(Python Web Server Gateway Interface,缩写为WSGI)web服务器网关接口,是一种规范,也是接口协议,它定义了使用python编写的web app(应用程序)与web server(socket服务端)之间接口格式,实现web app与web server间的解耦。前端向服务器发送一个URL(携带请求类型、参数、cookie等信息)请求,服务器把这个请求的各种参数传给WSGI模块,wsgi将各种参数进行python化,封装为request对象传递给按照WSGI接口标准调用注册的WSGI Application,并返回response参数给客户端。
web应用(使用socket)–get请求
import socket
def server():
server_sock = socket.socket(socket.AF_INET,socket.SOCK_STREAM)
server_sock.bind(("127.0.0.1", 8800))
server_sock.listen(5)
while True:
# 一个连接关闭了,继续接受下个连接
conn, client_addr = server_sock.accept()
data = conn.recv(1024)
print("recv_data-----", data)
# 发送数据到浏览器端
# \r\n\r\n表示响应体中的空行,表示响应头内容结束。。这里是根据HTTP协议的要求,回复响应数据
conn.send("HTTP/1.1 200 OK\r\nstatus: 200\r\nContent-Type:text/html\r\n\r\n".encode("utf8"))
conn.send("<h1>Hello, vita!</h1>".encode("utf8"))
# 发送完数据,就关闭连接,因为HTTP协议是无连接的。
conn.close()
if __name__ == '__main__':
server()
结果:
recv_data----- b'GET / HTTP/1.1\r\nHost: 127.0.0.1:8800\r\nUpgrade-Insecure-Requests: 1\r\nAccept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8\r\nUser-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/14.1.2 Safari/605.1.15\r\nAccept-Language: zh-cn\r\nAccept-Encoding: gzip, deflate\r\nConnection: keep-alive\r\n\r\n'
recv_data----- b'GET /favicon.ico HTTP/1.1\r\nHost: 127.0.0.1:8800\r\nConnection: keep-alive\r\nAccept: */*\r\nUser-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/14.1.2 Safari/605.1.15\r\nAccept-Language: zh-cn\r\nReferer: http://127.0.0.1:8800/\r\nAccept-Encoding: gzip, deflate\r\n\r\n'
wsgiref库简介
学习django框架之前,可以先学习一下wsgiref模块,熟悉前后端交互。
wsgiref是python内置库,实现了一个简单的WSGI Server和WSGI Application,
使用该库我们将很容易实现自定义的web架构而不用考虑TCP/HTTP层协议,库源码位于/django/lib/wsgiref文件夹,该库提供了5个模块:
* util -- Miscellaneous useful functions and wrappers
* headers -- Manage response headers
* handlers -- base classes for server/gateway implementations
* simple_server -- a simple BaseHTTPServer that supports WSGI
* validate -- validation wrapper that sits between an app and a server
to detect errors in either
先介绍下wsgiref模块
下面主要对simple_server模块的重点函数和使用方法进行说明
1.wsgiref.simple_server类使用及部分源码分析
(1)先上一段代码
该代码来自simple_server.py的最后7行
# 这段代码表达两个意思:启动服务—->处理‘http://localhost:8000/xyz?abc‘请求
if __name__ == '__main__':
with make_server('', 8000, demo_app) as httpd:
sa = httpd.socket.getsockname()
print("Serving HTTP on", sa[0], "port", sa[1], "...")
import webbrowser
webbrowser.open('http://localhost:8000/xyz?abc')
httpd.handle_request() # serve one request, then exit
(2)分析make_server启动服务过程
server_class传入两个参数,第一个元组(host,ip),第二个WSGIRequestHandler类,第一个参数用于socketserver.TCPServer启动服务(图中标记1),第二个参数WSGIRequestHandler用于BaseServer类初始化self.RequestHandlerClass属性(图中标记2),用于finish_request()函数进行对象初始化(图中的标记3),主要目的实现后面回调函数调用
(3)分析handle_request()函数处理过程
该函数的实现过程在基类BaseServer中,该函数主要实现以下功能(这里不讨论epoll异步并发,相关epoll的内容可以看我之前的博客):
get_request()—->verify_request()—->process_request()—->shutdown_request()
单看函数名就应该明白整个流程了
(4)现在到重点了,看demo_app回调是如何实现的
接下来主要分析下make_server()函数的第三个参数demo_app是如何实现调用的
通过源码我们看到,finish_request()函数中实现了RequestHandlerClass初始化,也就是WSGIRequestHandler初始化
def finish_request(self, request, client_address):
"""Finish one request by instantiating RequestHandlerClass."""
self.RequestHandlerClass(request, client_address, self)
来张图一看什么都清楚了
a. 服务启动后,WSGIServer通过set_app将回调函数保存起来
b. finish_request()对WSGIRequestHandler进行实例化,调用其基类BaseRequestHandle的构造函数,构造函数中又调用了handle处理函数,由于派生类WSGIRequestHandler重写了handle方法,实则调用的是WSGIRequestHandler类的handle函数,如上图标记3,得到回调函数对象,并执行
def run(self, application):
try:
self.setup_environ()
self.result = application(self.environ, self.start_response)
self.finish_response()
。。。。。。。。。。。。。。
接下来我们引用wsgiref来编写一个web服务器
#encoding:utf-8
from wsgiref.simple_server import make_server
def run_server(environ, start_response):
"""
当有用户在浏览器上访问:http://127.0.0.1:8001/, 立即执行该函数并将函数的返回值返回给用户浏览器
:param environ: 请求相关内容,比如浏览器类型、版本、来源地址、url等
:param start_response: 响应相关
:return:
"""
start_response('200 OK', [('Content-Type', 'text/html;charset=utf-8')])
return [bytes('hhhh', encoding="utf-8"), ]
if __name__ == '__main__':
httpd = make_server('localhost', 8001, run_server) # socket连接
print('----已连接至8001----')
httpd.serve_forever() # 持续监听
前端访问效果为:
在environ中有一个字段:“PATH_INFO”,这个键对应的值就是输入的路径
,可以打印一下environ
def run_server(environ, start_response):
print(environ)
print(start_response)
start_response('200 OK', [('Content-Type', 'text/html;charset=utf-8')])
return [bytes('hhhh', encoding="utf-8"), ]
if __name__ == '__main__':
httpd = make_server('localhost', 8001, run_server) # socket连接
print('----已连接至8001----')
httpd.serve_forever() # 持续监听
结果(只截取一部分):
接下来有个需求如果我想要通过输入不同的路径去访问不同的页面怎么实现呢?
#encoding:utf-8
from wsgiref.simple_server import make_server
def run_server(environ, start_response):
"""
当有用户在浏览器上访问:http://127.0.0.1:8001/, 立即执行该函数并将函数的返回值返回给用户浏览器
:param environ: 请求相关内容,比如浏览器类型、版本、来源地址、url等
:param start_response: 响应相关
:return:
"""
start_response('200 OK', [('Content-Type', 'text/html;charset=utf-8')])
if environ['PATH_INFO'] == '/login':
with open('login.html', 'rb') as f:
data = f.read()
return [data]
if environ['PATH_INFO'] == '/favicon.ico':
with open('favicon.ico', 'rb') as f :
data = f.read()
return [data]
elif environ['PATH_INFO'] == '/index':
with open('index.html', 'rb') as f:
data = f.read()
return [data]
if __name__ == '__main__':
httpd = make_server('localhost', 8001, run_server) # socket连接
print('----已连接至8001----')
httpd.serve_forever() # 持续监听
功能是实现了,但是如果需要跳转的页面很多,全放在run_server中会不会显得很累赘?这样我们把跳转各个页面的逻辑封装成各个方法。
#_author:leo gao
#encoding:utf-8
from wsgiref.simple_server import make_server
def login():
with open('login.html', 'r') as f:
data = f.read()
return data
def index():
with open('index.html', 'r') as f:
data = f.read()
return data
def fav():
with open('favicon.ico', 'r') as f:
data = f.read()
return data
def run_server(environ, start_response):
"""
当有用户在浏览器上访问:http://127.0.0.1:8000/, 立即执行该函数并将函数的返回值返回给用户浏览器
:param environ: 请求相关内容,比如浏览器类型、版本、来源地址、url等
:param start_response: 响应相关
:return:
"""
url_pattern = [
("/login", login),
("/index", index),
("fav", fav)
]
# 方案一
for i in url_pattern:
print(environ.get('PATH_INFO'))
if i[0] == environ.get('PATH_INFO'):
return [bytes(i[1](), encoding='utf-8'), ]
else:
return [b'404']
# 方案二
func = None
for item in url_pattern:
if item[0] == environ.get('PATH_INFO'):
func = item[1]
break
if func:
return [bytes(func(), encoding="utf-8"), ]
else:
return [bytes('404 not found.', encoding="utf-8"), ]
if __name__ == '__main__':
httpd = make_server('localhost', 8001, run_server) # socket连接
print('----已连接至8001----')
httpd.serve_forever() # 持续监听