模拟浏览器实现,服务端基础框架搭建

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())

    注意:请求报文默认是字符串,必须转二进制

  • 接收服务器返回的数据

  • 处理(截取)

    1. 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服务-基础入门

flink模拟项目:实时热门商品统计

手写网站服务器~用Python手动实现一个简单的服务器,不借助任何框架在浏览器中输出任意内容

如何搭建完备实用的基础架构与中间件体系?

Django基础一之web框架的本质