美文网首页
Python HTTP 协议与 web 静态服务器

Python HTTP 协议与 web 静态服务器

作者: jovelin | 来源:发表于2018-07-13 17:27 被阅读25次

    HTTP 超文本传输协议

    超文本传输协议(HyperText Transfer Protocol)是一种应用层协议。

    HTTP是万维网的数据通信的基础。设计HTTP最初的目的是为了提供一种发布和接收HTML页面<网页>的方法。

    • 1989年蒂姆·伯纳斯-李在CERN研发
    • 1999年公布现今广泛使用的HTTP 1.1版(RFC2616)

    浏览器请求的基本流程

    mini-web服务器工作流程

    浏览器请求的 URL

    # www.baidu.com: 网站(网址)! 太长就不太称呼为网站了;
    #   url(统一资源定位符):
    #   完整版: http://www.baidu.com:80/aaa/bbb/index.html?username=aaa&password=123
    #       http/https: https是http加密后进行传输;(https收费...)
    #       端口: http: 80;    https: 443;
    #       /aaa/bbb/index.html: 请求的资源路径;
    #       username=aaa&password=123: 传输的内容;(请求体...GET)
    

    请求报文格式总结

    
    # 总结: 请求报文格式
    # 1.请求行;
    #     GET / HTTP/1.1\r\n
    # 2.请求头;
    #     头属性: 属性值\r\n
    #     Host: www.baidu.com\r\n
    # 3.空行;
    #     \r\n
    # 4.请求体;
    #     username=jovelin&password=123456
    
    
    
    # 请求报文格式分析:
    # 1.请求行(request line)
    #   格式: 请求方式 资源路径 协议及版本号\r\n
    GET / HTTP/1.1\r\n
    #   GET: 常用请求方式GET/POST;   (GET/POST/PUT/DELETE...)
    #       GET:  获取(从服务器获取信息的时候用...)
    #       POST: 发送(向服务器存储信息的时候用...)
    #   /: /aaa/bbb/index.html; 想要访问的页面/图片/音频...(明天要用...)
    #   HTTP/1.1: 协议及版本号
    #   空行: \r\n
    
    # 2.请求头(request header)
    #   格式: 头属性: 头信息\r\n
    Host: www.baidu.com\r\n
    #       Host: 主机;(记住...)
    Connection: keep-alive\r\n
    #       Connection: 链接;(长连接)
    Upgrade-Insecure-Requests: 1\r\n
    #       提示服务端我可以解析https;
    User-Agent: Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/66.0.3359.117 Safari/537.36\r\n
    #       User-Agent: 用户代理;(浏览器及系统版本...)
    Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8\r\n
    #       Accept: 接收!
    Accept-Encoding: gzip, deflate, br\r\n
    #       压缩: 数据压缩算法;
    Accept-Language: zh-CN,zh;q=0.9\r\n
    #       语言: 中文;
    
    # 3.空行;
    #     \r\n
    
    # 4.请求体;
    #     username=jovelin&password=123456
    
    
    请求报文格式总结

    响应报文格式总结

    
    # 总结: 响应报文格式
    # 1.响应行;
    #   HTTP/1.1 200 OK\r\n
    # 2.响应头;
    #   头属性: 头信息\r\n
    #   Server: BWS/1.1\r\n
    # 3.换行
    #   \r\n
    # 4.响应体;
    #   文本/图片/音频/视频/网页...
    
    
    
    # 响应报文格式分析:
    # 1.响应行(response line)
    #   格式: 协议及版本号 状态码 英文解释\r\n
    HTTP/1.1 200 OK\r\n
    #       HTTP/1.1: 协议及版本号
    #       200 OK: 状态码 英文解释
    
    # 2.响应头(response header)
    #   格式: 头属性: 属性值\r\n
    Connection: Keep-Alive\r\n
    #   长连接
    Content-Encoding: gzip\r\n
    #   压缩格式
    Content-Type: text/html; charset=utf-8\r\n
    #   请求体的文本类型;
    Date: Wed, 14 Mar 2018 09:52:48 GMT\r\n
    #   更新时间
    Server: BWS/1.1\r\n
    #   服务器名:(记住,因为简单,以后用)
    
    # 3.空行;
    #     \r\n
    
    # 4.响应体(response body)
    #     文本/图片/音频/视频/网页...
    
    
    响应报文格式总结

    网络响应状态码

    2xx 成功  200 OK  (发送成功)
    3xx 重定向 
        302 Moved Temporarily/302 Found   解释作用(暂时跳转)  301/2/3/4/7
        307 Internal Redirect(内部重定向)
        Location: https://www.baidu.com
    4xx 客户端错误 404 Not Found(客户端发送的页面没找打)
        http://help.xunlei.com/online/stat_inst.php?pid=0000&thunderver=5.8.14.706&thundertype=4&peerid=000C294E4AE1J3J4    
        http://video.baomihua.com/play_error/-30001
    5xx 服务器错误 503 Service Unavailable(服务器不能使用)
    

    长连接和短连接

    TCP长/短连接好比地铁卡/单程票

    在HTTP/1.0中, 默认使用的是短连接.也就是说, 浏览器和服务器每进行一次HTTP操作, 就建立一次连接, 但任务结束就中断连接.如果客户端浏览器访问的某个HTML或其他类型的 Web 页中包含有其他的Web资源,如js文件、图像文件、CSS文件等;当浏览器每遇到这样一个Web资源,就会建立一个HTTP会话。

    但从 HTTP/1.1起,默认使用长连接,用以保持连接特性。使用长连接的HTTP协议,会在响应头有加入这行代码:

    Connection:keep-alive
    

    在真正的读写操作之前,server与client之间必须建立一个连接,

    当读写操作完成后,双方不再需要这个连接时它们可以释放这个连接,

    连接的建立通过三次握手,释放则需要四次握手,

    所以说每个连接的建立都是需要资源消耗和时间消耗的。

    TCP 短连接

    短连接一般只会在 client/server 间传递一次读写操作!

    1. client 向 server 发起连接请求
    2. server 接到请求,双方建立连接
    3. client 向 server 发送消息
    4. server 回应 client
    5. 一次读写完成,此时双方任何一个都可以发起 close 操作 (一般都是 client 先发起 close 操作。当然也不排除有特殊的情况。)

    TCP 长连接

    1. client 向 server 发起连接
    2. server 接到请求,双方建立连接
    3. client 向 server 发送消息
    4. server 回应 client
    5. 一次读写完成,连接不关闭
    6. 后续读写操作...
    7. 长时间操作之后 client 发起关闭请求

    TCP长/短连接的优点和缺点

    长连接可以省去较多的TCP建立和关闭的操作,节约时间。但是如果用户量太大容易造成服务器负载过高最终导致服务不可用。

    短连接对于服务器来说实现起来较为简单,存在的连接都是有用的连接,不需要额外的控制手段。但是如果用户访问量很大, 往往可能在很短时间内需要创建大量的连接,造成服务器响应速度过慢。

    总之:

    小的WEB网站的http服务一般都用短链接,因为长连接对于服务端来说会耗费一定的资源来让套接字 保持存活-keep alive,

    对于中大型WEB网站一般都采用长连接,好处是响应用户请求的时间更短,用户体验更好,虽然更耗硬件资源一些,但这都不是事儿。另外,数据库的连接用长连接,如果用短连接频繁的通信会造成socket错误。

    案例

    1.模拟服务器(服务端)

    
    # 需求: 获取请求报文的格式;
    
    import socket
    
    if __name__ == '__main__':
        tcp_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
        tcp_socket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, True)
        tcp_socket.bind(("127.0.0.1", 8888))
        tcp_socket.listen(128)
        print("服务已开启...")
        while True:
            service_client_socket, ip_port = tcp_socket.accept()
            print(ip_port, "已连接...")
            data_bin = service_client_socket.recv(5000)
            print("二进制数据:", data_bin)
            print("解析后数据:", data_bin.decode())
            service_client_socket.close()
    
    

    2.模拟浏览器(客户端)

    
    # 需求: 获取响应报文的格式内容并保存;
    
    import socket
    
    if __name__ == '__main__':
        # 创建TCP连接
        tcp_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
    
        # DNS解析 和 连接HTTP服务器
        tcp_socket.connect(("www.baidu.com", 80))
    
        # 组包 发送HTTP请求报文
    
        # 请求行
        request_line = "GET / HTTP/1.1\r\n"
    
        # 请求头
        request_header = "Host: www.baidu.com\r\n"
        request_data = request_line + request_header + "\r\n"
    
        # 发送请求
        tcp_socket.send(request_data.encode())
    
        # 接收响应报文
        response_data = tcp_socket.recv(4096)
    
        # 对响应报文进行解析 -- 切割
        response_str_data = response_data.decode()
        # print(response_data)
    
        # '\r\n\r\n'之后的数据就是响应体数据
        index = response_str_data.find("\r\n\r\n")
    
        # 切割出的数据就是文件数据
        html_data = response_str_data[index + 4:]
    
        # data_file = open("index.html", "wb")
        # data_file.write(html_data.encode())
        # data_file.close()
        with open("index.html", "wb") as file:
            file.write(html_data.encode())
            # 如果是长连接,还有很多内容没有收到,需要死循环接收
            while True:
                # 后面在获取到的响应内容,就不包含响应行和响应头了
                data_bin = tcp_socket.recv(4096)
                if data_bin:
                    file.write(data_bin)
                else:
                    break
    
        # 关闭套接字
        tcp_socket.close()
    
    
    1. web 静态服务器
    
    from gevent import monkey
    
    monkey.patch_all()
    
    import socket
    import gevent
    import sys
    
    
    class WebServer(object):
        """Web 服务器类"""
    
        def __init__(self, ip, port):
            # 创建套接字
            self.socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
            # 设置套接字复用地址
            self.socket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
            # 绑定IP地址和端口
            self.socket.bind((ip, port))
            # 设置被动套接字
            self.socket.listen(128)
    
        def startup(self):
            """等待客户端连接"""
            while True:
                # 等待被连接
                service_client_socket, ip_port = self.socket.accept()
                print(ip_port, "连接成功.", end="\n\n")
                # 处理请求
                gevent.spawn(self.client_handler, service_client_socket)
    
        def client_handler(self, service_client_socket):
            """处理客户端请求"""
            request_data_bin = service_client_socket.recv(4096)
    
            if not request_data_bin:
                print('客户端已经断开连接.', end="\n\n")
                service_client_socket.close()
                return
            else:
                print("客户端请求报文:", request_data_bin, end="\n\n")
    
            # 解析 HTTP 文本
            my_http = self.parse_http(request_data_bin.decode('utf-8'))
    
            # 读取固定页面数据
            try:
                response_line = 'HTTP/1.1 200 OK\r\n'
                response_header = 'Server: PythonWebServer1.0\r\n'
                file = open('./static' + my_http['url'], 'rb')
            except:
                response_line = 'HTTP/1.1 404 NOT FOUND\r\n'
                response_header = 'Server: PythonWebServer1.0\r\n'
                file = open('./static/404.html', 'rb')
    
            # 读取文件内容
            response_content = file.read()
            file.close()
            # 拼接响应报文
            response_data = (response_line + response_header + '\r\n').encode('utf-8') + response_content
            # 发送响应报文
            service_client_socket.send(response_data)
            # 关闭套接字
            service_client_socket.close()
    
            print("\n", "-" * 100, "\n")
    
        @staticmethod
        def parse_http(request_data):
            """解析 HTTP 文本"""
    
            my_http = {}
    
            # 分成多行
            request_headers = request_data.split('\r\n')
            request_lines = request_headers[0].split(' ')
            print("request_lines: ", request_lines, end="\n\n")
    
            my_http['method'] = request_lines[0]
            my_http['url'] = request_lines[1]
            my_http['version'] = request_lines[2]
    
            # 未指定页面时 默认访问 index.html
            if my_http['url'] == "/":
                my_http['url'] = "/index.html"
    
            for header in request_headers[1:]:
    
                if not header:
                    continue
    
                # Host: www.baidu.com
                lines = header.split(':')
                my_http[lines[0]] = lines[1][1:]
    
            print("my_http: ", my_http, end="\n\n")
    
            return my_http
    
    
    def port_handler():
        """指定端口"""
    
        # 默认设置端口为 8888
        port = 8888
    
        # # 获取外部传递过来的参数;
        # # 1.尽量值传递一个参数过来
        # #   ctrl+z: 退出页面,但是程序没有退出;(该端口还可以使用)
        # #   ctrl+c: 退出页面,也退出程序;
        # # print(sys.argv[1])
        # if not len(sys.argv) == 2:
        #     print('输入的格式错误,正确的格式应该是: python3 文件名.py 端口号')
        #     return
        #
        # # 2.如果传递过来端口号,里面有非数字;(也不行)
        # if not sys.argv[1].isdigit():
        #     print('端口号, 必须是整数!!!')
        #     return
        #
        # # 3.取值范围: [0-65535]
        # if not 0 <= int(sys.argv[1]) <= 65535:
        #     print('端口号必须在: [0-65535]之间!!!')
        #     return
        #
        # # 4.如果全部通过,那么要把端口号,传递到程序中
        # # 获取用户指定的绑定端口
        # port = int(sys.argv[1])
    
        return port
    
    
    def main():
        # 服务器 IP,默认为本机 IP
        server_ip = ""
        # 服务器 端口
        server_port = port_handler()
        web_server = WebServer(server_ip, server_port)
        web_server.startup()
    
    
    if __name__ == '__main__':
        main()
    
    

    相关文章

      网友评论

          本文标题:Python HTTP 协议与 web 静态服务器

          本文链接:https://www.haomeiwen.com/subject/ggdspftx.html