Python 建立一个web服务器(个人学习记录整理中)

本文介绍了如何使用Python建立一个简单的web服务器,包括静态版本的处理固定页面、动态版本解析HTTP请求并根据请求内容返回不同响应,以及使用多进程提高并发性能。
摘要由CSDN通过智能技术生成

一、Python 建立一个web服务器
学习前置知识:1、懂得简单http协议的使用。2、懂得简单TCP协议的使用

1、web 服务器也是一个硬件,是一个实体,当别人通过浏览器发送请求給服务器,服务器会跟据你的请求返回消息給这个服务器。 为了更加理解这句话我们要知道。

  • 1、服务器能根据你发的内容,返回你需要的内容。 所以两边是有一个规则的,两边都需要遵守。这个规则就是http协议。
  • 2、服务能返回你需要的内容,这个内容是怎么来的呢? 因为你请求的内容本来就放在服务器里面了,服务器里的硬盘中存放着这些资源。
    在这里插入图片描述

综上,建立一个web服务器,就是去建立一个处理http协议的东西。当别人给你发了一个http协议的请求,你怎么去响应,就是一个web服务器应该做的事

我们去实现的时候,重点
1、就是怎么处理别人发过来的http请求,然后提取出自己要的内容
2、建立一个 TCP 服务器

  • 1、创建套接字
  • 2、绑定本地信息
  • 3、变为监听套接字
  • 4、等待对方连结
静态版本

1、先做一个静态版本 不管发你什么内容,都返回一个固定页面的版本。

  
#静态版本
import socket    


def handle_request(client_socket):    
    """
    处理浏览器发送过来的数据    
    然后回送相对应的数据(html、css、js、img。。。)    
    :return:    
    """
    # 1. 接收
    recv_content = client_socket.recv(1024).decode("utf-8", errors="ignore")

    print("-----接收到的数据如下----:")
    print(recv_content)

    # 2. 处理请求(此时忽略)

    # 3.1 整理要回送的数据
    response_headers = "HTTP/1.1 200 OK\r\n"
    response_headers += "Content-Type:text/html;charset=utf-8\r\n"
    response_headers += "\r\n"

    response_boy = "hahahah"

    response = response_headers + response_boy

    # 3.2 给浏览器回送对应的数据
    client_socket.send(response.encode("utf-8"))

    # 4. 关闭套接字
    client_socket.close()


def main():
    """
    用来控制整体
    :return:
    """

    # 1. 创建套接字
    tcp_server_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
    # 为了保证在tcp先断开的情况下,下一次依然能够使用指定的端口,需要设置
    tcp_server_socket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)

    # 2. 绑定本地信息
    tcp_server_socket.bind(("", 8081))

    # 3. 变成监听套接字
    tcp_server_socket.listen(128)

    while True:
        # 4. 等待客户端的链接
        client_socket, client_info = tcp_server_socket.accept()

        print(client_info)  # 打印 当前是哪个客户端进行了请求

        # 5. 为客户端服务
        handle_request(client_socket)

    # 6. 关闭套接字
    tcp_server_socket.close()


if __name__ == '__main__':
    main()

2、动态版
import socket


def handle_request(client_socket):
    """
    处理浏览器发送过来的数据
    然后回送相对应的数据(html、css、js、img。。。)
    :return:
    """
    # 1. 接收
    recv_content = client_socket.recv(1024).decode("utf-8", errors="ignore")

    print("-----接收到的数据如下----:")
    print(recv_content)

    # 2. 处理请求(此时忽略)

    # 3.1 整理要回送的数据
    response_headers = "HTTP/1.1 200 OK\r\n"
    response_headers += "Content-Type:text/html;charset=utf-8\r\n"
    response_headers += "\r\n"

    response_boy = "hahahah"

    response = response_headers + response_boy

    # 3.2 给浏览器回送对应的数据
    client_socket.send(response.encode("utf-8"))

    # 4. 关闭套接字
    client_socket.close()


def main():
    """
    用来控制整体
    :return:
    """

    # 1. 创建套接字
    tcp_server_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
    # 为了保证在tcp先断开的情况下,下一次依然能够使用指定的端口,需要设置
    tcp_server_socket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)

    # 2. 绑定本地信息
    tcp_server_socket.bind(("", 8081))

    # 3. 变成监听套接字
    tcp_server_socket.listen(128)

    while True:  
        # 4. 等待客户端的链接  
        client_socket, client_info = tcp_server_socket.accept()



        print(client_info)  # 打印 当前是哪个客户端进行了请求  

        # 5. 为客户端服务  
        handle_request(client_socket)  

    # 6. 关闭套接字  
    tcp_server_socket.close()  


if __name__ == '__main__':  
    main()  

3、动态版
import socket
import re


'''
1
'''



def handle_request(client_socket):
    """
    处理浏览器发送过来的数据
    然后回送相对应的数据(html、css、js、img。。。)
    :return:
    """
    # 1. 接收数据
    recv_content = client_socket.recv(1024).decode("utf-8", errors="ignore")

    print("-----接收到的数据如下----:")
    # print(recv_content)
    lines = recv_content.splitlines()  # 将接收到的http的request请求数据按照行进行切割到一个列表中
    # for line in lines:
    #     print("---")
    #     print(line)
    print('lines[0] 的数据是:', lines[0])
    # 2. 处理接收到的数据,根据请求数据,拿到请求行中的 请求方式(GET POST)和 请求URL中的
    # 提取出浏览器发送过来的request中的路径
    # 浏览器输入http://127.0.0.1:8081/a/b/c/index.html    请求行是    GET /a/b/c/index.html HTTP/1.1
    # 对于请求行 GET /a/b/c/d/index.html HTTP/1.1  ,怎么获取到路径呢,即 从GET 后面的第一个斜杠到遇到第一个空格结尾。
    # request_file_path = re.match(r"[^/]+(/[^ ]*)", lines[0]).group(1)
    request_file_path = re.match(r"([^/]*)([^ ]*)", lines[0])  # 获取从‘/’开始,遇到空格结尾。 match 匹配第一个符合的
    if request_file_path:
        print("正则提取数据", request_file_path.group(1))  # GET
        print("正则提取数据", request_file_path.group(2))  # /a/b/c/index.html
        request_file_path = request_file_path.group(2)
    # 完善对方访问主页的情况,如果只有/那么就认为浏览器要访问的是主页
    if request_file_path == "/":
        request_file_path = "/index.html"
    try:
        # 从html文件夹中读取出对应的文件的数据内容
        with open("./html" + request_file_path, "rb") as f:
            content = f.read()
    except Exception:
        # 如果要是有异常,那么就认为:找不到那个对应的文件,此时就应该对浏览器404
        pass
        response_headers = "HTTP/1.1 404 Not Found\r\n"
        response_headers += "Content-Type:text/html;charset=utf-8\r\n"
        response_headers += "\r\n"
        response_boy = "----sorry,the file you need not found-------"
        response = response_headers + response_boy
        # 3、 给浏览器回送对应的数据
        client_socket.send(response.encode("utf-8"))
    else:
        # 如果要是没有异常,那么就认为:找到了指定的文件,将其数据回送给浏览器即可
        response_headers = "HTTP/1.1 200 OK\r\n"
        response_headers += "Content-Type:text/html;charset=utf-8\r\n"
        response_headers += "\r\n"
        response_boy = content
        # 将header 返回給浏览器
        client_socket.send(response_headers.encode('utf-8'))
        # body 返回給浏览器
        client_socket.send(response_boy)
        # 给浏览器回送对应的数据 ,上面是分开发送
        # response = response_headers.encode("utf-8") + response_boy
        # client_socket.send(response)
        # 4. 关闭套接字
    client_socket.close()


def main():
    """
    用来控制整体
    :return:
    """

    # 1. 创建套接字
    tcp_server_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
    # 为了保证在tcp先断开的情况下,下一次依然能够使用指定的端口,需要设置
    tcp_server_socket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)

    # 2. 绑定本地信息
    tcp_server_socket.bind(("", 8081))

    # 3. 变成监听套接字
    tcp_server_socket.listen(128)

    while True:
        # 4. 等待客户端的链接
        client_socket, client_info = tcp_server_socket.accept()

        print(client_info)  # 打印 当前是哪个客户端进行了请求

        # 5. 为客户端服务
        handle_request(client_socket)

    # 6. 关闭套接字
    tcp_server_socket.close()


if __name__ == '__main__':
    main()


5、多进程版

import socket
import re
import multiprocessing


def handle_request(client_socket):
    """
    处理浏览器发送过来的数据
    然后回送相对应的数据(html、css、js、img。。。)
    :return:
    """
    # 1. 接收
    recv_content = client_socket.recv(1024).decode("utf-8", errors="ignore")

    print("-----接收到的数据如下----:")
    # print(recv_content)
    lines = recv_content.splitlines()  # 将接收到的http的request请求数据按照行进行切割到一个列表中
    # for line in lines:
    #     print("---")
    #     print(line)

    # 2. 处理请求
    # 提取出浏览器发送过来的request中的路径
    # GET / HTTP/1.1
    # GET /index.html HTTP/1.1
    # .......
    lines[0]

    # 提取出/index.html 或者 /
    request_file_path = re.match(r"[^/]+(/[^ ]*)", lines[0]).group(1)

    print("----提出来的请求路径是:----")
    print(request_file_path)

    # 完善对方访问主页的情况,如果只有/那么就认为浏览器要访问的是主页
    if request_file_path == "/":
        request_file_path = "/index.html"

    try:
        # 从html文件夹中读取出对应的文件的数据内容
        with open("./html" + request_file_path, "rb") as f:
            content = f.read()
    except Exception:
        # 如果要是有异常,那么就认为:找不到那个对应的文件,此时就应该对浏览器404
        pass
        response_headers = "HTTP/1.1 404 Not Found\r\n"
        response_headers += "Content-Type:text/html;charset=utf-8\r\n"
        response_headers += "\r\n"
        response_boy = "----sorry,the file you need not found-------"
        response = response_headers + response_boy
        # 3.2 给浏览器回送对应的数据
        client_socket.send(response.encode("utf-8"))
    else:
        # 如果要是没有异常,那么就认为:找到了指定的文件,将其数据回送给浏览器即可
        response_headers = "HTTP/1.1 200 OK\r\n"
        response_headers += "Content-Type:text/html;charset=utf-8\r\n"
        response_headers += "\r\n"
        response_boy = content
        response = response_headers.encode("utf-8") + response_boy
        # 3.2 给浏览器回送对应的数据
        client_socket.send(response)

    # 4. 关闭套接字
    client_socket.close()


def main():
    """
    用来控制整体
    :return:
    """

    # 1. 创建套接字
    tcp_server_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
    # 为了保证在tcp先断开的情况下,下一次依然能够使用指定的端口,需要设置
    tcp_server_socket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)

    # 2. 绑定本地信息
    tcp_server_socket.bind(("", 8081))

    # 3. 变成监听套接字
    tcp_server_socket.listen(128)

    while True:
        # 4. 等待客户端的链接
        client_socket, client_info = tcp_server_socket.accept()

        print(client_info)  # 打印 当前是哪个客户端进行了请求

        # 5. 为客户端服务
        # handle_request(client_socket)
        p = multiprocessing.Process(target=handle_request, args=(client_socket,))
        p.start()

        # 如果是创建了一个子进程去使用client_socket,那么子进程会复制一份这个套接字,所以要在主进程中关闭一次
        # 这样能够保证在子进程接收且调用close时,能够真正的将这个套接字关闭,如果主进程中没有close。那么即使子进程使用了close
        # 这个套接字也不会被真正的关闭,所以就不会有tcp的4次挥手
        #
        # 简单来说:如果是子进程,那么 就要在主进程中关闭一次
        #         如果是子线程,那么 就不要再主进程中关闭,因为线程的方式是共享,而进程的方式是复制
        client_socket.close()

    # 6. 关闭套接字
    tcp_server_socket.close()


if __name__ == '__main__':
    main()

1、个人学习版
import re
import multiprocessing
# import mini_web


class Server(object):

    def __init__(self):
        # 1. 创建套接字
        self.tcp_server_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
        # 为了保证在tcp先断开的情况下,下一次依然能够使用指定的端口,需要设置
        self.tcp_server_socket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)

        # 2. 绑定本地信息
        self.tcp_server_socket.bind(("", 8081))

        # 3. 变成监听套接字
        self.tcp_server_socket.listen(128)

        # 定义2个属性,用来存储web框架传递过来的状态码以及响应头
        self.status = ""  # 指向状态码字符串
        self.headers = None  # 指向一个新的列表

    def handle_request(self, client_socket):
        """
        处理浏览器发送过来的数据
        然后回送相对应的数据(html、css、js、img。。。)
        :return:
        """
        # 1. 接收
        recv_content = client_socket.recv(1024).decode("utf-8", errors="ignore")

        print("-----接收到的数据如下----:")
        # print(recv_content)
        lines = recv_content.splitlines()  # 将接收到的http的request请求数据按照行进行切割到一个列表中
        # for line in lines:
        #     print("---")
        #     print(line)

        # 2. 处理请求
        # 提取出浏览器发送过来的request中的路径
        # GET / HTTP/1.1
        # GET /index.html HTTP/1.1
        # .......
        lines[0]

        # 提取出/index.html 或者 /
        request_file_path = re.match(r"[^/]+(/[^ ]*)", lines[0]).group(1)

        print("----提出来的请求路径是:----")
        print(request_file_path)

        # 完善对方访问主页的情况,如果只有/那么就认为浏览器要访问的是主页
        if request_file_path == "/":
            request_file_path = "/index.html"

        # 如果请求的后缀不是.py结尾,那么就认为是普通的静态资源(就是静态页面)
        if not request_file_path.endswith(".py"):

            try:
                # 从html文件夹中读取出对应的文件的数据内容
                with open("./html" + request_file_path, "rb") as f:
                    content = f.read()
            except Exception:
                # 如果要是有异常,那么就认为:找不到那个对应的文件,此时就应该对浏览器404
                pass
                response_headers = "HTTP/1.1 404 Not Found\r\n"
                response_headers += "Content-Type:text/html;charset=utf-8\r\n"
                response_headers += "\r\n"
                response_boy = "----sorry,the file you need not found-------"
                response = response_headers + response_boy
                # 3.2 给浏览器回送对应的数据
                client_socket.send(response.encode("utf-8"))
            else:
                # 如果要是没有异常,那么就认为:找到了指定的文件,将其数据回送给浏览器即可
                response_headers = "HTTP/1.1 200 OK\r\n"
                response_headers += "Content-Type:text/html;charset=utf-8\r\n"
                response_headers += "\r\n"
                response_boy = content
                response = response_headers.encode("utf-8") + response_boy
                # 3.2 给浏览器回送对应的数据
                client_socket.send(response)
        else:
            # # 如果是以.py结尾的请求,那么就进行动态生成页面内容
            #
            # env = dict()  # 定义个字典,用来封装数据,然后传递到application函数中
            # env["PATH_INFO"] = request_file_path  # "/login.py"
            #
            # response_boy = mini_web.application(env, self.set_status_headers)
            #
            # # 将header和body进行合并成一个整体,作为response的内容
            # response_headers = "HTTP/1.1 %s\r\n" % self.status
            # for header in self.headers:
            #     response_headers += "%s:%s\r\n" % (header[0], header[1])
            # response_headers += "\r\n"
            #
            # response = response_headers + response_boy
            # # 3.2 给浏览器回送对应的数据
            # client_socket.send(response.encode("utf-8"))
            pass

        # 4. 关闭套接字
        client_socket.close()

    def set_status_headers(self, status, headers):
        self.status = status  # "200 OK"
        self.headers = headers  # [("Content-Type", "text/html;charset=utf-8")]

    def run(self):
        """
        用来控制整体
        :return:
        """
        while True:
            # 4. 等待客户端的链接
            client_socket, client_info = self.tcp_server_socket.accept()

            print(client_info)  # 打印 当前是哪个客户端进行了请求

            # 5. 为客户端服务
            # handle_request(client_socket)
            p = multiprocessing.Process(target=self.handle_request, args=(client_socket,))
            p.start()

            # 如果是创建了一个子进程去使用client_socket,那么子进程会复制一份这个套接字,所以要在主进程中关闭一次
            # 这样能够保证在子进程接收且调用close时,能够真正的将这个套接字关闭,如果主进程中没有close。那么即使子进程使用了close
            # 这个套接字也不会被真正的关闭,所以就不会有tcp的4次挥手
            #
            # 简单来说:如果是子进程,那么 就要在主进程中关闭一次
            #         如果是子线程,那么 就不要再主进程中关闭,因为线程的方式是共享,而进程的方式是复制
            client_socket.close()

        # 6. 关闭套接字
        self.tcp_server_socket.close()


def main():
    """
    完成整体的控制
    :return:
    """
    # 1. 创建Server服务器对象
    server = Server()

    # 2. 调用它的运行方法
    server.run()


if __name__ == '__main__':
    main()

  • 3
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值