Django框架01 / http协议、web框架本质
Django框架01 / http协议、web框架本质
1.http协议
1.1 http协议简介
- 超文本传输协议(英文:HyperText Transfer Protocol,缩写:HTTP)是一种用于分布式、协作式和超媒体信息系统的应用层协议。HTTP是万维网的数据通信的基础。
1.2 什么是http协议
-
超文本传输协议
-
关于连接:一次请求一次响应之后断开来连接(无状态、短连接)
-
关于格式:
-
请求:请求行、请求头+请求体(http:www.baidu.com/index/?a=123)
- 请求行:是请求方法/url/协议版本
send("GET /index/?a=123 http1.1\r\nhost:www.baidu.com\r\nuseragent:Chrome\r\n\r\n")
send("POST /index/ http1.1\r\nhost:www.baidu.com\r\nuseragent:Chrome\r\n\r\nusername=alex&pwd=123")
-
响应:响应行、响应头+响应体
- 响应行:是请求协议/状态码/状态码描述
-
-
扩展:常见的请求头都有哪些?
- user-agent:用什么浏览器访问的网站。
- content-type: 请求体的数据格式是什么(服务端按照 格式要求进行解析)
-
扩展:常见的请求方式?
- GET
- POST
- PUT
- DELETE
- PATCH
- OPTIONS
-
get与post请求的区别:
-
GET提交的数据会放在URL之后,也就是请求行里面,以?分割URL和传输数据,参数之间以&相连,如EditBook?name=test1&id=123456;
而POST方法是把提交的数据放在HTTP包的请求体中。
-
GET提交的数据大小有限制(因为浏览器对URL的长度有限制)
而POST方法提交的数据没有限制.
-
GET与POST请求在服务端获取请求数据方式不同,就是我们自己在服务端取请求数据的时候的方式不同了
request.GET/request.POST
-
1.3 HTTP工作原理
-
请求数据:
-
请求行:
GET / HTTP/1.1
-
请求头:
Host: 127.0.0.1:8888(注意:端口号不能重复) 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/76.0.3809.132 Safari/537.36 Sec-Fetch-Mode: navigate 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 Purpose: prefetch Sec-Fetch-Site: none Accept-Encoding: gzip, deflate, br Accept-Language: zh-CN,zh;q=0.9
-
请求数据:
username=ergou&password=123
-
-
HTTP 请求/响应的详细步骤:
-
客户端连接到Web服务器
一个HTTP客户端,通常是浏览器,与Web服务器的HTTP端口(默认为80)建立一个TCP套接字连接。例如,http://www.luffycity.com。
-
发送HTTP请求
通过TCP套接字,客户端向Web服务器发送一个文本的请求报文,一个请求报文由请求行、请求头部、空行和请求数据4部分组成。
-
服务器接受请求并返回HTTP响应
Web服务器解析请求,定位请求资源。服务器将资源复本写到TCP套接字,由客户端读取。一个响应由状态行、响应头部、空行和响应数据4部分组成
-
释放连接TCP连接
若connection 模式为close,则服务器主动关闭TCP连接,客户端被动关闭连接,释放TCP连接;若connection 模式为keepalive,则该连接会保持一段时间,在该时间内可以继续接收请求;
-
客户端浏览器解析HTML内容
客户端浏览器首先解析状态行,查看表明请求是否成功的状态代码。然后解析每一个响应头,响应头告知以下为若干字节的HTML文档和文档的字符集。客户端浏览器读取响应数据HTML,根据HTML的语法对其进行格式化,并在浏览器窗口中显示。
-
-
在浏览器地址栏键入URL,按下回车之后会经历以下流程:
- 浏览器向 DNS 服务器请求解析该 URL 中的域名所对应的 IP 地址;
- 解析出 IP 地址后,根据该 IP 地址和默认端口 80,和服务器建立TCP连接;
- 浏览器发出读取文件(URL中域名后面部分对应的文件)的HTTP请求,该请求报文作为 TCP 三次握手的第三个报文的数据发送给服务器;
- 服务器对浏览器请求作出响应,并把对应的 html 文本发送给浏览器;
- 释放 TCP连接;
- 浏览器将该 html 文本并显示内容;
-
http的三大特性解释:
-
基于 请求-响应 的模式
HTTP协议规定,请求从客户端发出,最后服务器端响应该请求并 返回。换句话说,肯定是先从客户端开始建立通信的,服务器端在没有 接收到请求之前不会发送响应
-
无状态保存
- HTTP是一种不保存状态,即无状态(stateless)协议。HTTP协议 自身不对请求和响应之间的通信状态进行保存。也就是说在HTTP这个 级别,协议对于发送过的请求或响应都不做持久化处理。
- 使用HTTP协议,每当有新的请求发送时,就会有对应的新响应产生。
-
无连接
无连接的含义是限制每次连接只处理一个请求。服务器处理完客户的请求,并收到客户的应答后,即断开连接。采用这种方式可以节省传输时间,并且可以提高并发性能。
- 早期的http协议是一个请求一个响应之后,直接就断开了
- 但是现在的http协议1.1版本不是直接就断开了,而是等几秒钟,等着用户有后续的操作,如果用户在这几秒钟之内有新的请求,那么还是通过之前的连接通道来收发消息,如果过了这几秒钟用户没有发送新的请求,那么就会断开连接,这样可以提高效率,减少短时间内建立连接的次数
-
1.4 HTTP请求方法
-
8种方法:
HTTP/1.1协议中共定义了八种方法(也叫“动作”)来以不同方式操作指定的资源: GET: 向指定的资源发出“显示”请求。使用GET方法应该只用在读取数据,而不应当被用于产生“副作用”的操作中,例如在Web Application中。其中一个原因是GET可能会被网络蜘蛛等随意访问。 HEAD: 与GET方法一样,都是向服务器发出指定资源的请求。只不过服务器将不传回资源的本文部分。它的好处在于,使用这个方法可以在不必传输全部内容的情况下,就可以获取其中“关于该资源的信息”(元信息或称元数据)。 POST: 向指定资源提交数据,请求服务器进行处理(例如提交表单或者上传文件)。数据被包含在请求本文中。这个请求可能会创建新的资源或修改现有资源,或二者皆有。 PUT: 向指定资源位置上传其最新内容。 DELETE: 请求服务器删除Request-URI所标识的资源。 TRACE: 回显服务器收到的请求,主要用于测试或诊断。 OPTIONS: 这个方法可使服务器传回该资源所支持的所有HTTP请求方法。用'*'来代替资源名称,向Web服务器发送OPTIONS请求,可以测试服务器功能是否正常运作。 CONNECT: HTTP/1.1协议中预留给能够将连接改为管道方式的代理服务器。通常用于SSL加密服务器的链接(经由非加密的HTTP代理服务器)
-
注意事项:
1.方法名称是区分大小写的。当某个请求所针对的资源不支持对应的请求方法的时候,服务器应当返回状态码405(Method Not Allowed),当服务器不认识或者不支持对应的请求方法的时候,应当返回状态码501(Not Implemented)。 2.HTTP服务器至少应该实现GET和HEAD方法,其他方法都是可选的。当然,所有的方法支持的实现都应当匹配下述的方法各自的语义定义。此外,除了上述方法,特定的HTTP服务器还能够扩展自定义的方法。例如PATCH(由 RFC 5789 指定的方法)用于将局部修改应用到资源。
1.5 HTTP状态码
-
简单释义
所有HTTP响应的第一行都是状态行,依次是当前HTTP版本号,3位数字组成的状态代码,以及描述状态的短语,彼此由空格分隔。
-
状态代码
状态代码的第一个数字代表当前响应的类型: 1xx消息——请求已被服务器接收,继续处理 2xx成功——请求已成功被服务器接收、理解、并接收 3xx重定向——需要后续操作才能完成这一请求 4xx请求错误——请求含有词法错误或者无法被执行 5xx服务器错误——服务器在处理某个正确请求时发生错误
1.6 URL---统一资源定位器
-
各部分代表释义
超文本传输协议(HTTP)的统一资源定位符将从因特网获取信息的五个基本元素包括在一个简单的地址中: 1.传送协议。 # 2.层级URL标记符号(为[//],固定不变) # 3.访问资源需要的凭证信息(可省略) 4.服务器。(通常为域名,有时为IP地址) 5.端口号。(以数字方式表示,若为HTTP的默认值“:80”可省略) 6.路径。(以“/”字符区别路径中的每一个目录名称) 7.查询。(GET模式的窗体参数,以“?”字符为起点,每个参数以“&”隔开,再以“=”分开参数名称与数据,通常以UTF8的URL编码,避开字符冲突的问题) # 8.片段。以“#”字符为起点
-
示例:
以http://www.baidu.com:80/news/index.html?id=250&page=1 为例, 其中: - http---是协议; - www.baidu.com---是服务器; - 80---是服务器上的默认网络端口号,默认不显示; - /news/index.html---是路径(URI:直接定位到对应的资源); - ?id=250&page=1---是查询。 大多数网页浏览器不要求用户输入网页中“http://”的部分,因为绝大多数网页内容是超文本传输协议文件。同样,“80”是超文本传输协议文件的常用端口号,因此一般也不必写明。一般来说用户只要键入统一资源定位符的一部分(www.baidu.com:80/news/index.html?id=250&page=1)就可以了。 由于超文本传输协议允许服务器将浏览器重定向到另一个网页地址,因此许多服务器允许用户省略网页地址中的部分,比如 www。从技术上来说这样省略后的网页地址实际上是一个不同的网页地址,浏览器本身无法决定这个新地址是否通,服务器必须完成重定向的任务
1.7 HTTP请求格式(请求协议)
-
请求头释义:
请求头里面的内容举个例子: 1.length表示请求体里面的数据长度。 2.user-agent,就是告诉你的服务端,我是用什么给你发送的请求。 爬虫例子: 爬京东的时候没问题,但是爬抽屉的时候必须带着user-agent,因为抽屉对user-agent做了判断,来判断你是不是一个正常的请求,算是反扒机制的一种。
1.8 HTTP响应格式(响应协议)
2.web框架的本质及自定义web框架
-
web应用本质
1.所有的Web应用本质: 就是一个socket服务端,而用户的浏览器就是一个socket客户端,基于请求做出响应,客户都先请求,服务端做出对应的响应,按照http协议的请求协议发送请求,服务端按照http协议的响应协议来响应请求,这样的网络通信,我们就可以自己实现Web框架了 2.直接写在html页面里面的img标签的src属性值如果是别人网站的地址(网络地址)是直接可以在浏览器上显示的 3.如果都是网络地址,那么只要你的电脑有网,就可以看到,不需要自己在后端写对应的读取文件,返回图片文件信息的代码,因为别人的网站就做了这个事情了 4.如果你是本地的图片想要返回给页面,你需要对页面上的关于这个图片的请求要自己做出响应,这个src就是来你本地请求这个图片,你只要将图片信息读取出来,返回给页面,页面拿到这个图片的数据,就能够渲染出来了,是不是很简单
2.1 简单版web框架
-
代码示例:
test.py
import socket sk = socket.socket() sk.bind(('127.0.0.1',8001)) sk.listen() conn,addr = sk.accept() from_b_msg = conn.recv(1024) str_msg = from_b_msg.decode('utf-8') #这句就是按照http协议来写的 # conn.send(b'HTTP/1.1 200 ok \r\n\r\nhello') #可以分成下面两句来写 conn.send(b'HTTP/1.1 200 ok \r\n\r\n') conn.send(b'hello')
2.2 返回HTML文件的web框架
-
代码示例:
test.html
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>Title</title><link rel="stylesheet" href="test.css"> <!--直接写在html页面里面的css样式是直接可以在浏览器上显示的--> <style> h1{ background-color: green; color: white; } </style> </head> <body> <h1>姑娘,你好,我是Jaden,请问约吗?嘻嘻~~</h1> <!--直接写在html页面里面的img标签的src属性值如果是别人网站的地址(网络地址)是直接可以在浏览器上显示的--> <img src="https://timgsa.baidu.com/timg?image&quality=80&size=b9999_10000&sec=1550395461724&di=c2b971db12eef5d85aba410d1e2e8568&imgtype=0&src=http%3A%2F%2Fy0.ifengimg.com%2Fifengimcp%2Fpic%2F20140822%2Fd69e0188b714ee789e97_size87_w800_h1227.jpg" alt=""> <!--如果都是网络地址,那么只要你的电脑有网,就可以看到,不需要自己在后端写对应的读取文件,返回图片文件信息的代码,因为别人的网站就做了这个事情了--> <!--直接写在html页面里面的js操作是直接可以在浏览器上显示的--> <script> alert('这是我们第一个网页') </script> </body> </html>
test.py
import socket sk = socket.socket() sk.bind(('127.0.0.1',8001)) sk.listen() conn,addr = sk.accept() from_b_msg = conn.recv(1024) str_msg = from_b_msg.decode('utf-8') print('浏览器请求信息:',str_msg) conn.send(b'HTTP/1.1 200 ok \r\n\r\n') with open('test1.html','rb') as f: f_data = f.read() conn.send(f_data)
2.3 返回静态文件的高级web框架
-
代码示例:
test.py
import socket sk = socket.socket() sk.bind(('127.0.0.1',8001)) sk.listen() #首先浏览器相当于给我们发送了多个请求,一个是请求我们的html文件,而我们的html文件里面的引入文件的标签又给我们这个网站发送了请求静态文件的请求,所以我们要将建立连接的过程循环起来,才能接受多个请求,没毛病 while 1: conn,addr = sk.accept() from_b_msg = conn.recv(1024) str_msg = from_b_msg.decode('utf-8') #通过http协议我们知道,浏览器请求的时候,有一个请求内容的路径,通过对请求信息的分析,这个路径我们在请求的所有请求信息中可以提炼出来,下面的path就是我们提炼出来的路径 path = str_msg.split('\r\n')[0].split(' ')[1] print('path>>>',path) conn.send(b'HTTP/1.1 200 ok \r\n\r\n') #由于整个页面需要html、css、js、图片等一系列的文件,所以我们都需要给人家浏览器发送过去,浏览器才能有这些文件,才能很好的渲染你的页面 #根据不同的路径来返回响应的内容 if path == '/': #返回html文件 print(from_b_msg) with open('test.html','rb') as f: # with open('Python开发.html','rb') as f: data = f.read() conn.send(data) conn.close() elif path == '/meinv.png': #返回图片 with open('meinv.png','rb') as f: pic_data = f.read() # conn.send(b'HTTP/1.1 200 ok \r\n\r\n') conn.send(pic_data) conn.close() elif path == '/test.css': #返回css文件 with open('test.css','rb') as f: css_data = f.read() conn.send(css_data) conn.close() elif path == '/wechat.ico':#返回页面的ico图标 with open('wechat.ico','rb') as f: ico_data = f.read() conn.send(ico_data) conn.close() elif path == '/test.js': #返回js文件 with open('test.js','rb') as f: js_data = f.read() conn.send(js_data) conn.close() #注意:上面每一个请求处理完之后,都有一个conn.close()是因为,HTTP协议是短链接的,一次请求对应一次响应,这个请求就结束了,所以我们需要写上close,不然浏览器自己断了,你自己写的服务端没有断,就会出问题。
2.4 函数版高级web框架
-
代码示例:
test.py
import socket sk = socket.socket() sk.bind(('127.0.0.1',8001)) sk.listen() #处理页面请求的函数 def func1(conn): with open('test.html', 'rb') as f: # with open('Python开发.html','rb') as f: data = f.read() conn.send(data) conn.close() #处理页面img标签src属性值是本地路径的时候的请求 def func2(conn): with open('meinv.png', 'rb') as f: pic_data = f.read() # conn.send(b'HTTP/1.1 200 ok \r\n\r\n') conn.send(pic_data) conn.close() #处理页面link( <link rel="stylesheet" href="test.css">)标签href属性值是本地路径的时候的请求 def func3(conn): with open('test.css', 'rb') as f: css_data = f.read() conn.send(css_data) conn.close() #处理页面link(<link rel="icon" href="wechat.ico">)标签href属性值是本地路径的时候的请求 def func4(conn): with open('wechat.ico', 'rb') as f: ico_data = f.read() conn.send(ico_data) conn.close() #处理页面script(<script src="test.js"></script>)标签src属性值是本地路径的时候的请求 def func5(conn): with open('test.js', 'rb') as f: js_data = f.read() conn.send(js_data) conn.close() while 1: conn,addr = sk.accept() # while 1: from_b_msg = conn.recv(1024) str_msg = from_b_msg.decode('utf-8') path = str_msg.split('\r\n')[0].split(' ')[1] print('path>>>',path) conn.send(b'HTTP/1.1 200 ok \r\n\r\n') print(from_b_msg) if path == '/': func1(conn) elif path == '/meinv.png': func2(conn) elif path == '/test.css': func3(conn) elif path == '/wechat.ico': func4(conn) elif path == '/test.js': func5(conn)
2.5 多线程版web框架
-
代码示例:
test.py
import socket from threading import Thread #注意一点,不开多线程完全是可以搞定的,在这里只是教大家要有并发编程的思想,所以我使用了多线程 sk = socket.socket() sk.bind(('127.0.0.1',8001)) sk.listen() def func1(conn): with open('test.html', 'rb') as f: # with open('Python开发.html','rb') as f: data = f.read() conn.send(data) conn.close() def func2(conn): with open('meinv.png', 'rb') as f: pic_data = f.read() # conn.send(b'HTTP/1.1 200 ok \r\n\r\n') conn.send(pic_data) conn.close() def func3(conn): with open('test.css', 'rb') as f: css_data = f.read() conn.send(css_data) conn.close() def func4(conn): with open('wechat.ico', 'rb') as f: ico_data = f.read() conn.send(ico_data) conn.close() def func5(conn): with open('test.js', 'rb') as f: js_data = f.read() conn.send(js_data) conn.close() while 1: conn,addr = sk.accept() # while 1: from_b_msg = conn.recv(1024) str_msg = from_b_msg.decode('utf-8') path = str_msg.split('\r\n')[0].split(' ')[1] print('path>>>',path) conn.send(b'HTTP/1.1 200 ok \r\n\r\n') print(from_b_msg) if path == '/': # func1(conn) t = Thread(target=func1,args=(conn,)) t.start() elif path == '/meinv.png': # func2(conn) t = Thread(target=func2, args=(conn,)) t.start() elif path == '/test.css': # func3(conn) t = Thread(target=func3, args=(conn,)) t.start() elif path == '/wechat.ico': # func4(conn) t = Thread(target=func4, args=(conn,)) t.start() elif path == '/test.js': # func5(conn) t = Thread(target=func5, args=(conn,)) t.start()
2.6 高级版web框架
-
代码示例:
test.py
import socket from threading import Thread sk = socket.socket() sk.bind(('127.0.0.1',8001)) sk.listen() def func1(conn): conn.send(b'HTTP/1.1 200 ok\r\ncontent-type:text/html\r\ncharset:utf-8\r\n\r\n') with open('test.html', 'rb') as f: # with open('Python开发.html','rb') as f: data = f.read() conn.send(data) conn.close() def func2(conn): conn.send(b'HTTP/1.1 200 ok\r\n\r\n') with open('meinv.png', 'rb') as f: pic_data = f.read() # conn.send(b'HTTP/1.1 200 ok \r\n\r\n') conn.send(pic_data) conn.close() def func3(conn): conn.send(b'HTTP/1.1 200 ok\r\n\r\n') with open('test.css', 'rb') as f: css_data = f.read() conn.send(css_data) conn.close() def func4(conn): conn.send(b'HTTP/1.1 200 ok\r\n\r\n') with open('wechat.ico', 'rb') as f: ico_data = f.read() conn.send(ico_data) conn.close() def func5(conn): conn.send(b'HTTP/1.1 200 ok\r\n\r\n') with open('test.js', 'rb') as f: js_data = f.read() conn.send(js_data) conn.close() #定义一个路径和执行函数的对应关系,不再写一堆的if判断了 l1 = [ ('/',func1), ('/meinv.png',func2), ('/test.css',func3), ('/wechat.ico',func4), ('/test.js',func5), ] #遍历路径和函数的对应关系列表,并开多线程高效的去执行路径对应的函数, def fun(path,conn): for i in l1: if i[0] == path: t = Thread(target=i[1],args=(conn,)) t.start() # else: # conn.send(b'sorry') while 1: conn,addr = sk.accept() #看完这里面的代码之后,你就可以思考一个问题了,很多人要同时访问你的网站,你在请求这里是不是可以开起并发编程的思想了,多进程+多线程+协程,妥妥的支持高并发,再配合服务器集群,这个网页就支持大量的高并发了,有没有很激动,哈哈,但是咱们写的太low了,而且功能很差,容错能力也很差,当然了,如果你有能力,你现在完全可以自己写web框架了,写一个nb的,如果现在没有这个能力,那么我们就来好好学学别人写好的框架把,首先第一个就是咱们的django框架了,其实就是将这些功能封装起来,并且容错能力强,抗压能力强,总之一个字:吊。 # while 1: from_b_msg = conn.recv(1024) str_msg = from_b_msg.decode('utf-8') path = str_msg.split('\r\n')[0].split(' ')[1] print('path>>>',path) # 注意:因为开启的线程很快,可能导致你的文件还没有发送过去,其他文件的请求已经来了,导致你文件信息没有被浏览器正确的认识,所以需要将发送请求行和请求头的部分写道前面的每一个函数里面去,并且防止出现浏览器可能不能识别你的html文件的情况,需要在发送html文件的那个函数里面的发送请求行和请求头的部分加上两个请求头content-type:text/html\r\ncharset:utf-8\r\n # conn.send(b'HTTP/1.1 200 ok\r\n\r\n') 不这样写了 # conn.send(b'HTTP/1.1 200 ok\r\ncontent-type:text/html\r\ncharset:utf-8\r\n\r\n') 不这样写了 print(from_b_msg) #执行这个fun函数并将路径和conn管道都作为参数传给他 fun(path,conn)
2.7 不同路径返回不同页面的web框架
-
代码示例:
""" 根据URL中不同的路径返回不同的内容 返回独立的HTML页面 """ import socket sk = socket.socket() sk.bind(("127.0.0.1", 8080)) # 绑定IP和端口 sk.listen() # 监听 # 将返回不同的内容部分封装成函数 def index(url): # 读取index.html页面的内容 with open("index.html", "r", encoding="utf8") as f: s = f.read() # 返回字节数据 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), ] while 1: # 等待连接 conn, add = sk.accept() data = conn.recv(8096) # 接收客户端发来的消息 # 从data中取到路径 data = str(data, encoding="utf8") # 把收到的字节类型的数据转换成字符串 # 按\r\n分割 data1 = data.split("\r\n")[0] url = data1.split()[1] # url是我们从浏览器发过来的消息中分离出的访问路径 conn.send(b'HTTP/1.1 200 OK\r\n\r\n') # 因为要遵循HTTP协议,所以回复的消息也要加状态行 # 根据不同的路径返回不同内容 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!" # 返回具体的响应消息 conn.send(response) conn.close()
2.8 返回动态页面的web框架
-
代码示例:
""" 动态网站的意思是里面有动态变化的数据,而不是页面里面有动态效果 """ """ 根据URL中不同的路径返回不同的内容 返回HTML页面 让网页动态起来 """ import socket import time sk = socket.socket() sk.bind(("127.0.0.1", 8080)) # 绑定IP和端口 sk.listen() # 监听 # 将返回不同的内容部分封装成函数 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), ] while 1: # 等待连接 conn, add = sk.accept() data = conn.recv(8096) # 接收客户端发来的消息 # 从data中取到路径 data = str(data, encoding="utf8") # 把收到的字节类型的数据转换成字符串 # 按\r\n分割 data1 = data.split("\r\n")[0] url = data1.split()[1] # url是我们从浏览器发过来的消息中分离出的访问路径 conn.send(b'HTTP/1.1 200 OK\r\n\r\n') # 因为要遵循HTTP协议,所以回复的消息也要加状态行 # 根据不同的路径返回不同内容 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!" # 返回具体的响应消息 conn.send(response) conn.close()
注意:
- 在里面获取路径的时候,我们是按照\r\n来分割然后再通过空格来分割获取到的路径,但是如果不是http协议的话,就要注意消息格式
3.wsgiref模块版web框架
-
wsgiref模块其实就是将整个请求信息给封装了起来,就不需要你自己处理了,假如它将所有请求信息封装成了一个叫做request的对象,那么你直接request.path就能获取到用户这次请求的路径,request.method就能获取到本次用户请求的请求方式(get还是post)等
-
对于真实开发中的python web程序来说,一般会分为两部分:服务器程序和应用程序。
-
WSGI(Web Server Gateway Interface)就是一种规范,它定义了使用Python编写的web应用程序与web服务器程序之间的接口格式,实现web应用程序与web服务器程序间的解耦。
-
代码示例:
from wsgiref.simple_server import make_server # 函数名字随便起 def application(environ, start_response): ''' :param environ: 是全部加工好的请求信息,加工成了一个字典,通过字典取值的方式就能拿到很多你想要拿到的信息 :param start_response: 封装响应信息的(响应行和响应头),注意下面的参数 :return: ''' start_response('200 OK', [('Content-Type', 'text/html'),('k1','v1')]) print(environ) print(environ['PATH_INFO']) # 输入地址127.0.0.1:8000,这个打印的是'/',输入的是127.0.0.1:8000/index,打印结果是'/index' return [b'<h1>Hello, web!</h1>'] #和socketserver那个模块很像 httpd = make_server('127.0.0.1', 8080, application) print('Serving HTTP on port 8080...') # 开始监听HTTP请求: httpd.serve_forever() # wsgiref本身就是个web框架,提供了一些固定的功能 # 请求和响应信息的封装,不需要我们自己写原生的socket了也不需要咱们自己来完成请求信息的提取了,提取起来很方便
4.模板渲染JinJa2
-
Jinja2 是一个 Python 的功能齐全的模板引擎。它有完整的 unicode 支持,一个可选 的集成沙箱执行环境,被广泛使用,以 BSD 许可证授权。
-
代码示例:
index.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>Title</title> </head> <body> <h1>姓名:{{name}}</h1> <h1>爱好:</h1> <ul> {% for hobby in hobby_list %} <li>{{hobby}}</li> {% endfor %} </ul> </body> </html>
index.py --使用jinja2渲染index.html文件
from wsgiref.simple_server import make_server from jinja2 import Template def index(): with open("index2.html", "r",encoding='utf-8') as f: data = f.read() t = Template(data) # 生成模板文件 ret = t.render({"name": "张三", "hobby_list": ["抽烟", "喝酒"]}) # 把数据填充到模板里面 return [bytes(ret, encoding="utf8"), ] # 定义一个url和函数的对应关系 URL_LIST = [ ("/index/", index), ] 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()
5.完整版web项目--用户登录认证的项目--连接数据库
-
web model.py -- 创建文件
# 创建表,插入数据 def createtable(): import pymysql conn = pymysql.connect( host='127.0.0.1', port=3306, user='root', password='666', database='db1', charset='utf8' ) cursor = conn.cursor(pymysql.cursors.DictCursor) sql = ''' -- 创建表 create table userinfo(id int primary key auto_increment,username char(20) not null unique,password char(20) not null); -- 插入数据 insert into userinfo(username,password) values('chao','666'),('sb1','222'); ''' cursor.execute(sql) conn.commit() cursor.close() conn.close()
-
web auth.py--用户认证
# 对用户名和密码进行验证 def auth(username,password): import pymysql conn = pymysql.connect( host='127.0.0.1', port=3306, user='root', password='123', database='db1', charset='utf8' ) print('userinfo',username,password) cursor = conn.cursor(pymysql.cursors.DictCursor) sql = 'select * from userinfo where username=%s and password=%s;' res = cursor.execute(sql, [username, password]) if res: return True else: return False
-
web.html -- 用户输入用户名和密码
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>Title</title> </head> <body> <!--如果form表单里面的action什么值也没给,默认是往当前页面的url上提交你的数据,所以我们可以自己指定数据的提交路径--> <form action="http://127.0.0.1:8080/auth/" method="get"> 用户名<input type="text" name="username"> 密码 <input type="password" name="password"> <input type="submit"> </form> </body> </html>
-
web success.html--用户验证成功后跳转的页面
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>Title</title> <style> h1{ color:red; } </style> </head> <body> <h1>登陆成功</h1> </body> </html>
-
web_python.py--python服务端代码 ( 主逻辑代码 )
from urllib.parse import parse_qs from wsgiref.simple_server import make_server import webauth def application(environ, start_response): start_response('200 OK', [('Content-Type', 'text/html')]) print(environ) print(environ['PATH_INFO']) path = environ['PATH_INFO'] # 用户获取login页面的请求路径 if path == '/login': with open('web.html','rb') as f: data = f.read() # 针对form表单提交的auth路径,进行对应的逻辑处理 elif path == '/auth/': # 登陆认证 # 1.获取用户输入的用户名和密码 # 2.去数据库做数据的校验,查看用户提交的是否合法 # user_information = environ[''] if environ.get("REQUEST_METHOD") == "POST": # 获取请求体数据的长度,因为提交过来的数据需要用它来提取,注意POST请求和GET请求的获取数据的方式不同 try: request_body_size = int(environ.get('CONTENT_LENGTH', 0)) except (ValueError): request_body_size = 0 # POST请求获取数据的方式 request_data = environ['wsgi.input'].read(request_body_size) print('>>>>>',request_data) # >>>>> b'username=chao&password=123',是个bytes类型数据 print('---->',environ['QUERY_STRING']) # ----> 空的,因为post请求只能按照上面这种方式取数据 # parse_qs可以解析数据 re_data = parse_qs(request_data) print('拆解后的数据',re_data) # 拆解后的数据 {b'password': [b'123'], b'username': [b'zhangsan']} pass if environ.get("REQUEST_METHOD") == "GET": #GET请求获取数据的方式,只能按照这种方式取 print('?????',environ['QUERY_STRING']) #????? username=chao&password=123,是个字符串类型数据 request_data = environ['QUERY_STRING'] # parse_qs可以帮我们解析数据 re_data = parse_qs(request_data) print('拆解后的数据', re_data) #拆解后的数据 {'password': ['123'], 'username': ['chao']} username = re_data['username'][0] password = re_data['password'][0] print(username,password) # 进行验证: status = webauth.auth(username,password) if status: # 3.将相应内容返回 with open('websuccess.html','rb') as f: data = f.read() else: data = b'auth error' else: data = b'sorry 404!,not found the page' return [data] # 和socketserver模块很像 httpd = make_server('127.0.0.1', 8080, application) print('Serving HTTP on port 8080...') # 开始监听HTTP请求: httpd.serve_forever()
6.MVC框架、MTV框架
-
MVC框架( flask ):
M:model.py 就是和数据库打交道用的,创建表等操作 V:View 视图(视图函数,html文件) C:controller 控制器(里面那个urls文件里面的内容,url(路径)分发与视图函数的逻辑处理)
-
MTV框架(Django):
M:model.py 就是和数据库打交道用的,创建表等操作(和上面一样) T:templates 存放HTML文件的 V:View 视图函数(逻辑处理)
-
MVC中的View的目的是「呈现哪一个数据」,而MTV的View的目的是「数据如何呈现」。