web框架基础
一、web框架本质
我们可以这样理解:所有的Web应用本质上就是一个socket服务端,而用户的浏览器就是一个socket客户端。 这样我们就可以自己实现Web框架了。
import socket sk = socket.socket() sk.bind(("127.0.0.1", 80)) sk.listen() while True: conn, addr = sk.accept() data = conn.recv(8096) conn.send(b'hello world') conn.close()
可以说Web服务本质上都是在这十几行代码基础上扩展出来的,用户在浏览器一输入网址,给服务端发送数据,那浏览器会发送什么数据?怎么发?这个谁来定? 所以,必须有一个统一的规则,让大家发送消息、接收消息的时候有个格式依据,不能随便写。
这个规则就是HTTP协议,以后浏览器发送请求信息也好,服务器回复响应信息也罢,都要按照这个规则来。
HTTP协议主要规定了客户端和服务器之间的通信格式,那HTTP协议是怎么规定消息格式的呢?
让我们首先打印下我们在服务端接收到的消息是什么
import socket sk = socket.socket() sk.bind(("127.0.0.1", 80)) sk.listen() while True: conn, addr = sk.accept() data = conn.recv(8096) print(data) conn.send(b'hello world') conn.close()
输出结果:
b'GET / HTTP/1.1\r\n Host: 127.0.0.1\r\n Connection: keep-alive\r\nCache-Control: max-age=0\r\n Upgrade-Insecure-Requests: 1\r\n User-Agent: Mozilla/5.0 (Windows NT 6.1; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/69.0.3497.100 Safari/537.36\r\n Accept:text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8\r\nAccept-Encoding: gzip, deflate, br\r\n Accept-Language: zh-CN,zh;q=0.9\r\n Cookie: bdshare_firstime=1529473540203; csrftoken=WqST6GYvUUK9qKpkZlMnsbQzue1kOm5LknTvd9GQlr1MOj8yZ9YouYFjKLwgE9lb\r\n\r\n'
我们发现收发的消息是要按照一定的格式来,这里就需要了解一下HTTP协议了
HTTP协议(HyperText Transfer Protocol,超文本传输协议)是因特网上应用最为广泛的一种网络传输协议,所有的WWW文件都必须遵守这个标准。
HTTP是一个基于TCP/IP通信协议来传递数据(HTML 文件, 图片文件, 查询结果等)
HTTP协议定义Web客户端如何从Web服务器请求Web页面,以及服务器如何把Web页面传送给客户端。HTTP协议采用了请求/响应模型。客户端向服务器发送一个请求报文,请求报文包含请求的方法、URL、协议版本、请求头部和请求数据。服务器以一个状态行作为响应,响应的内容包括协议的版本、成功或者错误代码、服务器信息、响应头部和响应数据。然后客户端浏览器解析HTML内容
HTTP请求方法:
GET(向指定的资源发出“显示”请求。使用GET方法应该只用在读取数据,而不应当被用于产生“副作用”的操作中,例如在Web Application中。其中一个原因是GET可能会被网络蜘蛛等随意访问)
HEAD(与GET方法一样,都是向服务器发出指定资源的请求。只不过服务器将不传回资源的本文部分。它的好处在于,使用这个方法可以在不必传输全部内容的情况下,就可以获取其中“关于该资源的信息”(元信息或称元数据))
POST(向指定资源提交数据,请求服务器进行处理(例如提交表单或者上传文件)。数据被包含在请求本文中。这个请求可能会创建新的资源或修改现有资源,或二者皆有)
PUT(向指定资源位置上传其最新内容)
DELETE(请求服务器删除Request-URI所标识的资源)
TRACE(回显服务器收到的请求,主要用于测试或诊断)
OPTIONS(这个方法可使服务器传回该资源所支持的所有HTTP请求方法。用'*'来代替资源名称,向Web服务器发送OPTIONS请求,可以测试服务器功能是否正常运作)
CONNECT(HTTP/1.1协议中预留给能够将连接改为管道方式的代理服务器。通常用于SSL加密服务器的链接(经由非加密的HTTP代理服务器))
HTTP状态码:
- 1xx消息——请求已被服务器接收,继续处理
- 2xx成功——请求已成功被服务器接收、理解、并接受
- 3xx重定向——需要后续操作才能完成这一请求
- 4xx请求错误——请求含有词法错误或者无法被执行
- 5xx服务器错误——服务器在处理某个正确请求时发生错误
HTTP协议对收发消息的格式要求:
HTTP 请求格式:
HTTP响应格式:
二、完善自定义的web框架
import socket #index页面 def f1(request): f = open('index.txt','rb') data = f.read() f.close() return data #home页面 def f2(request): import pymysql # 使用pymsql连接数据库 conn = pymysql.connect(host='127.0.0.1', port=3306, user='root', passwd='', db='userbase') cursor = conn.cursor(cursor=pymysql.cursors.DictCursor) cursor.execute("select id,username,password from userinfo") user_list = cursor.fetchall() #拿出数据 cursor.close() conn.close() content_list = [] for row in user_list: tp = "<tr><td>%s</td><td>%s</td><td>%s</td></tr>" %(row['id'],row['username'],row['password']) content_list.append(tp) content = "".join(content_list)
f = open('home.html','r',encoding='utf-8') template = f.read() f.close() # 模板渲染 data = template.replace('@@userlist@@',content) #替换掉原有文件里面的特殊符号内容 return bytes(data,encoding='utf-8') routers = [ ('/index', f1), ('/home.html', f2), ] def runserver(): sock = socket.socket() sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) sock.bind(('127.0.0.1',80)) sock.listen(5) while True: conn,addr = sock.accept() data = conn.recv(8096) data = str(data,encoding='utf-8') headers,bodys = data.split('\r\n\r\n') temp_list = headers.split('\r\n') method,url,protocal = temp_list[0].split(' ') func_name = None for item in routers: if item[0] == url: func_name = item[1] break if func_name: response = func_name(data) else: response = b"404" conn.send(response) conn.close() if __name__ == '__main__': runserver()
三、wsgiref
对于真实开发中的python web程序来说,一般会分为两部分:服务器程序和应用程序。服务器程序负责对socket服务器进行封装,并在请求到来时,对请求的各种数据进行整理。应用程序则负责具体的逻辑处理。为了方便应用程序的开发,就出现了众多的Web框架,例如:Django、Flask、web.py 等。不同的框架有不同的开发方式,但是无论如何,开发出的应用程序都要和服务器程序配合,才能为用户提供服务。这样,服务器程序就需要为不同的框架提供不同的支持。这样混乱的局面无论对于服务器还是框架,都是不好的。对服务器来说,需要支持各种不同框架,对框架来说,只有支持它的服务器才能被开发出的应用使用。这时候,标准化就变得尤为重要。我们可以设立一个标准,只要服务器程序支持这个标准,框架也支持这个标准,那么他们就可以配合使用。一旦标准确定,双方各自实现。这样,服务器可以支持更多支持标准的框架,框架也可以使用更多支持标准的服务器。
WSGI(Web Server Gateway Interface)就是一种规范,它定义了使用Python编写的web应用程序与web服务器程序之间的接口格式,实现web应用程序与web服务器程序间的解耦。常用的WSGI服务器有uwsgi、Gunicorn。而Python标准库提供的独立WSGI服务器叫wsgiref,Django开发环境用的就是这个模块来做服务器
from wsgiref.simple_server import make_server def run_server(environ, start_response): start_response('200 OK', [('Content-Type', 'text/html;charset=utf8'), ]) # 设置HTTP响应的状态码和头信息 url = environ['PATH_INFO'] # 取到用户输入的url,可进行判断取舍 if __name__ == '__main__': httpd = make_server('127.0.0.1', 8090, run_server) print("开始服务...") httpd.serve_forever()
四、jinja2
上面的自定义web框架中,我们使用字符串拼接后用特殊符号替换来完成html的渲染,其实模板渲染有个现成的工具: jinja2
下载安装:pip install jinja2
from jinja2 import Template def index(): userlist=[{"id":1,"username":'jump',"age":12}] with open("index.html", "r") as f: data = f.read() template = Template(data) # 生成模板文件 ret = template.render(index=userlist) # 把数据填充到模板里面 return [bytes(ret, encoding="utf8"), ] #html页面部分 {% for row in index %} <tr> <td>{{row.id}}</td> <td>{{row.username}}</td> <td>{{row.age}}</td> </tr> {% endfor %}
补充:
自定义web框架:
- socket服务端
- 根据URL不同返回不同的内容(路由系统:URL -> 函数)
- 结果返回给用户(模板引擎渲染:自己创造任意数据替换,jinja2)
Web框架:
根据上述3点web框架分类:
- 1,2,3 --> Tornado
- [第三方1],2,3 --> wsgiref -> Django
- [第三方1],2,[第三方3] --> flask
#!/usr/bin/env python # -*- coding:utf-8 -*- from wsgiref.simple_server import make_server def index(): # return 'index' f = open('index.html') data = f.read() return data def login(): # return 'login' f = open('login.html') data = f.read() return data def routers(): urlpatterns = ( ('/index/', index), ('/login/', login), ) return urlpatterns def run_server(environ, start_response): start_response('200 OK', [('Content-Type', 'text/html')]) url = environ['PATH_INFO'] urlpatterns = routers() func = None for item in urlpatterns: if item[0] == url: func = item[1] break if func: return func() else: return '404 not found' if __name__ == '__main__': httpd = make_server('', 8000, run_server) print "Serving HTTP on port 8000..." httpd.serve_forever()