python高级之HTTP
一、HTTP
1.1网络
(1)网络的本质:就是为了获取和收发数据
(2)基于网络的应用程序的本质:对于用户来说,应用程序的本质就是获取数据;对于开发人员来说,应用程序的本质就是传输数据
1.2 TCP/IP协议栈
(1)协议:让通信的双方能够理解对方的意图
(2)TCP/IP协议是一个协议族
(3)TCP/IP四层模型:链路层——网络层——传输层——应用层
(4)工作流程:A要将数据发送给B:第一步,A将要发送的数据发送给TCP/IP协议栈;第二步,TCP/IP协议栈封装端口信息,确定传输的协议;第三步,封装IP地址;第四步,网卡收到后,封装mac地址的信息;第五步,确定计算机设备要处于网络当中。B端的操作正好和A端相反
1.3网络五层模型
(1)网络五层模型:实体层——链接层——网络层——传输层——应用层
(2)网络五层模型特点:
①分层是为了更高效的通信以及定位错误。
②每一层都使用了一些协议,目的为了通信双方能够理解对方的意图
③每一层功能的实现,都为上层功能的实现做好了铺垫
④越靠下的层,越接近硬件。越靠上的层越接近用户
⑤每层都对数据进行了封装
1.3.1实体层
作用:负责通过物理手段联网,并传输0和1的电信号
1.3.2链路层
(1)作用:确定了数据的分组规则,实现网卡到网卡的通信
(2)使用协议:以太网协议
(3)数据包:每一组电信号就是一个数据包
数据包分为两部分:标头和数据
标头包含的信息有:自己的ip地址,自己的mac地址,对方的ip地址
1.3.3网络层
(1)网络层引入了一套新的地址(ip地址),实现了主机到主机的通信
(2)使用协议:ARP协议(地址解析协议)
作用:通过IP地址找到Mac地址
过程:自己的数据包——>交换机———>广播———>比对ip地址——>mac
如果对方的ip地址和自己的ip地址不属于同一个局域网,数据会交给网关处理
(3)网关:一个网络到另一个网络的“关口“。作用:能够将数据从一个网络传向另一个网络
1.3.4传输层
建立端口到端口的通信
1.3.5应用层
规定应用程序的数据格式
应用层的协议:规范数据传输的格式
1.3.6普及网络每层使用到的协议
实体层:1000BASE-SX、1000BASE-LX、1000BASE-CX
链接层:Frame Relay, HDLC, PPP, IEEE 802.3/802.2, FDDI, ATM, IEEE802.5/802.2
网络层:ICMP、ARP、RARP、IGMP
传输层:TCP 、UDP、 SPX
应用层:HTTP、 DNS, SMTP,FTP ,Telnet ,SNMP,POP3
1.4 HTTP协议(超文本传输协议)
1.4.1超文本(超级文本)
超文本就是利用超链接将线性存储的数据,提升到了非线性
1.4.2 HTTP协议
(1)作用:规范了浏览器请求数据的方式,规范了数据传输的格式
(2)核心:请求报文和响应报文
(3)特点:
① 应用层协议
②明文传输,安全性较差(网银插件)
③基于TCP协议
④无状态:每次请求服务器,服务器都不认识
1.5 HTTP服务器
1.5.1服务器
服务器就是提供计算服务的设备
在网络环境下,根据服务器提供的服务类型的不同,将服务器分为:文件服务器、数据库服务器、应用程序服务器和WEB服务器
服务器、服务、服务器程序之间的关系:服务器通过服务程序为用户提供服务
1.5.2 url(统一资源定位符)
(1)url作用:用来描述网络上的一个资源
(2)url的格式:schema://host[:port#]/path/…/[?query-string][#anchor]
其中,
schema:使用的协议,http、https… https就是安全的http
host:表示请求服务器的域名或者ip地址
port:端口号,默认是80
path:请求资源的路径
query-string:发送给服务器的数据
anchor:锚
1.5.3浏览器请求服务器
(1)浏览器向服务器发送请求,服务器收到请求后对浏览器做出响应
(2)请求报文
请求报文分为四部分:
①请求行:METHOD /path HTTP/Version-number
其中,
METHOD表示请求方式:
GET:获取数据;
POST:修改数据;
PUT:保存数据;
DELETE:删除;
OPTIONS:询问服务器的某种支持特性;
HEAD:返回报文头
……
Path-to-resource:请求资源的路径
Version-number:HTTP的版本号
②请求头
Accept:浏览器能够接收的数据类型 q=0.9权重 ;号前面的权重
Accept-Encoding:浏览器能够接收数据的压缩格式
Accept-Language:浏览器能够支持的语言
Accept-Charset:浏览器能够接收的字符集类型
User_Agent:用户代理,记录了浏览器和当前操作系统信息
Host:主机地址(注意:请求报文必须要写Host)
Referer:引用,记录用户的来源,帮助我们分析用户来源
If-Modified-Since:Thu, 09 Feb 2018 09:07:57 GMT浏览器缓存的页面的最后修改时间(修改时间来源于服务器)。如果一致,客户端继续使用,服务器返回304 Modified,如果不一致,服务器返回新页面,并返回200 OK
If-None_match: “03f2b33c0bfcc1:0”:If-None_Match与服务器Response中的Etag一起工作,相当于给资源加了一个资源戳。浏览器再请求的时候,带着If-None_Match去服务器,服务器看到请求后,将If_None_match的值取出来跟自己资源的Etag值做比对,如果一致,则返回304 Modified,让浏览器继续使用。如果不一致,则将资源重新返回,并返回200 OK
Cookie:用户识别
connection:keep-alive
短连接:请求----->响应---->断开
优点:不占用服务器资源
缺点:每次请求响应都需要建立连接,断开连接。影响用户体验
长连接:请求、响应过程中,一般不断开连接
优点:不需要频繁重新建立连接、断开连接,传输效率高,用户体验好
缺点:在连接过程中,需要占用服务器的连接资源。
③空行(注意:请求报文不管有没有提交给服务器的数据,都必须加空行)
④请求体
(3)响应报文
响应报文分为四部分:
①响应行
响应行分为三部分:
A.HTTP/version number:版本号
B.Status code 状态码
a) 状态码作用:服务器用来告诉浏览器是否产生了浏览器预期Response
b) 状态码类别:1XX 2XX 3 XX 4XX 5XX
i.1XX:提示信息,表示服务器已经接收到浏览器的请求,继续处理
ii. 2XX:处理成功,表示浏览器的请求已经成功被接收、并正确处理
iii.3XX:重定向,表示需要进行更进一步的处理
iv.4XX客户端请求错误,表示客户端请求出现错误
v. 5XX服务器端错误,服务器未能正确处理客户端请求
c)常见状态码解释
i.200 OK:表示客户端请求被成功接收,并将响应数据发送给客户端
ii. 302Found:重定向,新的URL会在Response中返回,浏览器将会自动向新的
URL发送请求。
iii. 304 Not Modified ,表示信息已经被缓存了,还可以继续使用
iv.403 Forbidden 服务器接收到客户端请求,但拒绝为客户端提供服务
v.404 Not Found 表示客户端请求的资源不存在(url输错了)
vi. 500 Internet Server Error 服务器发生了不可预期的错误
C.Message:状态码对应的状态信息
②响应头
Date:消息生成时间
Content-type:响应数据的类型
Transfer-encoding:分块传输
Last-Modified: Fri, 13 Apr 2018 06:43:31 GMT :服务器记录的文件最后一次修改的时间
Etag:资源戳,与请求报文的if-none_match配合使用
Set-Cookie:将Cookie数据发送到浏览器,并要求浏览器进行记录
Content-Encoding:服务器响应给浏览器的文件的压缩方式
Content-Language:服务器告诉浏览器响应的语言
Server:服务器告诉浏览器当前服务器的信息
Location:重定向的url
③空行
④响应体
1.5.4模拟浏览器向服务器发送请求
(1)流程:
①创建负责连接服务器的socket对象
②调用connect方法连接指定的服务器
③准备请求报文
④发送请求报文
⑤接收服务器的响应报文
⑥关闭socket
(2)代码实现:
import socket
# 1、建立socket连接对象
socket_con = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
# 2、连接指定的服务器地址
socket_con.connect(("www.baidu.com",80))
# 3、拼接请求报文
# 注意:请求报文必须要写host
# 请求行
# GET 必须全部大写
request_line = "GET / HTTP/1.1\r\n"
# 请求头
#Host必须要出现
request_head = "Host: www.baidu.com\r\n"
# 请求报文不管有没有提交给服务器的数据,都必须加空行
request = request_line + request_head +"\r\n"
# 4、发送给服务器
socket_con.send(request.encode())
# 接收服务器的响应报文
response = socket_con.recv(4096).decode()
print(response)
# 5、关闭socket
socket_con.close()
1.5.5静态WEB服务器
(1)返回固定数据的WEB服务器
import socket
def handle_client(socket_con):
"""
接收来自客户端的请求,并接收请求报文,解析,返回
"""
# 1、服务器接收客户端的请求报文
request = socket_con.recv(4096).decode()
# 拼接服务器要返回的响应报文
response_line = "HTTP/1.1 200 OK\r\n"
response_head = "Server: laozhaov1.0\r\n"
response_head += "Content-Type:text/html;charset=utf-8\r\n"
response_body = "窗前明月光,疑是地上霜,举头望明月,我叫郭德纲"
response = response_line + response_head + "\r\n" +response_body
socket_con.send(response.encode())
def main():
# 1、服务器创建负责监听的socket
socket_watch = socket.socket(socket.AF_INET,socket.SOCK_STREAM)
# 2、设置地址重用
socket_watch.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
# 3、绑定监听的端口
socket_watch.bind(("localhost",8888))
# 4、设置监听队列
socket_watch.listen(128)
# 5、通过循环,不停的接收来自客户端的连接请求
while True:
socket_con,con_adds = socket_watch.accept()
# 注意将con_adds转成字符串
print("客户端:%s连接成功!!!" % str(con_adds))
# 接收来自客户端的请求,并接收请求报文,解析,返回
handle_client(socket_con)
if __name__ == '__main__':
main()
(2)返回固定页面的WEB服务器
import socket
def handle_client(socket_con):
"""
接收来自客户端的请求,并接收请求报文,解析,返回
"""
# 1、服务器接收客户端的请求报文
request = socket_con.recv(4096).decode()
# print(request)
# 拼接服务器要返回的响应报文
response_line = "HTTP/1.1 200 OK\r\n"
response_head = "Server: laozhaov1.0\r\n"
response_head += "Content-Type:text/html;charset=utf-8\r\n"
# 读取固定的页面,以响应体的形式拼接响应报文 然后返回给客户端
path ="/Users/zhaojianyu/Desktop/hm_day02/www/index.html"
file = open(path,"r")
response_body = file.read()
# response_body = "窗前明月光,疑是地上霜,举头望明月,我叫郭德纲"
response = response_line + response_head + "\r\n" +response_body
socket_con.send(response.encode())
def main():
# 1、服务器创建负责监听的socket
socket_watch = socket.socket(socket.AF_INET,socket.SOCK_STREAM)
# 2、设置地址重用
socket_watch.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
# 3、绑定监听的端口
socket_watch.bind(("localhost",8888))
# 4、设置监听队列
socket_watch.listen(128)
# 5、通过循环,不停的接收来自客户端的连接请求
while True:
socket_con,con_adds = socket_watch.accept()
# 注意将con_adds转成字符串
print("客户端:%s连接成功!!!" % str(con_adds))
# 接收来自客户端的请求,并接收请求报文,解析,返回
handle_client(socket_con)
if __name__ == '__main__':
main()
(3)返回用户指定页面的WEB服务器
知识点补充:os模块
判断路径是否存在,若存在返回True,否则返回False
os.path.exists("/Users/zhaojianyu/Desktop/hm_day02/www")
判断是否是文件,若是,返回True,否则返回False
os.path.isfile("/Users/zhaojianyu/Desktop/hm_day02/www")
判断是否是目录,若是,返回True,否则返回False
os.path.isdir("/Users/zhaojianyu/Desktop/hm_day02/www")
以列表的形式显示目录下的文件名和文件夹名
os.listdir("/Users/zhaojianyu/Desktop/hm_day02/www")
sys模块
获取用户在命令行中输入的参数 返回一个参数的列表
print(sys.argv)
print(sys.argv[1])
代码实现:
import socket
import re
import os
def handle_client(socket_con):
"""
接收来自客户端的请求,并接收请求报文,解析,返回
"""
# 1、服务器接收客户端的请求报文
request = socket_con.recv(4096).decode()
# 2、截取请求报文,获取请求行
request_lines = request.split("\r\n")
# 3、获取请求行
request_line = request_lines[0]
# GET /a/ab/c.html HTTP/1.1
# 通过正则表达式 匹配出请求行中请求资源路径
res = re.match(r"\w+\s+(\S+)",request_line)
# 获取资源路径
path = res.group(1)
# 将资源路径和我的web文件夹的绝对路径拼接
path ="/Users/zhaojianyu/Desktop/hm_day02/www" + path
# 在判断是文件还是文件夹之前,首先要判断你这个路径在服务器中是否存在
if not os.path.exists(path):
response_line = "HTTP/1.1 404 Not Found\r\n"
response_head = "Server:laowangv.1.3\r\n"
response_head += "Content-type:text/html;charset=utf-8\r\n"
response_body = "你请求"+ path +"不存在"
response = response_line + response_head + "\r\n" +response_body
socket_con.send(response.encode())
socket_con.close()
return
else:
# 判断用户请求的是文件还是文件夹
if os.path.isfile(path):
# 如果文件存在 读取页面数据,然后返回
response_line = "HTTP/1.1 200 OK\r\n"
response_head = "Server:laozhao\r\n"
# 注意请求图片需要使用"rb"的方式进行读取
file = open(path,"rb")
# response_body 是二进制
response_body = file.read()
response = response_line.encode() + response_head.encode() +"\r\n".encode() +response_body
socket_con.send(response)
socket_con.close()
return
else:
if path.endswith("/"):
# www.baidu.com/images
# 用户请求的文件夹
# 1、判断该文件夹下是否有默认的文件,如果有,则返回,如果没有
# index.html default.html
default_document = False
# 如果允许你访问我目录下的默认文档
if default_document:
# 判断用户访问的文件夹下是否有index.html 或者 default.html
if os.path.exists(path + "/index.html"):
response_line = "HTTP/1.1 200 OK\r\n"
response_head = "Server:laowang\r\n"
file = open(path+"/index.html","rb")
response_body = file.read()
response = response_line.encode() + response_head.encode() +"\r\n".encode()+response_body
socket_con.send(response)
socket_con.close()
return
elif os.path.exists(path + "/default.html"):
response_line = "HTTP/1.1 200 OK\r\n"
response_head = "Server:laowang\r\n"
file = open(path + "/default.html", "rb")
response_body = file.read()
response = response_line.encode() + response_head.encode() + "\r\n".encode() + response_body
socket_con.send(response)
socket_con.close()
return
else:
# 访问的目录下,既没有index.html 也没有default.html
response_line = "HTTP/1.1 404 Not Found\r\n"
response_head = "Server:laowang\r\n"
response_body = "index.html or default.html is not exist!!!"
response = response_line +response_head +"\r\n" +response_body
socket_con.send(response.encode())
socket_con.close()
# 2、判断服务器是否开启了目录浏览
else:
# 判断你是否开启了目录浏览
dir_browsing = True
if dir_browsing:
# 把用户请求的文件夹中所有的文件和文件夹以目录的形式返回到页面中
# 获取用户请求的文件夹
list_names = os.listdir(path)
response_line = "HTTP/1.1 200 OK\r\n"
response_head = "Server:laozhao\r\n"
# 动态的拼接页面,将目录中的文件或者文件夹的名称以HTML页面的方式返回给浏览器
response_body = "<html><head><body><ul>"
for item in list_names:
response_body +="<li><a href = '#'>"+item+"</a></li>"
response_body+="</ul></body></head></html>"
response =response_line + response_head +"\r\n" +response_body
socket_con.send(response.encode())
socket_con.close()
return
else:
# 用户请求的路径没有斜线
# 重定向到+斜线的目录下
response_line = "HTTP/1.1 302 Found\r\n"
response_head = "Server:laozhao\r\n"
response_body = "redirect "+ path +"/"
response = response_line +response_head +"\r\n" +response_body
socket_con.send(response.encode())
socket_con.close()
def main():
# 1、服务器创建负责监听的socket
socket_watch = socket.socket(socket.AF_INET,socket.SOCK_STREAM)
# 2、设置地址重用
socket_watch.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
# 3、绑定监听的端口
socket_watch.bind(("localhost",8888))
# 4、设置监听队列
socket_watch.listen(128)
# 5、通过循环,不停的接收来自客户端的连接请求
while True:
socket_con,con_adds = socket_watch.accept()
# 注意将con_adds转成字符串
print("客户端:%s连接成功!!!" % str(con_adds))
# 接收来自客户端的请求,并接收请求报文,解析,返回
handle_client(socket_con)
if __name__ == '__main__':
main()