一 web框架的本质
对于所有的Web应用,本质上就是一个socket服务端,用户的浏览器是一个socket客户端。通过socket实现其代码如下:
import socket def handle_request(client): '''处理请求及返回数据''' buf = client.recv(1024) #接收请求数据 client.send(b'HTTP/1.1 200 OK\r\n\r\n') client.send(b'Holle, Welcome to my first web.') def main(): sock = socket.socket() #创建socket对象 sock.bind(('127.0.0.1',8080)) #监听端口 sock.listen(5) #设置最大监听数 while True: conn,add = sock.accept() #等待用户连接,默认accept阻塞,当有请求的时候继续往下执行 handle_request(conn) #把连接交给handle_request函数处理 conn.close() #关闭连接 if __name__ == '__main__': main()
再往深层次讲,当我们需要完成真正的web应用时,往往需要结合HTML文档以展示真正的页面功能。Web应用的本质就是:
- 客户端发送一个HTTP请求
- 服务端收到请求,生成一个HTML文档
- 服务端把HTML文档作为HTTP响应的body返回给客户端
- 客户端收到HTTP响应,从body中取出HTML文档并显示
所以,最简单的Web应用就是我们事先将HTML文档创建并保存,然后用一个HTTP服务器软件,接收用户请求,读取相应HTML文档,返回。如:常见Apache、Nginx、Lighttpd等静态服务器就完成这些功能。
如果要动态生成HTML,就需要把上述步骤自己来实现,但是,这样我们需要花大量的时间了解更底层的HTTP规范。正确的做法是底层代码由专门的服务器软件实现,我们用Python专注于生成HTML文档。所以,需要一个统一的接口,让我们专心用Python编写Web业务。这个接口就是WSGI。
二 WSGI
WSGI,全称 Web Server Gateway Interface,或者 Python Web Server Gateway Interface ,是为 Python 语言定义的 Web 服务器和 Web 应用程序或框架之间的一种简单而通用的接口(补充说明:对于真实开发中的python web程序来说,一般会分为两部分:服务器程序和应用程序。服务器程序负责对socket服务器进行封装,并在请求到来时,对请求的各种数据进行整理。应用程序则负责具体的逻辑处理。为了方便应用程序的开发,就出现了众多的Web框架,例如:Django、Flask、web.py 等),实现服务器程序和应用程序间的解耦。它的定义很简单,只要求web开发者实现一个函数,就可以响应HTTP请求,结合之间socket的例子,如下:
def application(environ, start_response): start_response('200 OK', [('Content-Type', 'text/html')]) return [b'<h3>Holle, Welcome to my first web.<h3>']
上面的application()函数就是符合WSGI标准的一个HTTP处理函数,它接收两个参数:
- environ:一个包含所有HTTP请求的字典对象
- start_response:一个发送HTTP响应的函数
application()函数的使用:
在application()函数中,调用:
start_response('200 OK', [('Content-Type', 'text/html')])
即发送了HTTP响应的Header,注意Header只能发送一次,也就是只能调用一次start_response()函数。start_response()函数接收两个参数:
- HTTP响应码:’200 OK’
- 列表表示的HTTP Header,每个Header由一个包含两个字符串的元组组成
然后将函数的返回值b'<h3>b'Holle, Welcome to my first web.<h3>'作为HTTP响应的body发送给客户端浏览器。
有了WSGI就够了?NO,application()函数我们要怎么调用?怎样提供environ和start_response这两个参数?这里,我们就需要引入WSGI服务器来调用application()函数,常用的WSGI服务器如下:
server_names = { 'cgi': CGIServer, 'flup': FlupFCGIServer, 'wsgiref': WSGIRefServer, 'waitress': WaitressServer, 'cherrypy': CherryPyServer, 'paste': PasteServer, 'fapws3': FapwsServer, 'tornado': TornadoServer, 'gae': AppEngineServer, 'twisted': TwistedServer, 'diesel': DieselServer, 'meinheld': MeinheldServer, 'gunicorn': GunicornServer, 'eventlet': EventletServer, 'gevent': GeventServer, 'geventSocketIO':GeventSocketIOServer, 'rocket': RocketServer, 'bjoern' : BjoernServer, 'auto': AutoServer, }
基于WSGI实现简单web应用
Python标准库中提供了一个WSGI服务器——wsgiref,我们以此来完成web服务:
from wsgiref.simple_server import make_server def application(environ, start_response): start_response('200 OK', [('Content-Type', 'text/html')]) return [b'<h3>Holle, Welcome to my first web.<h3>'] if __name__ == '__main__': # 创建一个服务器,IP地址为空,端口是8080,处理函数application: httpd = make_server('', 8080, application) print("Serving HTTP on port 8080...") # 监听HTTP请求 httpd.serve_forever()
无论多么复杂的Web应用程序,入口都是一个WSGI处理函数。HTTP请求的所有输入信息都可以通过environ获得,HTTP响应的输出都可以通过start_response()加上函数返回值作为Body。
复杂的Web应用程序,光靠一个WSGI函数来处理还是太底层了,我们需要在WSGI之上再抽象出Web框架,进一步简化Web开发。
三 自定义web框架
3.1 简易框架
通过python标准库提供的wsgiref模块开发一个自己的Web框架,前面的例子只能访问一个url,下面我们根据不同url,返回不同页面。
from wsgiref.simple_server import make_server def func1(): return [b'url1 page'] #如有中文字符需要解码,如return ['url1 页面'.encode('gbk')] def func2(): return [b'url2 page'] #定义一个列表将url和上面的函数做一个对应 url_list = [ ('/url1/', func1), ('/url2/', func2), ] def application(environ, start_response): #environ包含请求相关信息 start_response('200 OK', [('Content-Type', 'text/html')]) url = environ['PATH_INFO'] #获取需要访问的url func = None #循环列表,查找列表中是否存在访问的url,存在则执行函数并返回,不存在则返回404 for item in url_list: if item[0] == url: func = item[1] break if func: return func() else: return [b'404 not found',] if __name__ == '__main__': httpd = make_server('', 8080, application) print("Serving HTTP on port 8080...") httpd.serve_forever()
3.2 静态页面(模板引擎)
在上一步骤中,对于func1,func2均返回给用户浏览器一个简单的字符串,在现实的Web请求中一般会返回一个复杂的符合HTML规则的字符串,所以我们一般将要返回给用户的HTML写在指定文件中,然后再返回。如:
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>Title</title> </head> <body> <h3>url1 页面</h3> </body> </html>
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>Title</title> </head> <body> <h3>url2 页面</h3> </body> </html>
from wsgiref.simple_server import make_server def func1(): f = open('url1.html', 'rb') data = f.read() f.close() return [data] def func2(): f = open('url2.html', 'rb') data = f.read() f.close() return [data] url_list = [ ('/url1/', func1), ('/url2/', func2), ] def application(environ, start_response): start_response('200 OK', [('Content-Type', 'text/html')]) url = environ['PATH_INFO'] func = None for item in url_list: if item[0] == url: func = item[1] break if func: return func() else: return [b'404 not found',] if __name__ == '__main__': httpd = make_server('', 8080, application) print("Serving HTTP on port 8080...") httpd.serve_forever()
3.3 动态页面
上一步我们已经实现了静态页面的返回,那如何返回动态页面呢?常用以下两种方式:
- 自定义一套特殊的语法,进行替换
- 使用开源工具jinja2,遵循其指定语法
安装jinja2
pip install jinja2
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>Title</title> </head> <body> <h3>url1 页面</h3> <h4>{{ name }}</h4> <ul> {% for item in test_list %} <li>{{ item }}</li> {% endfor %} </ul> </body> </html>
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>Title</title> </head> <body> <h3>url2 页面</h3> <h4>当前时间为:@time@</h4> </body> </html>
from wsgiref.simple_server import make_server from jinja2 import Template def func1(): '''利用jinja2实现动态''' f = open('url1.html', 'r', encoding='utf8') data = f.read() f.close() template = Template(data) result = template.render(name='joe1991',test_list=['test1','test2','test3']) #当然我们可以从数据库中获取数据,以实现真正意义上的动态 return [result.encode('utf8')] def func2(): '''利用字符串替换实现动态''' f = open('url2.html', 'r', encoding='utf8') data = f.read() f.close() import time ctime = time.strftime("%Y-%m-%d %X", time.localtime()) print(ctime,type(ctime)) result = data.replace('@time@',ctime) #replace有返回值,必须要接收 return [result.encode('utf8')] url_list = [ ('/url1/', func1), ('/url2/', func2), ] def application(environ, start_response): start_response('200 OK', [('Content-Type', 'text/html')]) url = environ['PATH_INFO'] func = None for item in url_list: if item[0] == url: func = item[1] break if func: return func() else: return [b'404 not found',] if __name__ == '__main__': httpd = make_server('', 8080, application) print("Serving HTTP on port 8080...") httpd.serve_forever()
访问url1结果:
访问url2结果:
当然,以上自定义web框架我们也可以通过socket做。