HTTP协议和静态Web服务器
3.4.1 HTTP协议
1.HTTP协议介绍
全程:超文本传输协议作用:规定浏览器和web服务器通信的数据格式HTTP协议是在应用层的协议,基于传输层的TCP传输协议,设计之初是传输网页数据,现在可以传输任何数据。
浏览器访问web服务器的通信过程
2.URL
1.url概念
意思是统一资源定位符,通俗理解就是网络资源地址
2.组成
协议部分:http://, https://, ftp://, sftp://, smb://等域名部分:资源路径部分:/res/xxxxx191xxx查询参数部分: ?page=1&count=203.4.2 查看HTTP协议的通信过程
1.带开发者工具模式的浏览器
一般有火狐谷歌,这里我们使用谷歌浏览器来查看HTTP协议的通信过程
开发者工具的标签选项说明:
元素(Elements):用于查看或修改HTML标签控制台(Console):执行js代码源代码(Sources):查看静态资源文件,断点调试JS代码网络(Network):查看http协议的通信过程
开发者工具的使用说明:
点击Network标签选项在浏览器的地址栏输入百度的网址,就能看到请求百度首页的http的通信过程这里的每项记录都是请求+响应的一次过程如果返回的是个html文件那大概率是不止要一次请求,在执行代码的过程中会慢慢要多次请求
2.查看HTTP协议通信的过程
查看HTTP请求信息图
查看HTTP响应信息的图
3.HTTP请求报文
HTTP最常见的请求报文有2种:
GET方式:获取web服务器数据POST方式:向web服务器提交数据
1. HTTP GET请求报文分析
组成
请求行:GET / HTTP/1.1\r\n
请求方式:GET请求资源路径:/协议版本:HTTP/1.1\r\n:回车换行
请求头 格式(属性名:属性值\r\n)
Host:主机地址和端口 默认是80
Connection:keep-alive 保持长连接
拓展
长连接和短连接各有优势
长连接操作间隔的速度快,但是需要大量系统资源
短连接速度慢,但是需求的系统资源少
Upgrade-Insecure-Requests: 1 # 让浏览器升级不安全请求,使用https请求
User-Agent:浏览器访问标志,不同浏览器和平台有不同的标志
Accept:可接受的文件格式
Accept-Encoding:可接受的压缩格式
Accept-language:可接受的语言
Cookie:登录用户的身份标志
空行 \r\n
2. HTTP POST请求报文分析
组成
请求行: 请求方式:POST请求资源路径:一般是需要登录或者输入查询的地方协议版本:\r\n:回车换行 请求头: 属性名:属性值\r\n空行:\r\n请求体:需要提交给web服务器的数据 (不用带\r\n)
注意
请求体可以为空,但是一般为空我们就用GET
4.HTTP响应报文
1.响应报文组成
响应行 :HTTP/1.1 200 OK \r\n 协议版本:HTTP/1.1 (和请求报文区别,先报协议版本后出状态)状态码:200状态描述:0k回车换行:\r\n 响应头 属性名:属性值 \r\n Server: 服务器名称Content-Type:text/html; charset=utf-8 内容类型Transfer-Encoding:chunked # 发送给客户端内容不确定内容长度,发送结束的标记是0\r\n, Content-Length表示服务端确定发送给客户端的内容大小,但是二者只能用其一。Connection:keep-alive 和客户端保持长连接Date:Fri, 23 Nov 02:01:05 GMT 服务器的响应时间 空行响应体 响应给客户端的数据2.响应状态码
3.4.3 搭建Python自带的静态Web服务器
1.静态Web服务器
可以为发出请求的浏览器提供静态文档的程序
一般为动态的服务器,我们做的是数据不变化的静态资源
2.搭建Web服务器
在已配置的static文件夹里打开终端,利用Python自带的静态web服务器发起
python3 -m http.server 9000[端口号 可选]Serving HTTP on 0.0.0.0 port 9000 (http://0.0.0.0:9000/) ...192.168.19.69 - - [22/Nov/ 08:41:55] "GET / HTTP/1.1" 200 -192.168.19.69 - - [22/Nov/ 08:41:55] "GET /favicon.ico HTTP/1.1" 200 -192.168.19.69 - - [22/Nov/ 08:42:32] "GET /index.html HTTP/1.1" 200 -192.168.19.69 - - [22/Nov/ 08:42:38] "GET /index2.html HTTP/1.1" 200 -192.168.19.69 - - [22/Nov/ 08:42:38] "GET /web.jpg HTTP/1.1" 200 -192.168.19.69 - - [22/Nov/ 08:42:51] "GET /grand.html HTTP/1.1" 200 -192.168.19.69 - - [22/Nov/ 08:42:51] "GET /images/001.jpg HTTP/1.1" 200 -192.168.19.69 - - [22/Nov/ 08:42:51] "GET /js/jquery-1.12.4.min.js HTTP/1.1" 200 -192.168.19.69 - - [22/Nov/ 08:42:51] "GET /images/002.jpg HTTP/1.1" 200 -192.168.19.69 - - [22/Nov/ 08:42:51] "GET /images/003.jpg HTTP/1.1" 200 -192.168.19.69 - - [22/Nov/ 08:42:51] "GET /images/004.jpg HTTP/1.1" 200 -192.168.19.69 - - [22/Nov/ 08:42:51] "GET /images/005.jpg HTTP/1.1" 200 -192.168.19.69 - - [22/Nov/ 08:43:03] "GET /grand.html HTTP/1.1" 200 -192.168.19.69 - - [22/Nov/ 08:43:03] "GET /favicon.ico HTTP/1.1" 200
3.开发一个返回固定页面的静态web服务器
实现步骤:
编写一个TCP服务端程序获取浏览器发送的http请求报文数据读取固定页面数据,把页面数据组装成HTTP响应报文数据发送给浏览器。HTTP响应报文数据发送完成以后,关闭服务于客户端的套接字。
import socket# HTTP是基于TCP传输协议的服务器,我们从昨天的TCP服务器端创建开始if __name__ == '__main__':tcp_server_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)tcp_server_socket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, True)tcp_server_socket.bind(('', 9222))tcp_server_socket.listen(128)# 以上是搭建了一个本地tcp服务器端,一旦有用户创立连接我们再做下一步new_socket, ip_port = tcp_server_socket.accept()while True:# 我们先读取一下浏览器发过来的请求报头但是不做解析,默认读取是4k字节client_data = new_socket.recv(4096)# 解码一下并且打出请求报头client_content = client_data.decode('utf-8')print(client_content)# 我们读取一下配置好的文档,来自定义一个静态文档,然后按照HTTP协议的响应报文格式返回# 自定义一个静态文档内容读取with open('static/index.html', 'rb')as f:ret = f.read()# 定义一个响应行response_line = 'HTTP/1.1 200 OK\r\n'# 定义一个响应头response_header = 'Server: PWS1.0\r\n'# 定义一个响应体 也就是我们写好的网页文件response_body = ret# 把所有响应格式,按照HTTP协议的格式打包发送response_data = (response_line+response_header+'\r\n').encode('utf-8')+response_bodynew_socket.send(response_data)new_socket.close()# 我们结束通讯是因为我们设计的服务器十分简陋,建立tcp连接的时候也没有具体互相告诉对面即将发送的数据大小,数据头尾,实际上浏览器是不知道我们具体发多少的,如果没有我们关闭连接,浏览器会处于长时间接受响应报文的状态
我们尝试访问一下本地页面
GET /favicon.ico HTTP/1.1Host: 127.0.0.1:9222Connection: keep-alivePragma: no-cacheCache-Control: no-cacheUser-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/86.0.4240.193 Safari/537.36 Edg/86.0.622.68Accept: image/webp,image/apng,image/*,*/*;q=0.8Sec-Fetch-Site: same-originSec-Fetch-Mode: no-corsSec-Fetch-Dest: imageReferer: http://127.0.0.1:9222/Accept-Encoding: gzip, deflate, brAccept-Language: zh-CN,zh;q=0.9,en;q=0.8,en-GB;q=0.7,en-US;q=0.6,zh-TW;q=0.5
可以看到终端完整的打出了
4.开发一个返回指定页面数据的静态web服务器
实现步骤:
编写一个TCP服务端程序获取浏览器发送的http请求报文数据读取固定页面数据,把页面数据组装成HTTP响应报文数据发送给浏览器。HTTP响应报文数据发送完成以后,关闭服务于客户端的套接字。
import socket# 之前我们只能返回一个指定页面,而且对用户发过来的请求报头不解析# HTTP是基于TCP传输协议的服务器,我们从昨天的TCP服务器端创建开始def main():tcp_server_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)tcp_server_socket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, True)tcp_server_socket.bind(('', 9222))tcp_server_socket.listen(128)# 以上是搭建了一个本地tcp服务器端,一旦有用户创立连接我们再做下一步while True:new_socket, ip_port = tcp_server_socket.accept()client_request_data = new_socket.recv(4096)if not client_request_data:print("用户关闭了浏览器")new_socket.close()return# 解码一下并且打出请求报头client_content = client_request_data.decode('utf-8')print(client_content)# 限定指定字符串进行分割,最大分割次数2request_list = client_content.split(" ", maxsplit=2)# 取出访问路径request_path = request_list[1]print(request_path)# 如果访问路径为/则为首页if request_path == "/":request_path = '/index.html'# 对截取的用户资源路径进行判断,进行读取,如果发生错误则返回错误的页面try:with open("static"+request_path, "rb")as f:file_data = f.read()except Exception as e:print("用户访问出错", e)with open("static/error.html", 'rb')as ef:file_data = ef.read()# 定义一个错误的响应行response_line = 'HTTP/1.1 404 Not Found\r\n'# 定义一个响应头response_header = 'Server: PWS1.0\r\n'# 定义一个响应体 也就是我们写好的网页文件response_body = file_dataresponse_data = (response_line + response_header + '\r\n').encode('utf-8') + response_bodynew_socket.send(response_data)# 如果没有发生错误,则正确读取else:# 定义一个响应行response_line = 'HTTP/1.1 200 OK\r\n'# 定义一个响应头response_header = 'Server: PWS1.0\r\n'# 定义一个响应体 也就是我们写好的网页文件response_body = file_data# 把所有响应格式,按照HTTP协议的格式打包发送response_data = (response_line + response_header + '\r\n').encode('utf-8') + response_bodynew_socket.send(response_data)finally:new_socket.close()if __name__ == '__main__':main()
5.开发一个多任务和函数封装版的静态web服务器
流程步骤
给每个连接创建一个子进程,由子进程处理连接客户端的请求把创建的子客户端设置成守护进程
import socketimport threading# 之前我们只能返回一个指定页面,而且对用户发过来的请求报头不解析# HTTP是基于TCP传输协议的服务器,我们从昨天的TCP服务器端创建开始def main():tcp_server_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)tcp_server_socket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, True)tcp_server_socket.bind(('', 9222))tcp_server_socket.listen(128)# 以上是搭建了一个本地tcp服务器端,一旦有用户创立连接我们再做下一步while True:new_socket, ip_port = tcp_server_socket.accept()new_thread = threading.Thread(target=client_request, args=(new_socket, ip_port))new_thread.setDaemon(True)new_thread.start()def client_request(new_socket, ip_port):print("有一位新用户接入,他的ip地址为", ip_port)client_request_data = new_socket.recv(4096)if not client_request_data:print("用户关闭了浏览器")new_socket.close()return# 解码一下并且打出请求报头client_content = client_request_data.decode('utf-8')# print(client_content)# 限定指定字符串进行分割,最大分割次数2request_list = client_content.split(" ", maxsplit=2)# 取出访问路径request_path = request_list[1]print("该用户访问的路径为", request_path)# 如果访问路径为/则为首页if request_path == "/":request_path = '/index.html'# 对截取的用户资源路径进行判断,进行读取,如果发生错误则返回错误的页面try:with open("static" + request_path, "rb")as f:file_data = f.read()except Exception as e:print("用户访问出错", e)with open("static/error.html", 'rb')as ef:file_data = ef.read()# 定义一个错误的响应行response_line = 'HTTP/1.1 404 Not Found\r\n'# 定义一个响应头response_header = 'Server: PWS1.0\r\n'# 定义一个响应体 也就是我们写好的网页文件response_body = file_dataresponse_data = (response_line + response_header + '\r\n').encode('utf-8') + response_bodynew_socket.send(response_data)# 如果没有发生错误,则正确读取else:# 定义一个响应行response_line = 'HTTP/1.1 200 OK\r\n'# 定义一个响应头response_header = 'Server: PWS1.0\r\n'# 定义一个响应体 也就是我们写好的网页文件response_body = file_data# 把所有响应格式,按照HTTP协议的格式打包发送response_data = (response_line + response_header + '\r\n').encode('utf-8') + response_bodynew_socket.send(response_data)finally:new_socket.close()if __name__ == '__main__':main()
6.做一个面向对象版的封装静态web服务器
实现步骤:
把提供服务的Web服务器抽象成一个类(HTTPWebServer)提供Web服务器的初始化方法,在初始化方法里面创建socket对象提供一个开启Web服务器的方法,让Web服务器处理客户端请求操作。
import socketimport threading# 先分析需求,我们需要创立的是一个服务器类# 首先我们服务器需要的属性有几样 1.socket 2.端口绑定 3.监听数class WebHttpServer(object):def __init__(self):# 实例属性最需要的就是服务器嵌套的socketself.web_socket = socket.socket(socket.AF_INET,socket.SOCK_STREAM)# 同时需要设定它的端口复用self.web_socket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, True)self.web_socket.bind(("", 9222))self.web_socket.listen(128)# 服务器属性创立好后还需要能处理客户端的需求,可以直接照搬我们前面做的函数# 但是可以看到如果作为实例方法我们需要传参实例,但是我们并不需要传任何实例对象和属性进去,因为不需求这个方法返回任何值,因此我们将其做成静态方法@staticmethoddef client_request(new_socket, ip_port):print("有一位新用户接入,他的ip地址为", ip_port)client_request_data = new_socket.recv(4096)if not client_request_data:print("用户关闭了浏览器")new_socket.close()return# 解码一下并且打出请求报头client_content = client_request_data.decode('utf-8')# print(client_content)# 限定指定字符串进行分割,最大分割次数2request_list = client_content.split(" ", maxsplit=2)# 取出访问路径request_path = request_list[1]print("该用户访问的路径为", request_path)# 如果访问路径为/则为首页if request_path == "/":request_path = '/index.html'# 对截取的用户资源路径进行判断,进行读取,如果发生错误则返回错误的页面try:with open("static" + request_path, "rb")as f:file_data = f.read()except Exception as e:print("用户访问出错", e)with open("static/error.html", 'rb')as ef:file_data = ef.read()# 定义一个错误的响应行response_line = 'HTTP/1.1 404 Not Found\r\n'# 定义一个响应头response_header = 'Server: PWS1.0\r\n'# 定义一个响应体 也就是我们写好的网页文件response_body = file_dataresponse_data = (response_line + response_header + '\r\n').encode('utf-8') + response_bodynew_socket.send(response_data)# 如果没有发生错误,则正确读取else:# 定义一个响应行response_line = 'HTTP/1.1 200 OK\r\n'# 定义一个响应头response_header = 'Server: PWS1.0\r\n'# 定义一个响应体 也就是我们写好的网页文件response_body = file_data# 把所有响应格式,按照HTTP协议的格式打包发送response_data = (response_line + response_header + '\r\n').encode('utf-8') + response_bodynew_socket.send(response_data)finally:new_socket.close()# 既然已经有了属性和处理客户端请求的方法,那我们必须还有个方法,就是启动实例,这里把之前启动的多线程方法加进去def start(self):while True:new_socket, ip_port = self.web_socket.accept()# 创建线程使用的是静态方法new_thread = threading.Thread(target=self.client_request, args=(new_socket, ip_port))new_thread.setDaemon(True)new_thread.start()def main():web_server = WebHttpServer()web_server.start()if __name__ == '__main__':main()
7.用命令行启动并且动态绑定端口的静态web服务器
我们就算封装了服务器,但是实际上也只能实例化一个对象并且端口是事先设定的,我们为了通用化,一般是使用命令行操作运行,或者图形化的界面启动,明显我们之前封装的类还得更改一下。