Python HTTP项目实战丨【生长吧!Python】

举报
ruochen 发表于 2021/07/14 22:14:08 2021/07/14
【摘要】 Python HTTP项目实战

推荐书籍

  • 日本人写的 “图解Http"
  • 图解系列严重推荐

HTTP项目实战

  • 深入理解HTTP协议
  • 模拟后台服务程序基本流程和大致框架
  • 每一个步骤一个文件夹
  • 图解http协议, 图解tcp/ip协议

v01-验证技术

  • 验证socket-tcp技术,看能否走通流程
  • 使用浏览器发送消息,访问地址
    import socket
    
    # 理解两个参数的含义
    # 理解创建一个socket的过程
    sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
    
    # 注意addr的格式是tuple
    # 以及tuple两个元素的含义
    sock.bind(("127.0.0.1", 7852))
    print("已经绑定端口........")
    # 监听
    sock.listen()
    print("正在监听......")
    
    # 接受一个传进来的socket
    print("准备接受socket传入....")
    skt, addr = sock.accept()
    print("已经接收到传入socket: {0}".format(skt))
    
    # 读取传入消息,实际上是信息
    # 需要注意读取的信息的长度一定要小于等于实际消息的长度,否则会假死
    msg = skt.recv(100)
    print(type(msg))
    
    # decode默认utf-8
    print(msg.decode())
    
    
    # 给对方一个反馈
    msg = "I love only wangxiaojing"
    skt.send(msg.encode())
    
    skt.close()
    sock.close()
    
    
    

V02-解析传入http协议

  • 根据http协议格式,逐行读取信息
  • 按行读取后的信息,需要进行拆解
    import socket
    
    
    
    def getHttpHeader(skt):
        '''
        得到传入socket的http请求头
        :param skt: 通信的socket
        :return: 解析后的请求头内容,字典形式
        '''
    
        # 读取某一行
        # 直到读取的行返回空行为止
    
        # 用来存放结果,dict类型
        rst = {}
    
        line = getLine(skt)
        while line:
            '''
            判断得到的行是报头还是首部行,两个操作方法不一样
            算法是:
            1. 利用‘: ’作为分隔符,分割字符串
            2. 如果是首部行,则一定会把字符串分成两个子串
            3. 否则就是一个字符串
            '''
            r = line.split(r': ')
    
            if len(r) == 2:
                rst[r[0]] = r[1]
            else:
                r = line.split(r' ')
                rst['method'] = r[0]
                rst['uri'] = r[1]
                rst['version'] = r[2]
    
            line = getLine(skt)
    
        return rst
    
    
    
    
    def getLine(skt):
        '''
        从socket中读取某一行
        :param skt: ocket
        :return:  返回读取到的一行str格式内容
        '''
        '''
        前提:
        1. http协议传输内容是ascii编码
        2. 真正传输的内容是通过网络流传输
        3. 回车换行: b'\r', b'\n', b表示是一个bytes格式
        '''
        # 每次从socket读取一个byte内容
        b1 = skt.recv(1)
        b2 = 0
        #data用来存放读取的行的内容
        data = b''
    
        #当确定还没有读到一行最后,也就是回车换行符号的时候,需要循环
        while b2 != b'\r' and b1 != b'\n':
            b2 = b1
            b1 = skt.recv(1)
    
            data += bytes(b2)
    
        # decode 需要一个参数,即编码,但是不给的话就采用默认utf-8解码
        return data.strip(b'\r').decode()
    
    
    # 理解两个参数的含义
    # 理解创建一个socket的过程
    sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
    
    # 注意addr的格式是tuple
    # 以及tuple两个元素的含义
    sock.bind(("127.0.0.1", 7852))
    print("已经绑定端口........")
    # 监听
    sock.listen()
    print("正在监听......")
    
    # 接受一个传进来的socket
    print("准备接受socket传入....")
    skt, addr = sock.accept()
    print("已经接收到传入socket: {0}".format(skt))
    
    # 实际处理请求内容
    http_info = getHttpHeader(skt)
    print(http_info)
    
    
    
    
    # 给对方一个反馈
    msg = "I love only wangxiaojing"
    skt.send(msg.encode())
    
    skt.close()
    sock.close()
    
    
    

v03-http协议封装返回内容

  • 返回头: “HTTP/1.1 200 OK\r\n”
  • 首部行:
    • “Content-Length: xxx\r\n”
    • “Date: 20180616\r\n”
  • 空行:
    • “\r\n”
  • 返回内容:
    • “I love beijign tulingxueyuan”
  • 例子v03
    import socket
    
    
    
    def getHttpHeader(skt):
        '''
        得到传入socket的http请求头
        :param skt: 通信的socket
        :return: 解析后的请求头内容,字典形式
        '''
    
        # 读取某一行
        # 直到读取的行返回空行为止
    
        # 用来存放结果,dict类型
        rst = {}
    
        line = getLine(skt)
        while line:
            '''
            判断得到的行是报头还是首部行,两个操作方法不一样
            算法是:
            1. 利用‘: ’作为分隔符,分割字符串
            2. 如果是首部行,则一定会把字符串分成两个子串
            3. 否则就是一个字符串
            '''
            r = line.split(r': ')
    
            if len(r) == 2:
                rst[r[0]] = r[1]
            else:
                r = line.split(r' ')
                rst['method'] = r[0]
                rst['uri'] = r[1]
                rst['version'] = r[2]
    
            line = getLine(skt)
    
        return rst
    
    
    
    
    def getLine(skt):
        '''
        从socket中读取某一行
        :param skt: ocket
        :return:  返回读取到的一行str格式内容
        '''
        '''
        前提:
        1. http协议传输内容是ascii编码
        2. 真正传输的内容是通过网络流传输
        3. 回车换行: b'\r', b'\n', b表示是一个bytes格式
        '''
        # 每次从socket读取一个byte内容
        b1 = skt.recv(1)
        b2 = 0
        #data用来存放读取的行的内容
        data = b''
    
        #当确定还没有读到一行最后,也就是回车换行符号的时候,需要循环
        while b2 != b'\r' and b1 != b'\n':
            b2 = b1
            b1 = skt.recv(1)
    
            data += bytes(b2)
    
        # decode 需要一个参数,即编码,但是不给的话就采用默认utf-8解码
        return data.strip(b'\r').decode()
    
    
    
    def sendRsp(skt, content):
        '''
        发送返回值,利用传入的socket
        :param skt: 通信的socket
        :return:
        '''
    
        # 构建返回头
        rsp_1 = "HTTP/1.1 200 OK\r\n"
        rsp_2 = "Date: 20180616\r\n"
        # 求返回内容的长度
        len_value= len(content)
        rsp_3 = "Content-Length: {0}\r\n".format(len_value)
        rsp_4 = "\r\n"
        rsp_content = content
        # rsp代表返回的全部数据信息,里面包含http协议本身的内容
        rsp = rsp_1 + rsp_2 + rsp_3 + rsp_4 + rsp_content
    
        skt.send(rsp.encode())
    
    
    
    # 理解两个参数的含义
    # 理解创建一个socket的过程
    sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
    
    # 注意addr的格式是tuple
    # 以及tuple两个元素的含义
    sock.bind(("127.0.0.1", 7852))
    print("已经绑定端口........")
    # 监听
    sock.listen()
    print("正在监听......")
    
    # 接受一个传进来的socket
    print("准备接受socket传入....")
    skt, addr = sock.accept()
    print("已经接收到传入socket: {0}".format(skt))
    
    # 实际处理请求内容
    http_info = getHttpHeader(skt)
    print(http_info)
    
    
    
    
    # 给对方一个反馈
    msg = "I love only wangxiaojing"
    sendRsp(skt, msg)
    
    skt.close()
    sock.close()
    
    
    

v04-面向对象重构

  • 两个对象:
    • 一个负责监听接受传入socket, WebServer
    • 一个负责通讯, SocketHandler
  • 参看例子v04
    import socket
    import threading
    
    class SocketHandler:
    
        def __init__(self, sock):
            self.sock = sock
            # 放置Http请求的头部信息
            self.headInfo = set()
    
        def startHandler(self):
            '''
            处理传入请求做两件事情
            1. 解析http协议
            2. 返回n内容
            :return:
            '''
            self.headHandler()
            self.sendRsp()
            return None
    
        def headHandler(self):
            # 两个下划线开头的变量是啥意思捏?
            self.headInfo = self.__getAllLine()
            print(self.headInfo)
            return None
    
        def sendRsp(self):
            data = "HELLO WORLD"
            self.__sendRspAll(data)
            return None
    
    #####################################
    
        def __getLine(self):
    
            b1 = self.sock.recv(1)
            b2 = 0
            data = b''
    
            while  b2 != b'\r' and b1 != b'\n' :
                b2 = b1
                b1 = self.sock.recv(1)
    
                data += bytes(b2)
    
            return data.strip(b'\r')
    
    
        def __getAllLine(self):
    
            data = b''
            dataList = list()
            data = b''
    
            while True:
                data = self.__getLine()
                if data:
                    dataList.append(data)
                else:
                    return dataList
    
            return None
    
        def __sendRspLine(self,data):
    
            data += "\r\n"
            self.sock.send(data.encode("ASCII"))
            return None
    
    
        def __sendRspAll(self, data):
    
            self.__sendRspLine("HTTP/1.1 200 OK")
    
            strRsp = "Content-Length: "
            strRsp += str(len(data))
            self.__sendRspLine( strRsp )
    
            self.__sendRspLine("Content-Type: text/html")
    
            self.__sendRspLine("")
            self.__sendRspLine(data)
    
    
    class WebServer():
    
    
        def __init__(self, ip='127.0.0.1', port=7853):
            self.ip = ip
            self.port = port
    
            self.sock = socket.socket( socket.AF_INET, socket.SOCK_STREAM)
            self.sock.bind((self.ip, self.port))
            self.sock.listen(1)
            print("WebServer is started............................")
    
        def start(self):
            '''
            服务器程序一共永久性不间断提供服务
            :return:
            '''
            while True:
                skt, addr = self.sock.accept()
    
                if skt:
                    print("Received a socket {0} from {1} ................. ".format(skt.getpeername(), addr))
                    # sockHandler负责具体通信
                    sockHandler = SocketHandler(skt)
                    thr = threading.Thread(target=sockHandler.startHandler , args=( ) )
                    thr.setDaemon(True)
                    thr.start()
                    thr.join()
    
                    skt.close()
                    print("Socket {0} handling is done............".format(addr))
    
    
    
    if __name__ == '__main__':
        ws = WebServer()
        ws.start()
    
    

v05-使用配置文件

class ServerContent:
    ip = '127.0.0.1'
    port = 9999

    head_protocal = "HTTP/1.1 "
    head_code_200 = "200 "
    head_status_OK = "OK"

    head_content_length = "Content-Length: "
    head_content_type = "Content-Type: "
    content_type_html = "text/html"

    blank_line = ""



import socket
import threading

class SocketHandler:

    def __init__(self, sock):
        self.sock = sock
        # 放置Http请求的头部信息
        self.headInfo = set()

    def startHandler(self):
        '''
        处理传入请求做两件事情
        1. 解析http协议
        2. 返回n内容
        :return:
        '''
        self.headHandler()
        self.sendRsp()
        return None

    def headHandler(self):
        # 两个下划线开头的变量是啥意思捏?
        self.headInfo = self.__getAllLine()
        print(self.headInfo)
        return None

    def sendRsp(self):
        data = "HELLO WORLD"
        self.__sendRspAll(data)
        return None

    #####################################

    def __getLine(self):

        b1 = self.sock.recv(1)
        b2 = 0
        data = b''

        while  b2 != b'\r' and b1 != b'\n' :
            b2 = b1
            b1 = self.sock.recv(1)

            data += bytes(b2)

        return data.strip(b'\r')


    def __getAllLine(self):

        data = b''
        dataList = list()
        data = b''

        while True:
            data = self.__getLine()
            if data:
                dataList.append(data)
            else:
                return dataList

        return None

    def __sendRspLine(self,data):

        data += "\r\n"
        self.sock.send(data.encode("ASCII"))
        return None


    def __sendRspAll(self, data):


        self.__sendRspLine("HTTP/1.1 200 OK")

        strRsp = "Content-Length: "
        strRsp += str(len(data))
        self.__sendRspLine( strRsp )

        self.__sendRspLine("Content-Type: text/html")

        self.__sendRspLine("")
        self.__sendRspLine(data)


class WebServer():


    def __init__(self, ip=ServerContent.ip, port=ServerContent.port):
        self.ip = ip
        self.port = port

        self.sock = socket.socket( socket.AF_INET, socket.SOCK_STREAM)
        self.sock.bind((self.ip, self.port))
        self.sock.listen(1)
        print("WebServer is started............................")

    def start(self):
        '''
        服务器程序一共永久性不间断提供服务
        :return:
        '''
        while True:
            skt, addr = self.sock.accept()

            if skt:
                print("Received a socket {0} from {1} ................. ".format(skt.getpeername(), addr))
                # sockHandler负责具体通信
                sockHandler = SocketHandler(skt)
                thr = threading.Thread(target=sockHandler.startHandler , args=( ) )
                thr.setDaemon(True)
                thr.start()
                thr.join()

                skt.close()
                print("Socket {0} handling is done............".format(addr))



if __name__ == '__main__':
    ws = WebServer()
    ws.start()

v06-返回静态页面

  • 静态文件:不常编辑的文件内容
  • 静态文件的存储: 一般单独放入一共文件夹,或者静态文件服务器
  • 需要有一共html类型的页面
  • 把html文件作为文件读入内容
  • 作为结果反馈回去
  • 静态文件存放再: webapp文件夹下
    class ServerContent:
        ip = '127.0.0.1'
        port = 9999
    
        head_protocal = "HTTP/1.1 "
        head_code_200 = "200 "
        head_status_OK = "OK"
    
        head_content_length = "Content-Length: "
        head_content_type = "Content-Type: "
        content_type_html = "text/html"
    
        blank_line = ""
    
    
    
    import socket
    import threading
    
    class SocketHandler:
    
        def __init__(self, sock):
            self.sock = sock
            # 放置Http请求的头部信息
            self.headInfo = set()
    
        def startHandler(self):
            '''
            处理传入请求做两件事情
            1. 解析http协议
            2. 返回n内容
            :return:
            '''
            self.headHandler()
            self.sendRsp()
            return None
    
        def headHandler(self):
            # 两个下划线开头的变量是啥意思捏?
            self.headInfo = self.__getAllLine()
            print(self.headInfo)
            return None
    
    
        def sendRsp(self):
            data = "HELLO WORLD"
            '''
            想返回一个静态页面,可以考虑把静态页面文件读入,作为str类型
            然后作为一共长字符串返回
            '''
    
            fp = r'.\webapp\hello.html'
    
            with  open(fp, mode='r', encoding='utf-8') as f:
                data = f.read()
                self.__sendRspAll(data)
    
            return None
    
        #####################################
    
        def __getLine(self):
    
            b1 = self.sock.recv(1)
            b2 = 0
            data = b''
    
            while  b2 != b'\r' and b1 != b'\n' :
                b2 = b1
                b1 = self.sock.recv(1)
    
                data += bytes(b2)
    
            return data.strip(b'\r')
    
    
        def __getAllLine(self):
    
            data = b''
            dataList = list()
            data = b''
    
            while True:
                data = self.__getLine()
                if data:
                    dataList.append(data)
                else:
                    return dataList
    
            return None
    
        def __sendRspLine(self,data):
    
            data += "\r\n"
            self.sock.send(data.encode())
            return None
    
    
        def __sendRspAll(self, data):
    
    
            self.__sendRspLine("HTTP/1.1 200 OK")
    
            strRsp = "Content-Length: "
            strRsp += str(len(data))
            self.__sendRspLine( strRsp )
    
            self.__sendRspLine("Content-Type: text/html")
    
            self.__sendRspLine("")
            self.__sendRspLine(data)
    
    
    class WebServer():
    
    
        def __init__(self, ip=ServerContent.ip, port=ServerContent.port):
            self.ip = ip
            self.port = port
    
            self.sock = socket.socket( socket.AF_INET, socket.SOCK_STREAM)
            self.sock.bind((self.ip, self.port))
            self.sock.listen(1)
            print("WebServer is started............................")
    
        def start(self):
            '''
            服务器程序一共永久性不间断提供服务
            :return:
            '''
            while True:
                skt, addr = self.sock.accept()
    
                if skt:
                    print("Received a socket {0} from {1} ................. ".format(skt.getpeername(), addr))
                    # sockHandler负责具体通信
                    sockHandler = SocketHandler(skt)
                    thr = threading.Thread(target=sockHandler.startHandler , args=( ) )
                    thr.setDaemon(True)
                    thr.start()
                    thr.join()
    
                    skt.close()
                    print("Socket {0} handling is done............".format(addr))
    
    
    
    if __name__ == '__main__':
        ws = WebServer()
        ws.start()
    
    

v07-添加路由功能和404

  • 路由: 能够理解请求并按照请求调用相应处理函数的模块
    • 理解请求内容
    • 能够调用或者指定相应业务处理模块
  • 算法:
    • 按行读取传入报文
    • 假如报文能用空格分割成三段,则是请求行
    • 否则,是首部行,首部行必须要能够用冒号空格分割成两段
    • 首部行是天然的键值对
    • 请求行需要自行增加键
  • 404代表访问的资源不存在
    class ServerContent:
        ip = '127.0.0.1'
        port = 9999
    
        head_protocal = "HTTP/1.1 "
        head_code_200 = "200 "
        head_status_OK = "OK"
    
        head_content_length = "Content-Length: "
        head_content_type = "Content-Type: "
        content_type_html = "text/html"
    
        blank_line = ""
    
    
    
    import socket
    import threading
    
    class SocketHandler:
    
        def __init__(self, sock):
            self.sock = sock
            # 放置Http请求的头部信息
            self.headInfo = dict()
    
        def startHandler(self):
            '''
            处理传入请求做两件事情
            1. 解析http协议
            2. 返回n内容
            :return:
            '''
            self.headHandler()
            self.reqRoute()
            return None
    
        def reqRoute(self):
    
            uri = self.headInfo.get("uri")
            if uri == b"/":
                self.sendRsp(r"./webapp/hello.html")
                return None
            if uri == b"/favicon.ico":
                self.sendStaticIco(r"./static/fav.jfif")
                return None
    
            self.sendRsp(r"./webapp/404.html")
    
        def sendStaticIco(self, fp):
            with open(fp, mode='rb') as f:
                ico = f.read()
                self.__sendRspAll(ico)
    
        def headHandler(self):
            self.headInfo = dict()
            tmpHead = self.__getAllLine()
            for line in tmpHead:
                if b":" in line:
                    # split的具体含义
                    infos = line.split(b": ")
                    self.headInfo[infos[0]] = infos[1]
                else:
                    infos = line.split(b" ")
                    self.headInfo["protocal"] = infos[2]
                    self.headInfo["method"] = infos[0]
                    self.headInfo["uri"] = infos[1]
    
    
        def sendRsp(self, fp):
            data = "HELLO WORLD"
            '''
            想返回一个静态页面,可以考虑把静态页面文件读入,作为str类型
            然后作为一共长字符串返回
            '''
    
            #r'.\webapp\hello.html'
    
            with  open(fp, mode='r', encoding='utf-8') as f:
                data = f.read()
                self.__sendRspAll(data)
    
            return None
    
        #####################################
    
        def __getLine(self):
    
            b1 = self.sock.recv(1)
            b2 = 0
            data = b''
    
            while  b2 != b'\r' and b1 != b'\n' :
                b2 = b1
                b1 = self.sock.recv(1)
    
                data += bytes(b2)
    
            return data.strip(b'\r')
    
    
        def __getAllLine(self):
    
            data = b''
            dataList = list()
            data = b''
    
            while True:
                data = self.__getLine()
                if data:
                    dataList.append(data)
                else:
                    return dataList
    
            return None
    
        def __sendRspLine(self,data):
    
            if type(data) == bytes:
                self.sock.send(data)
            else:
                data += "\r\n"
                self.sock.send(data.encode())
            return None
    
    
        def __sendRspAll(self, data):
    
    
            self.__sendRspLine("HTTP/1.1 200 OK")
    
            strRsp = "Content-Length: "
            strRsp += str(len(data))
            self.__sendRspLine( strRsp )
    
            self.__sendRspLine("Content-Type: text/html")
    
            self.__sendRspLine("")
            self.__sendRspLine(data)
    
    
    class WebServer():
    
    
        def __init__(self, ip=ServerContent.ip, port=ServerContent.port):
            self.ip = ip
            self.port = port
    
            self.sock = socket.socket( socket.AF_INET, socket.SOCK_STREAM)
            self.sock.bind((self.ip, self.port))
            self.sock.listen(1)
            print("WebServer is started............................")
    
        def start(self):
            '''
            服务器程序一共永久性不间断提供服务
            :return:
            '''
            while True:
                skt, addr = self.sock.accept()
    
                if skt:
                    print("Received a socket {0} from {1} ................. ".format(skt.getpeername(), addr))
                    # sockHandler负责具体通信
                    sockHandler = SocketHandler(skt)
                    thr = threading.Thread(target=sockHandler.startHandler , args=( ) )
                    thr.setDaemon(True)
                    thr.start()
                    thr.join()
    
                    skt.close()
                    print("Socket {0} handling is done............".format(addr))
    
    
    
    if __name__ == '__main__':
        ws = WebServer()
        ws.start()
    
    

v08-添加静态文件

  • 静态文件: 在web后台,一般把图片,音频,视频,附件等很少需要更改内容的文件成为静态文件
  • 新建文件夹static用来存放静态文件

【生长吧!Python】有奖征文火热进行中:https://bbs.huaweicloud.com/blogs/278897

【版权声明】本文为华为云社区用户原创内容,转载时必须标注文章的来源(华为云社区)、文章链接、文章作者等基本信息, 否则作者和本社区有权追究责任。如果您发现本社区中有涉嫌抄袭的内容,欢迎发送邮件进行举报,并提供相关证据,一经查实,本社区将立刻删除涉嫌侵权内容,举报邮箱: cloudbbs@huaweicloud.com
  • 点赞
  • 收藏
  • 关注作者

评论(0

0/1000
抱歉,系统识别当前为高风险访问,暂不支持该操作

全部回复

上滑加载中

设置昵称

在此一键设置昵称,即可参与社区互动!

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。