手撕框架
在学Django之前,我们先手撸一个简单的socket框架出来
通过对socket的学习,我们知道网络通信,我们完全可以自己写了,因为socket就是做网络通信用的,
下面我们就基于socket来自己实现一个web框架,写一个web服务端,让浏览器来请求,并通过自己的服务端把页面返回给浏览器,浏览器渲染出我们想要的效果。
web框架的本质及自定义web框架
我们可以这样理解:所有的Web应用本质上就是一个socket服务端,而用户的浏览器就是一个socket客户端,
基于请求做出响应,客户都先请求,服务端做出对应的响应,按照http协议的请求协议发送请求,
服务端按照http协议的响应协议来响应请求,这样的网络通信,我们就可以自己实现Web框架了。
通过对socket的学习,我们知道网络通信,我们完全可以自己写了,因为socket就是做网络通信用的,
下面我们就基于socket来自己实现一个web框架,写一个web服务端,让浏览器来请求,并通过自己的服务端把页面返回给浏览器,浏览器渲染出我们想要的效果
1、tcp_server端
import socket tcp_server = socket.socket() #端口复用 tcp_server.setsockopt(socket.SOL_SOCKET,socket.SO_REUSEADDR,True) tcp_server.bind(("127.0.0.1",8898)) #绑定本地8898端口,后续用浏览器访问 tcp_server.listen(128) #获取连接对象和客户端ip和端口 while True: conn,adddr = tcp_server.accept() # 接收数据 res = conn.recv(1024) print(res.decode()) # 发送数据 strvar = "need server?" conn.send(strvar.encode()) conn.close() #tcp_server.close();
浏览器访问:不通
浏览器访问服务器,会发送http请求报文格式到服务端,在服务端打印
GET / HTTP/1.1 #请求方法,请求协议版本号
Host: 127.0.0.1:8898 #请求服务器的ip和端口
Connection: keep-alive #是否长连接
sec-ch-ua: “Chromium”;v=“112”, “Google Chrome”;v=“112”, “Not:A-Brand”;v=“99”
sec-ch-ua-mobile: ?0
sec-ch-ua-platform: “Windows”
Upgrade-Insecure-Requests: 1
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/112.0.0.0 Safari/537.36
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,/;q=0.8,application/signed-exchange;v=b3;q=0.7
Sec-Fetch-Site: none
Sec-Fetch-Mode: navigate
Sec-Fetch-User: ?1
Sec-Fetch-Dest: document
Accept-Encoding: gzip, deflate, br
Accept-Language: zh-CN,zh;q=0.9
但我们服务端是基于socket写的,没写应用层协议,因此要写基于http协议的数据格式响应客户端
现在服务端回应客户端只有一句二进制的 need server ?
客户端根本解不开,因此客户端会报错,无效响应。
回应的时候要把回应的数据格式加工好,再发出去
改造服务端
import socket tcp_server = socket.socket() #端口复用 tcp_server.setsockopt(socket.SOL_SOCKET,socket.SO_REUSEADDR,True) tcp_server.bind(("127.0.0.1",8898)) tcp_server.listen(128) #获取连接对象和客户端ip和端口 while True: conn,adddr = tcp_server.accept() res = conn.recv(1024) print(res.decode()) # 发送数据,这是基于HTTP协议的报文格式 strvar = "HTTP/1.1 200 0k\r\n\r\nneed server?" conn.send(strvar.encode()) conn.close() #tcp_server.close();
此时在通过浏览器访问,就收到服务端信息
现在回复给客户端的是简单的字符串,正常的应该回复页面才对
先创建个简单的页面
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta http-equiv="X-UA-Compatible" content="IE=edge"/> <meta name="viewport" content="width=device-width, initial-scale=1.0"/> <title>个人中心</title> </head> <body> <h1>欢迎来到32期spa会所</h1> </body> </html>
在服务端通过http协议发送给客户端
import socket tcp_server = socket.socket() #端口复用 tcp_server.setsockopt(socket.SOL_SOCKET,socket.SO_REUSEADDR,True) tcp_server.bind(("127.0.0.1",8898)) tcp_server.listen(128) #获取连接对象和客户端ip和端口 while True: conn,adddr = tcp_server.accept() # 接收客户端数据 res = conn.recv(1024) print(res.decode()) with open("beautiful.html","rb") as fp: data = fp.read() # 发送协议数据 strvar = "HTTP/1.1 200 0k\r\n\r\n" conn.send(strvar.encode()) # 发送给客户端页面 conn.send(data) conn.close() #tcp_server.close();
客户端访问,已收到服务端发来的页面
给页面添加点样式和动画效果
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta http-equiv="X-UA-Compatible" content="IE=edge"/> <meta name="viewport" content="width=device-width, initial-scale=1.0"/> <title>个人中心</title> <link rel="stylesheet" href="xx.css"> #引入外部样式 </head> <body> <h1>欢迎来到32期spa会所</h1> <div class="c1"></div> <img src="1.jpg" alt=""> </body> <script src="xx.js"></script> #引入外部js </html>
重新运行服务器,在浏览器访问,样式也引入了,js也引入了,但是却不显示效果
这是为啥呢,这是因为浏览器对html从上往下解析
当解析到link时,由于写的是相对路径,
浏览器会从当前浏览器路径
127.0.0.1:8898/xx.css 查找,去请求后台,拿这个文件。
但却没找到,异步发了几个请求
请求 xx.css 1.jpg xx.js 都没找到,所以样式显示不出来
查看客户端请求
点击xx.css,查看请求详情
后台没给浏览器发数据,浏览器拿不到数据就无法渲染代码
浏览器打开网页 另存为
会将本页面的html代码 和外部引入的js文件css文件等都存到本地
这样,在本地通过浏览器访问该页面才能正常显示
本地访问,就相当于上网访问一样的显示效果
浏览器查看网页文件 检查–application-top。里面都有
如果把一块下载下来的js,css文件删除,那么本地访问,也将少了样式
我们自己搭的服务器,通过浏览器访问页面,没样式相当于上面另存为本地的html文件,样式被删除了
所以访问没有样式
客户端发来的请求
请求方法后面是请求路径,当我们访问url只接端口号,后面也没接的时候,请求的就是根路径
然后是要各种外部加载文件的请求
客户端请求什么数据,服务端就应该给客户端什么数据。
服务端要把对应的数据给响应回去,根据客户端的请求,要什么,就响应什么回去
服务端循环回应客户端数据,每次都是一样的,html页面
目前我们服务端的做法是,客户端一次请求,服务端回应一个数据,断开连接
客户端再次请求,服务端又响应一个数据,断开连接
但每次客户端请求的数据不一样,但服务端回应的数据却是一样的,都是html页面
显然这样是不合理的
应该根据不同的请求回复不同的数据
分析:每次请求,就GET后面的路径不一样
我们可以基于不同的路径回复不同的数据
服务端改造如下
import socket #改造服务端,针对客户端不同的请求,服务端回复不同的内容 tcp_server = socket.socket() #端口复用 tcp_server.setsockopt(socket.SOL_SOCKET,socket.SO_REUSEADDR,True) tcp_server.bind(("127.0.0.1",8898)) tcp_server.listen(128) while True: # 获取连接对象和客户端ip和端口 conn,adddr = tcp_server.accept() # 接收客户端数据 res = conn.recv(1024) from_broswer = res.decode() #重点在这里,根据客户端请求头 GET / HTTP/1.1 可知,以空格为分隔符,取索引为1的数据,即为客户端请求的服务端内容 path = from_broswer.split()[1] 做逻辑判断,针对不同请求,回复给客户端不同数据 if path == "/": with open("beautiful.html", "rb") as fp: data = fp.read() elif path == "/xx.css": with open("xx.css", "rb") as fp: data = fp.read() elif path == "/1.jpg": with open("1.jpg", "rb") as fp: data = fp.read() elif path == "/xx.js": with open("xx.js", "rb") as fp: data = fp.read() # 发送协议数据 strvar = "HTTP/1.1 200 0k\r\n\r\n" conn.send(strvar.encode()) # 发送给客户端页面 conn.send(data) conn.close() #tcp_server.close();
Django前置知识--手撕框架(二):https://developer.aliyun.com/article/1495503