Web框架的本质
我们可以这样理解:所有的Web应用本质上就是一个socket服务端,而用户的浏览器就是一个socket客户端。 这样我们就可以自己实现Web框架了。
Python中使用socket和浏览器进行通信
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"OK") 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"OK") conn.close()
浏览器向socket服务器发送的请求输出:
b'GET / HTTP/1.1 Host: 127.0.0.1:8080 Connection: keep-alive Upgrade-Insecure-Requests: 1 User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/64.0.3282.186 Safari/537.36 Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8 DNT: 1 Accept-Encoding: gzip, deflate, br Accept-Language: zh-CN,zh;q=0.9 Cookie: csrftoken=RKBXh1d3M97iz03Rpbojx1bR6mhHudhyX5PszUxxG3bOEwh1lxFpGOgWN93ZH3zv '
然后我们再看一下我们访问博客园官网时浏览器收到的响应信息是什么。
响应相关信息可以在浏览器调试窗口的network标签页中看到。
点击view source之后显示如下图:
我们发现收发的消息需要按照一定的格式来,这里就需要了解一下HTTP协议了。
HTTP协议对收发消息的格式要求
每个HTTP请求和响应都遵循相同的格式,一个HTTP包含Header和Body两部分,其中Body是可选的。 HTTP响应的Header中有一个 Content-Type
表明响应的内容格式。如 text/html
表示HTML网页。
HTTP中请求格式:
HTTP中响应格式:
支持HTTP的socket简单web服务器:
import socket server = socket.socket() server.bind(("127.0.0.1",8080)) server.listen() while 1: conn,_ = server.accept() data = conn.recv(8096) # print(data) conn.send(b'HTTP/1.1 200 OK Content-Type:text/html;charset=utf-8 ') conn.send(b"Hello World") # conn.send(b'<h1>简单web服务器测试</h1>') 这行会报错,因为 中文超出了ascii范围 conn.send(b'<h1>web</h1>') conn.send(b'<h1>hello s10!</h1>') conn.close()
根据不同的URL返回不同的内容:
import socket ''' 根据不同的url ,返回不同的网页 http:127.0.0.1/index/ 请求服务器的时候 GET /index/ HTTP/1.1 --> 浏览器会请求行中会 /index/ 参数 ''' server = socket.socket() server.bind(("127.0.0.1",8080)) server.listen() while 1: conn,_ = server.accept() data = conn.recv(8096) print(data) request_list = str(data, encoding="utf-8").split() print(request_list[1]) conn.send(b'HTTP/1.1 200 OK Content-Type:text/html;charset=utf-8 ') if request_list[1] == "/index/": response = b"<h1>index</h1>" elif request_list[1] == '/home/': response = b'<h1>home</h1>' else: response = b'404 Not Found!' conn.send(response) conn.close()
再优化,利用函数:
import socket ''' 根据 客户端请求的url 与 函数进行映射 不同的url调用不同的业务处理函数 ''' def index(item): return "<h1>" + str(item).strip('/') + "</h1>" def home(item): return "<h1>" + str(item).strip('/') + "</h1>" def F404(item): return "<h1>" + str(item).strip('/') + ' Not Found!!!' + "</h1>" server = socket.socket() server.bind(("127.0.0.1",8080)) server.listen() FUNC = [ ('/index/',index), ('/home/',home), ] fun = None while 1: conn,_ = server.accept() data = conn.recv(8096) request_list = str(data, encoding="utf-8").split() conn.send(b'HTTP/1.1 200 OK Content-Type:text/html;charset=utf-8 ') for i in FUNC: if request_list[1] == i[0]: fun = i[1] break else: fun = F404 response = fun(request_list[1]) conn.send(bytes(response,encoding="utf-8")) conn.close()
再优化,返回一个HTML网页给客户端浏览器:
import socket ''' 根据 客户端请求的url 与 函数进行映射 不同的url调用不同的业务处理函数 然后将对应的html文件读取并加载之内存返回给客户端 ''' def index(item): with open("index.html","rb") as f: ret = f.read() return ret def home(item): with open("home.html", "rb") as f: ret = f.read() return ret def F404(item): return bytes("<h1>" + str(item).strip('/') + ' Not Found!!!' + "</h1>",encoding="utf-8") server = socket.socket() server.bind(("127.0.0.1",8080)) server.listen() FUNC = [ ('/index/',index), ('/home/',home), ] fun = None while 1: conn,_ = server.accept() data = conn.recv(8096) request_list = str(data, encoding="utf-8").split() conn.send(b'HTTP/1.1 200 OK Content-Type:text/html;charset=utf-8 ') for i in FUNC: if request_list[1] == i[0]: fun = i[1] break else: fun = F404 response = fun(request_list[1]) conn.send(response) conn.close()
返回一个动态网页:
import socket ''' 根据 客户端请求的url 与 函数进行映射 不同的url调用不同的业务处理函数 ''' import time def index(item): with open("dynamic.html",mode="r",encoding="utf-8") as f: ret = f.read() ret = ret.replace("{{time}}",str(time.time())) return bytes(ret,encoding="utf-8") def home(item): with open("home.html", "rb") as f: ret = f.read() return ret def F404(item): return bytes("<h1>" + str(item).strip('/') + ' Not Found!!!' + "</h1>",encoding="utf-8") server = socket.socket() server.bind(("127.0.0.1",8080)) server.listen() FUNC = [ ('/index/',index), ('/home/',home), ] fun = None while 1: conn,_ = server.accept() data = conn.recv(8096) request_list = str(data, encoding="utf-8").split() conn.send(b'HTTP/1.1 200 OK Content-Type:text/html;charset=utf-8 ') for i in FUNC: if request_list[1] == i[0]: fun = i[1] break else: fun = F404 response = fun(request_list[1]) conn.send(response) conn.close()
服务器程序和应用程序
对于真实开发中的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开发环境用的就是这个模块来做服务器。
WSGIREF案例:
""" 根据URL中不同的路径返回不同的内容--函数进阶版 返回HTML页面 让网页动态起来 wsgiref模块版 """ import time from wsgiref.simple_server import make_server # 将返回不同的内容部分封装成函数 def index(url): with open("index.html", "r", encoding="utf8") as f: s = f.read() now = str(time.time()) s = s.replace("@@oo@@", now) return bytes(s, encoding="utf8") def home(url): with open("home.html", "r", encoding="utf8") as f: s = f.read() return bytes(s, encoding="utf8") # 定义一个url和实际要执行的函数的对应关系 list1 = [ ("/index/", index), ("/home/", home), ] def run_server(environ, start_response): start_response('200 OK', [('Content-Type', 'text/html;charset=utf8'), ]) # 设置HTTP响应的状态码和头信息 url = environ['PATH_INFO'] # 取到用户输入的url func = None for i in list1: if i[0] == url: func = i[1] break if func: response = func(url) else: response = b"404 not found!" # return [response, ] return [b"<h1>Hello World</h1>",] # 返回一个列表 if __name__ == '__main__': httpd = make_server('127.0.0.1', 8090, run_server) print("我在8090等你哦...") httpd.serve_forever()
jinja2案例:
from wsgiref.simple_server import make_server from jinja2 import Template def index(): with open("index.html", "r",encoding="utf-8") as f: data = f.read() template = Template(data) # 生成模板文件 ret = template.render({"name": "Alex", "hobby_list": ["烫头", "泡吧"]}) # 把数据填充到模板里面 return [bytes(ret, encoding="utf8"), ] def home(): with open("home.html", "rb") as f: data = f.read() return [data, ] # 定义一个url和函数的对应关系 URL_LIST = [ ("/index/", index), ("/home/", home), ] def run_server(environ, start_response): start_response('200 OK', [('Content-Type', 'text/html;charset=utf8'), ]) # 设置HTTP响应的状态码和头信息 url = environ['PATH_INFO'] # 取到用户输入的url func = None # 将要执行的函数 for i in URL_LIST: if i[0] == url: func = i[1] # 去之前定义好的url列表里找url应该执行的函数 break if func: # 如果能找到要执行的函数 return func() # 返回函数的执行结果 else: return [bytes("404没有该页面", encoding="utf8"), ] if __name__ == '__main__': httpd = make_server('', 8000, run_server) print("Serving HTTP on port 8000...") httpd.serve_forever()