Web框架原理
Posted dongye95
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Web框架原理相关的知识,希望对你有一定的参考价值。
一 web框架的本质
简单描述就是:浏览器通过你输入的网址给你的socket服务端发送请求,服务端接受到请求给其回复一个对应的html页面,这就是web项目。所有的Web应用本质上就是一个socket服务端,而用户的浏览器就是一个socket客户端,基于请求做出响应,客户都先请求,服务端做出对应的响应,按照http协议的请求协议发送请求,服务端按照http协议的响应协议来响应请求,这样的网络通信,我们就可以自己实现Web框架了。
什么是web框架?这就好比建设房子,房子主体钢结构等都为我们搭建好了,我们就是抹抹墙面,添加一些装饰,修饰一下即可。Django框架就是已经为我们搭建好的主题钢结构,剩下的根据不同的业务自定制即可。但是我们先不着急学习Django框架,今天我们先自己搭建一个web框架,自己搭建了web框架之后,你对Django框架就会比较好理解了。
1.1 先构建socket服务端
import socket server = socket.socket() server.bind((\'127.0.0.1\', 8002)) server.listen() while 1: conn, addr = server.accept() while 1: client_data = conn.recv(1024).decode(\'utf-8\') print(client_data) # 服务端与客户端建立联系必须要遵循一个协议,此时我们用http协议示例。 conn.send(\'HTTP/1.1 200 OK \\r\\n\\r\\n\'.encode(\'utf-8\')) conn.send(\'<h1>hello</h1>\'.encode(\'utf-8\')) conn.close()
我们通过浏览器请求服务端,服务端给我们返回一个hello标签。客户端请求过来之后,要想让服务端给客户端返回消息,必须基于一个协议,我们用http协议示例。那么服务端如果返回给浏览器一个页面呢?
1.2 构建一个html页面
<!DOCTYPE html> <html lang="zh-CN"> <head> <meta charset="utf-8"> <meta http-equiv="X-UA-Compatible" content="IE=edge"> <meta name="viewport" content="width=device-width, initial-scale=1"> <style> div { background-color: #7fb8ff; color: red; } </style> </head> <body> <div>你好,世界</div> </body> </html>
此时你的服务端必须将html页面返回给客户端,怎么返回?就得通过发送bytes数据。
服务端:
import socket server = socket.socket() server.bind((\'127.0.0.1\', 8002)) server.listen() while 1: conn, addr = server.accept() client_data = conn.recv(1024).decode(\'utf-8\') print(client_data) # 服务端与客户端建立联系必须要遵循一个协议,此时我们用http协议示例。 conn.send(\'HTTP/1.1 200 OK \\r\\n\\r\\n\'.encode(\'utf-8\')) with open(\'demo.html\', mode=\'rb\') as f1: conn.send(f1.read()) conn.close()
这么我们发现一个问题,当你的浏览器访问服务端时,服务端会接受到这些信息:
GET / HTTP/1.1 Host: 127.0.0.1:8001 Connection: keep-alive Upgrade-Insecure-Requests: 1 User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_14_6) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/79.0.3945.130 Safari/537.36 Sec-Fetch-User: ?1 Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.9 Sec-Fetch-Site: none Sec-Fetch-Mode: navigate Accept-Encoding: gzip, deflate, br Accept-Language: zh-CN,zh;q=0.9
这些是什么信息?这是因为服务端与客户端遵循http协议,这是协议做的事情,所以在我们研究web框架之前,必不可少的需要研究一下这个协议,协议到底做了什么事情。
http协议详见:《图解HTTP》- 上野·宣、《HTTP抓包实战》- 肖佳
1.3 http协议具体研究
1.3.1 请求步骤
1.3.1.1 建立链接:客户端连接到Web服务器。
一个HTTP客户端,通常是浏览器,与Web服务器的HTTP端口(默认为80)建立一个TCP套接字连接。例如,http://www.baidu.com。
1.3.1.2 发送HTTP请求
通过TCP套接字,客户端向Web服务器发送一个文本的请求报文,一个请求报文由请求行、请求头部、空行和请求数据4部分组成。
这里分不同的请求,主要是get、post请求。
首先看get请求:
更改一下我们的html页面:
<!DOCTYPE html> <html lang="zh-CN"> <head> <meta charset="utf-8"> <meta http-equiv="X-UA-Compatible" content="IE=edge"> <meta name="viewport" content="width=device-width, initial-scale=1"> <title>Bootstrap 101 Template</title> <link href="https://cdn.jsdelivr.net/npm/bootstrap@3.3.7/dist/css/bootstrap.min.css" rel="stylesheet"> </head> <body> <form action="http://127.0.0.1:8002"> 用户名:<input type="text" name="username"> 密码:<input type="password" name="password"> 验证码:<input type="text" name="code"> <input type="submit"> </form> </body> </html>
此时你发送的是get请求,服务器接受到的消息为:
GET /?username=taibai&password=123&code=2w3e HTTP/1.1 Host: 127.0.0.1:8002 Connection: keep-alive Upgrade-Insecure-Requests: 1 User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_14_6) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/79.0.3945.130 Safari/537.36 Sec-Fetch-User: ?1 Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.9 Sec-Fetch-Site: same-origin Sec-Fetch-Mode: navigate Referer: http://127.0.0.1:8002/?username=taibai&password=123&code=3er4 Accept-Encoding: gzip, deflate, br Accept-Language: zh-CN,zh;q=0.9
你的get请求的数据都是拼接到url后面的:
再来看post请求:
更改一下我们的html页面:
<!DOCTYPE html> <html lang="zh-CN"> <head> <meta charset="utf-8"> <meta http-equiv="X-UA-Compatible" content="IE=edge"> <meta name="viewport" content="width=device-width, initial-scale=1"> <title>Bootstrap 101 Template</title> <link href="https://cdn.jsdelivr.net/npm/bootstrap@3.3.7/dist/css/bootstrap.min.css" rel="stylesheet"> </head> <body> <form action="http://127.0.0.1:8002" method="post"> 用户名:<input type="text" name="username"> 密码:<input type="password" name="password"> 验证码:<input type="text" name="code"> <input type="submit"> </form> </body> </html>
此时你发送的是post请求,服务器接受到的消息为:
POST / HTTP/1.1 Host: 127.0.0.1:8002 Connection: keep-alive Content-Length: 38 Cache-Control: max-age=0 Origin: http://127.0.0.1:8002 Upgrade-Insecure-Requests: 1 Content-Type: application/x-www-form-urlencoded User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_14_6) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/79.0.3945.130 Safari/537.36 Sec-Fetch-User: ?1 Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.9 Sec-Fetch-Site: same-origin Sec-Fetch-Mode: navigate Referer: http://127.0.0.1:8002/ Accept-Encoding: gzip, deflate, br Accept-Language: zh-CN,zh;q=0.9 username=taibai&password=123&code=2w3e
post请求,请求数据是在最后一行的。
正确的请求的格式为:
请求头部有一些重要的键值对:
Host: 127.0.0.1:8002------> 主机(IP地址和端口)
Connection: keep-alive-----> 链接是否保留一段时间
User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_14_6) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/78.0.3904.87 Safari/537.36 ------> 用户的浏览器代理
Content-Length: 29 ------> 请求数据的长度
1.3.1.3 服务器接受请求并返回HTTP响应
Web服务器解析请求,定位请求资源。服务器将资源复本写到TCP套接字,由客户端读取。一个响应由状态行、响应头部、空行和响应数据4部分组成。
响应从哪里看呢?响应从服务器中获取:
标准的响应格式:
1.3.1.4 释放连接TCP连接
若connection 模式为close,则服务器主动关闭TCP连接,客户端被动关闭连接,释放TCP连接;若connection 模式为keepalive,则该连接会保持一段时间,在该时间内可以继续接收请求;
1.3.1.5 客户端浏览器解析HTML内容
客户端浏览器首先解析状态行,查看表明请求是否成功的状态代码。然后解析每一个响应头,响应头告知以下为若干字节的HTML文档和文档的字符集。客户端浏览器读取响应数据HTML,根据HTML的语法对其进行格式化,并在浏览器窗口中显示。
1.3.2 重要知识点
URL、请求方式、http协议的特点,请求流程,请求格式,响应格式。响应状态码等。
post与get请求的区别:
GET提交的数据会放在URL之后,也就是请求行里面,以?分割URL和传输数据,参数之间以&相连,如EditBook?name=test1&id=123456.(请求头里面那个content-type做的这种参数形式,后面讲) POST方法是把提交的数据放在HTTP包的请求体中. GET提交的数据大小有限制(因为浏览器对URL的长度有限制),而POST方法提交的数据没有限制. GET与POST请求在服务端获取请求数据方式不同,就是我们自己在服务端取请求数据的时候的方式不同了,这句废话昂。 常用的get请求:浏览器输入网址,a标签,form标签默认get请求。 常用的post请求:一般就是用来提交数据,比如用户名密码登录。
二、构建web框架
2.1 简单版web框架
之前通过pycharm打开html页面,是pychram给你提供的服务端,我们应该自己提供服务端,通过浏览器与服务端交互。
html:
<!DOCTYPE html> <html lang="zh-CN"> <head> <meta charset="utf-8"> <meta http-equiv="X-UA-Compatible" content="IE=edge"> <meta name="viewport" content="width=device-width, initial-scale=1"> <style> div{ background-color: #7fb8ff; font-size: 16px; } </style> </head> <body> <div>欢迎访问xx商城</div> <ul> <li>aa</li> <li>bb</li> <li>cc</li> </ul> </body> </html>
python:
import socket
server = socket.socket()
server.bind((\'127.0.0.1\', 8001))
server.listen()
while 1:
conn, addr = server.accept()
client_data = conn.recv(1024).decode(\'utf-8\')
print(client_data)
# 服务端与客户端建立联系必须要遵循一个协议,此时我们用http协议示例。
conn.send(\'HTTP/1.1 200 OK \\r\\nk1:v1\\r\\n\\r\\n\'.encode(\'utf-8\'))
with open(\'02 简单版html.html\', mode=\'rb\') as f1:
conn.send(f1.read())
conn.close()
2.2 升级版web框架
首先我们应该创建不同的css、js文件,通过html引入。
css
div{
background-color: #7fb8ff;
font-size: 16px;
}
js
alert(\'未满18岁禁止入内\');
浏览器执行到link标签时,href会发出一个请求,请求你当前根目录下面的test.css文件,其实完整url也就是http://127.0.0.1:8002/test.css,你引入的js同理,此过程是异步的。html代码不会等你引入完css之后,再执行下面代码。
你的python服务端与简单版本一致,我们启动一下服务端,看浏览器发送了什么请求。
此时你的服务端接受到了如下几个请求:
GET / HTTP/1.1
Host: 127.0.0.1:8002
Connection: keep-alive
Upgrade-Insecure-Requests: 1
User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_14_6) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/79.0.3945.130 Safari/537.36
Sec-Fetch-User: ?1
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.9
Sec-Fetch-Site: none
Sec-Fetch-Mode: navigate
Accept-Encoding: gzip, deflate, br
Accept-Language: zh-CN,zh;q=0.9
GET /test.css HTTP/1.1
Host: 127.0.0.1:8002
Connection: keep-alive
User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_14_6) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/79.0.3945.130 Safari/537.36
Accept: text/css,*/*;q=0.1
Sec-Fetch-Site: same-origin
Sec-Fetch-Mode: no-cors
Referer: http://127.0.0.1:8002/
Accept-Encoding: gzip, deflate, br
Accept-Language: zh-CN,zh;q=0.9
GET /test.js HTTP/1.1
Host: 127.0.0.1:8002
Connection: keep-alive
User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_14_6) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/79.0.3945.130 Safari/537.36
Accept: */*
Sec-Fetch-Site: same-origin
Sec-Fetch-Mode: no-cors
Referer: http://127.0.0.1:8002/
Accept-Encoding: gzip, deflate, br
Accept-Language: zh-CN,zh;q=0.9
GET /favicon.ico HTTP/1.1
Host: 127.0.0.1:8002
Connection: keep-alive
User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_14_6) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/79.0.3945.130 Safari/537.36
Accept: image/webp,image/apng,image/*,*/*;q=0.8
Sec-Fetch-Site: same-origin
Sec-Fetch-Mode: no-cors
Referer: http://127.0.0.1:8002/
Accept-Encoding: gzip, deflate, br
Accept-Language: zh-CN,zh;q=0.9
你的浏览器给服务端发送了4个请求,这4个请求分别是:对根目录也就是html的请求、对css文件请求、对js文件请求、以及favincon也就是小图标的请求,
虽然浏览器发送了四个请求,但是我们统一回复的就是一个html页面,相当于我们重复回复了4次同一个html页面,这也导致了我们的页面没有css,js的效果:
所以我们应该怎么做?对不同的请求返回给不同的文件资源,比如他请求的css文件,我们就返回css文件资源,这样页面就有了更加丰富的样式。那么如何区分他的请求呢?就是通过每次请求的第一行的请求路径,所以我们应该把每次的请求路径分割,根据不同的请求路径,返回给其不同的文件资源。
最终的html、以及服务端代码:
<!DOCTYPE html> <html lang="zh-CN"> <head> <meta charset="utf-8"> <meta http-equiv="X-UA-Compatible" content="IE=edge"> <meta name="viewport" content="width=device-width, initial-scale=1"> <link rel="stylesheet" href="test.css"> <link rel="icon" href="jd.ico"> </head> <body> <div>欢迎访问和软商城</div> <ul> <li>aa</li> <li>bb</li> <li>cc</li> </ul> <img src="https://timgsa.baidu.com/timg?image&quality=80&size=b9999_10000&sec=1573643177693&di=8164a8d196f71177af75ed9fdaf7c3bd&imgtype=0&src=http%3A%2F%2Fhbimg.b0.upaiyun.com%2F04763621f7af266b3cc1d9d2fdd44e283f42188a13bda-9dpYLt_fw658" alt=""> {#<img src="mv.jpeg" alt="">#} <script src="test.js"></script> </body> </html> # 上面我添加了两个标签,就是对图片的请求。
python
import socket server = socket.socket() server.bind((\'127.0.0.1\', 8001)) server.listen() while 1: conn, addr = server.accept() client_data = conn.recv(1024) request_path = client_data.decode(\'utf-8\').split(\'\\r\\n\')[0].split()[1] # 服务端与客户端建立联系必须要遵循一个协议,此时我们用http协议示例。 conn.send(\'HTTP/1.1 200 OK \\r\\nk1:v1\\r\\n\\r\\n\'.encode(\'utf-8\')) if request_path == \'/\': with open(\'03 升级版html.html\', mode=\'rb\') as f1: conn.send(f1.read()) elif request_path == \'/test.css\': with open(\'test.css\', mode=\'rb\') as f1: conn.send(f1.read()) elif request_path == \'/mv.jpeg\': with open(\'mv.jpeg\', mode=\'rb\') as f1: conn.send(f1.read()) elif request_path == \'/test.js\': with open(\'test.js\', mode=\'rb\') as f1: conn.send(f1.read()) elif request_path == \'/jd.ico\': with open(\'jd.ico\', mode=\'rb\') as f1: conn.send(f1.read()) conn.close()
favicon.ico 就是窗口小图标,浏览器一直会向服务器发送请求。
图片的索取与文件是一样的,也就是通过路径索取资源。
这样升级版web框架我们就完成了。
2.3 函数版web框架
上一个版本用了多个if elif elif....... 这种方式比较low,并且代码应该整合成函数而不能使用纯面向过程方式,所以我们进行改版:
html
<!DOCTYPE html> <html lang="zh-CN"> <head> <meta charset="utf-8"> <meta http-equiv="X-UA-Compatible" content="IE=edge"> <meta name="viewport" content="width=device-width, initial-scale=1"> <link rel="stylesheet" href="test.css"> <link rel="icon" href="jd.ico"> </head> <body> <div>欢迎访问和软商城</div> <ul> <li>aa</li> <li>bb</li> <li>cc</li> </ul> <img src="mv.jpeg" alt=""> <script src="test.js"></script> </body> </html>
python
import socket server = socket.socket() server.bind((\'127.0.0.1\', 8001)) server.listen() def html(conn): with open(\'04 函数进阶版html.html\', mode=\'rb\') as f1: conn.send(f1.read()) conn.close() def css(conn): with open(\'test.css\', mode=\'rb\') as f1: conn.send(f1.read()) conn.close() def jpeg(conn): with open(\'mv.jpeg\', mode=\'rb\') as f1: conn.send(f1.read()) conn.close() def js(conn): with open(\'test.js\', mode=\'rb\') as f1: conn.send(f1.read()) conn.close() def jd(conn): with open(\'jd.ico\', mode=\'rb\') as f1: conn.send(f1.read()) conn.close() request_list = [ (\'/\', html), (\'/test.css\', css), (\'/mv.jpeg\', jpeg), (\'/test.js\', js), (\'/jd.ico\', jd), ] while 1: conn, addr = server.accept() client_data = conn.recv(1024) request_path = client_data.decode(\'utf-8\').split(\'\\r\\n\')[0].split()[1] # 服务端与客户端建立联系必须要遵循一个协议,此时我们用http协议示例。 conn.send(\'HTTP/1.1 200 OK \\r\\nk1:v1\\r\\n\\r\\n\'.encode(\'utf-8\')) for i in request_list: if request_path == i[0]: i[1](conn) conn.close()
虽然这个这个版本感觉更加简洁明了了,但是这个版本还是不完美,现在虽然是异步处理请求,但是上一阶段我们学完并发,我们对于这些异步请求应该用并发处理更加合理。
2.4 并发版web框架
此时我们要加上多线程处理浏览器发送过来的请求。
html
<!DOCTYPE html> <html lang="zh-CN"> <head> <meta charset="utf-8"> <meta http-equiv="X-UA-Compatible" content="IE=edge"> <meta name="viewport" content="width=device-width, initial-scale=1"> <link rel="stylesheet" href="test.css"> <link rel="icon" href="jd.ico"> </head> <body> <div>欢迎访问河软商城</div> <div>{time_now}</div> <ul> <li>aa</li> <li>bb</li> <li>cc</li> </ul> <!--<img src="https://timgsa.baidu.com/timg?image&quality=80&size=b9999_10000&sec=1573643177693&di=8164a8d196f71177af75ed9fdaf7c3b以上是关于Web框架原理的主要内容,如果未能解决你的问题,请参考以下文章
text 来自Codyhouse框架的Browserlist片段源代码