模拟浏览器实现,服务端基础框架搭建
Posted 黑马程序员官方
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了模拟浏览器实现,服务端基础框架搭建相关的知识,希望对你有一定的参考价值。
Python 进阶篇-系列文章全篇
🍑 Linux操作系统与常用命令
🍑 Linux系统性能定时监控升级,源码可复制
🍑 tcp网络程序的实现步骤,看这个就够了
🍑 http请求与响应,tcp3次握手&四次挥手
🍑 Python进阶:利用线程实现多任务
🍑 Python进阶:进程的状态及基本操作
🍑 Python进阶:一文搞懂迭代器、生成器、协程(附案例)
🍑 数据库:了解MySQL数据类型、SQL命令
🍑 从零开始学SQL:where条件查询与连接
🍑 Python网络开发基础,实现udp聊天器小案例
🍑 多任务版TCP服务端程序开发案例+源码
🍑 【图文教程详解】Ubuntu的两种安装方式
🍑 Python中的深拷贝与浅拷贝
希望本阶段内容帮助大家从Python基础到进阶学习,详情可以关注上方专栏 ↑ ↑ ↑
1. [重点]案例-模拟浏览器实现
-
导入模块
-
创建套接字
-
建立连接
tcp_client_socket.connect((“www.icoderi.com”,80))
web服务器默认是 80端口
-
拼接请求报文
request_line = "GET / HTTP/1.1\\r\\n" # 4.2 请求头 request_header = "Host:www.icoderi.com\\r\\n" # 4.3 请求空行 request_blank = "\\r\\n" # 整体拼接 request_data = request_line + request_header + request_blank
get方式没有请求的主体
-
发送请求报文
tcp_client_socket.send(request_data.encode())
注意:请求报文默认是字符串,必须转二进制
-
接收服务器返回的数据
-
处理(截取)
- find 进行查找 \\r\\n\\r\\n
2)字符串的切片 截取
loc = recv_text.find("\\r\\n\\r\\n") # 7.2 截取字符串 html_data = recv_text[loc+4:]
-
保存
with open(“index.html”, “w”) as file:
file.write(html_data) -
关闭连接
2. [重点]返回固定数据
-
导入模块
-
创建套接字
-
设置地址可以重用
# 当前套接字 地址重用 值True tcp_server_socket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, True)
-
绑定端口和IP
-
设置套接字为被动
-
开始接受客户端连接
while True: new_client_socket, ip_port = tcp_server_socket.accept() # 调用功能函数处理请求并且响应 request_handler(new_client_socket, ip_port)
-
接受客户端发送的请求协议
def request_handler(new_client_socket,ip_port)
request_data = new_client_socket.recv(1024)
-
判断客户端是否已经下线
if not request_data: print("%s客户端已经下线!" % str(ip_port)) new_client_socket.close() return
-
拼接响应报文
-
响应行
-
响应头
-
响应空行
-
响应主体
# 9.1 响应行 response_line = "HTTP/1.1 200 OK\\r\\n" # 9.2 响应头 response_header = "Server:Python20WS/2.1\\r\\n" # 9.3 响应空行 response_blank = "\\r\\n" # 9.4 响应的主体 response_body = "HelloWorld!" response_data = response_line + response_header + response_blank + response_body
-
-
发送响应报文
new_client_socket.send(response_data.encode())
发送之前,需要对响应报文编码
-
关闭和当前客户端的连接
3. [重点]返回固定页面
-
改进 返回固定内容 的代码
- 第一步,拷贝 static 到项目目录下
- 改进代码如下:
# response_body = "HelloWorld!" # ************ 返回固定页面 ********** # 通过 with open 读取文件 with open("static/index.html", "rb") as file: # 把读取的文件内容返回给客户端 response_body = file.read()
-
- 第三步
response_data = (response_line + response_header + response_blank).encode() + response_body # 10、发送响应报文 new_client_socket.send(response_data)
4. [重点]返回指定页面
-
核心思路,获取请求的资源路径
-
解码请求的协议
# 1)把请求协议解码,得到请求报文的字符串 request_text = request_data.decode()
-
得到请求行
# 2)得到请求行 # (1) 查找 第一个\\r\\n 出现的位置 loc = request_text.find("\\r\\n") # (2) 截取字符串,从开头截取到 第一个\\r\\n 出现的位置 request_line = request_text[:loc]
-
拆分请求行,得到请求的资源路径
request_line_list = request_line.split(" ") # print(request_line_list) # 得到请求的资源路径 file_path = request_line_list[1] print("[%s]正在请求:%s" % (str(ip_port), file_path))
-
打开指定的文件
with open("static"+file_path, "rb") as file: # 把读取的文件内容返回给客户端 response_body = file.read()
-
5. [重点]返回指定页面存在的问题
-
访问的页面不存在
解决方案:对打开文件的代码,做异常捕获
try: # 通过 with open 读取文件 with open("static"+file_path, "rb") as file: # 把读取的文件内容返回给客户端 response_body = file.read() except Exception as e: # 1)重新修改响应行 为 404 response_line = "HTTP/1.1 404 Not Found\\r\\n" # 2)响应的内容为错误 response_body = "Error! (%s)" % str(e) # 3)把内容转换为字节码 response_body = response_body.encode()
-
默认首页,直接访问 地址
192.168.150.31:8080
不加路径报错解决思路,如果如果直接访问,请求行中可以得到 / 以此作为判断条件
# 设置默认首页 if file_path == "/": file_path = "/index.html"
6. [重点]面向对象封装
- 新建类 WebServer
- 创建对象方法
__init__()
、start() - 修改代码
- 把套接字初始化的操作,放到
__init__()
中 - 把接受客户端连接的代码 放到
start()
方法中 - 把 request_handler() 函数,变成 对象方法(选中缩进)
- 在 main() 函数中创建 对象 ws = WebServer() 然后启动 ws.start()
- 把套接字初始化的操作,放到
7. [重点]服务端基础框架构建-1
- 创建 application 文件夹
- 在 application文件夹中 创建 app.py
- app.py application函数
- 修改 request_handler() 把核心代码放到 app模块 application函数中
- app.py 模块中,创建 parse_request() 专门用于解析请求的资源路径
8. [重点]服务端基础框架构建-2
- 在application 文件夹中创建一个utils 模块
- utils 模块中创建 create_http_response() 函数,专门用来拼接响应报文
- 修改 app模块的application的返回响应协议的部分
9. [重点]给服务器加上命令行参数
-
导入模块 sys
-
sys.argv 获取系统传递给程序的参数,并且判断个数是否正确
# 3、判断参数格式是否正确 if len(sys.argv) != 2: print("启动失败,参数格式错误!正确格式:python3 xxx.py 端口号") return
-
判断端口号是否是纯数字
if not sys.argv[1].isdigit(): print("启动失败,端口号不是一个纯数字!") return
-
保存端口号
port = int(sys.argv[1])
-
启动服务器的时候指定端口号
ws = WebServer(port)
…
# 初始化方法 def __init__(self, port): # 1、导入模块 # 2、创建套接字 tcp_server_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM) # 3、设置地址重用 tcp_server_socket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, True) # 4、绑定端口 tcp_server_socket.bind(("", port)) # 5、设置监听,让套接字由主动变为被动接收 tcp_server_socket.listen(128) # 定义实例属性,保存套接字对象 self.tcp_server_socket = tcp_server_socket
[重点]案例:网游服务器
-
拷贝游戏资源到 项目目录中
-
在 webserver 初始化方法中,配置一个字典
# 定义类的实例属性, projects_dict 初始化为空 self.projects_dict = dict() # 定义实例属性,保存要发布的项目的路径 self.current_dir = "" # key value self.projects_dict['植物大战僵尸-普通版'] = "zwdzjs-v1" self.projects_dict['植物大战僵尸-外挂版'] = "zwdzjs-v2" self.projects_dict['保卫萝卜'] = "tafang" self.projects_dict['2048'] = "2048" self.projects_dict['读心术'] = "dxs"
-
初始化项目的方法
# 添加一个初始化项目的方法 def init_porjects(self): # 2.1 显示所有可以发布的游戏菜单 # list(self.projects_dict.keys()) 取出字典的key 并且转换为列表 keys_list = list(self.projects_dict.keys()) # 遍历显示所有的key # enumerate(keys_list) # [(0,'植物大战僵尸v1'), (1, '植物大战僵尸v2') ...] for index, game_name in enumerate(keys_list): print("%d.%s" % (index,game_name)) # 2.2 接收用户的选择 sel_no = int(input("请选择要发布的游戏序号:\\n")) # 2.3 根据用户的选择发布指定的项目 (保存用户选择的游戏对应的本地目录) # 根据用户的选择,得到游戏的名称(字典的key) key = keys_list[sel_no] # 根据字典的key 得到项目的具体路径 self.current_dir = self.projects_dict[key]
注意,该方法在 WebServer的 初始化方法中调用
-
修改 request_handler() 代码
# 使用 application文件夹 app 模块的 application() 函数处理 response_data = app.application(self.current_dir, request_data, ip_port)
-
设置浏览器优先的解析方式
修改 utils模块的 create_http_response() 函数
response_header += “Content-Type: text/html\\r\\n”
以上是关于模拟浏览器实现,服务端基础框架搭建的主要内容,如果未能解决你的问题,请参考以下文章
搭建简单Django服务并通过HttpRequester实现GET/POST请求提交表单
json-server30秒无代码搭建一个完整的REST API服务-基础入门