python进阶--搭建一个多任务的静态web服务器

HTTP协议

    http协议:超文本传输协议,基于tcp通信协议
    作用:浏览器和服务器通讯的数据格式

URL

    概念:统一资源定位符/网络资源地址/网址
    组成:协议部分、域名部分、资源路径部分、查询参数部分

http请求方式: GET:没有请求体 POST:有请求体

http请求报文格式

    1.请求行
    2.请求头
    3.空行
    4.请求体 

注意点: 请求报文的原始格式是有\r\n的

请求行是由三部分组成:

请求方式
请求资源路径
HTTP协议版本
GET方式的请求报文没有请求体,只有请求行、请求头、空行组成。
POST方式的请求报文可以有请求行、请求头、空行、请求体四部分组成,注意:POST方式可以允许没有请求体,但是这种格式很少见。

响应报文格式

    响应行\r\n
    响应头\r\n
    空行\r\n
    响应体\r\n
    状态码          说明
    200         请求成功
    307         重定向
    400         错误的请求,请求地址或者参数有误
    404         请求资源在服务器不存在
    500         服务器内部源代码出现错误

搭建一个静态的web服务器

    1.编写一个TCP服务端程序
    2.获取浏览器发送的http请求报文数据
    3.读取固定页面数据,把页面数据组装成HTTP响应报文数据发送给浏览器。
    4.HTTP响应报文数据发送完成以后,关闭服务于客户端的套接字。

代码示例:这里的服务器不能发送不同的响应,无论用户输入什么资源路径,只要ip地址和端口号正确,返回的页面都是同一个

读者可以参考我的代码,如果需要验证结果请点击页面文件夹,然后访问本地ip(127.0.0.1:端口号)。

import socket
if __name__ == '__main__':
    # 1.创建socket套接字
    server_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
    # 2.设置端口重用
    server_socket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEPORT, True)
    # 3. 绑定本地端口
    server_socket.bind(("", 9090))
    # 4. 设置最大监听数,由主动变为被动
    server_socket.listen(2)
    while True:
        # 连接成功,产生一个新的套接字专门为客户端服务
        new_socket_client, client_address_infor = server_socket.accept()
        new_socket_client.recv(4096)
        # 打开返回的文件,读取文件
        with open("./static/index.html", "rb") as file:
            file_data = file.read()
        # 创建响应行
        response_line = "HTTP/1.1 200 OK\r\n"
        # 创建响应头
        response_head = "Server: NBWS/1.1\r\n"
        # 创建响应体
        response_body = file_data
        # 拼接响应数据
        response_data = (response_line + response_head + "\r\n").encode() + response_body
        # 发送相应数据
        new_socket_client.send(response_data)
        new_socket_client.close()

这里我们要升级,搭建一个随着输入不同的资源路径,返回不同的页面

代码示例:

import socket
if __name__ == '__main__':
    # 创建服务器的socket套接字
    server_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
    server_socket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEPORT, True)
    server_socket.bind(("", 9090))
    server_socket.listen(2)
    while True:
        
        new_socket_client, client_address_info = server_socket.accept()
        request_data = new_socket_client.recv(4096)
        # 判断客户端是否退出链接,避免客户端断开导致服务器崩溃
        if len(request_data) == 0:
            new_socket_client.close()
            continue
        # 从客户端的请求行里面获取到资源路径
        request_list = request_data.decode().split(" ", maxsplit=2)
        file_path = request_list[1]
        # 判断资源路径是否为空,如果为空,则返回一个错误404页面(别忘了修改状态码)
        if file_path == "/":
            file_path = "/index.html"
        # try一下避免客户端用户访问的资源路径不存在导致程序崩溃
        try:
            with open("static" + file_path, "rb") as file:
                file_data = file.read()
        except Exception as e:
            with open("static/error.html", "rb") as file:
                file_data = file.read()
            # 拼接响应提数据(下同)
            response_line = "HTTP/1.1 404 OK\r\n"
            response_head = "Server: NBWS/1.1\r\n"
            response_body = file_data
            response_data = (response_line + response_head + "\r\n").encode() + response_body
            new_socket_client.send(response_data)
            new_socket_client.close()

        else:
            response_line = "HTTP/1.1 200 OK\r\n"
            response_head = "Server: NBWS/1.1\r\n"
            response_body = file_data
            response_data = (response_line + response_head + "\r\n").encode() + response_body
            new_socket_client.send(response_data)
            new_socket_client.close()

实现了返回不同页面的mini服务器之后,就升级实现多任务版的服务器

多任务web服务器就是能够支持多个浏览器同事访问咋们的服务器,之前我写过多任务版的tcp服务端的开发代码,其实多任务web服务器就是多任务tcp服务端里面加上了http协议,即tcp是返回数据,web服务器返回的是http超文本(二进制),若有不清楚多任务tcp开发的话,请移步至[tcp多任务编程(多线程)](www.jianshu.com/p/88b7a5372de1)

多任务mini-web服务器代码示例:

# 1.实现tcp服务器端
#   1.1 创建socket套接字
#   1.2 绑定端口
#   1.3 设置成监听模式
#   1.4 等待客户端链接
#   1.5 收发数据
# 2.接收浏览器发送过来的请求报文
#   2.1 取出资源路径
#         GET /index.html HTTP/1.1\r\n ....\r\n ... \r\n
#       按照空格分割字符串
#       取出索引[1]的数据 -> file_path
# 3.打开指定文件(”static“ + file_path),返回http响应报文
#   响应行:
#       HTTP/1.1 200 OK\r\n
#   响应头
#       Server: NBW/1.1\r\n
#   空行
#       \r\n
#   响应体
#       file_data
# 4.关闭链接
import socket
from threading import Thread


def client_handler(server_socket):
    new_socket_client, client_address_info = server_socket.accept()
    request_data = new_socket_client.recv(4096)
    # 判断客户端是否退出链接,避免客户端断开导致服务器崩溃
    if len(request_data) == 0:
        new_socket_client.close()
        return
    # 从客户端的请求行里面获取到资源路径,请求行的构成:"GET(POST) /index.html(资源路径) HTTP/1.1",这里应用split方法分割字符串两次,就可以得到我们想要的资源路径了。
    request_list = request_data.decode().split(" ", maxsplit=2)
    file_path = request_list[1]
    # 判断资源路径是否为空,如果为空,则返回一个错误404页面(别忘了修改状态码)
    if file_path == "/":
        file_path = "/index.html"
    # try一下避免客户端用户访问的资源路径不存在导致程序崩溃
    try:
        with open("static" + file_path, "rb") as file:
            file_data = file.read()
    except Exception as e:
        with open("static/error.html", "rb") as file:
            file_data = file.read()
        # 拼接响应提数据(下同)
        response_line = "HTTP/1.1 404 OK\r\n"
        response_head = "Server: NBWS/1.1\r\n"
        response_body = file_data
        response_data = (response_line + response_head + "\r\n").encode() + response_body
        new_socket_client.send(response_data)
        new_socket_client.close()

    else:
        response_line = "HTTP/1.1 200 OK\r\n"
        response_head = "Server: NBWS/1.1\r\n"
        response_body = file_data
        response_data = (response_line + response_head + "\r\n").encode() + response_body
        new_socket_client.send(response_data)
        new_socket_client.close()


def main():
    server_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
    server_socket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEPORT, True)
    server_socket.bind(("", 9090))
    server_socket.listen(2)
    while True:  # 这里用一个死循环,就是有多少个浏览器来访问我,我就会产生多少个线程和new_socket_client来为浏览器服务
        client_thread = Thread(target=client_handler, args=(server_socket,))
        # 设置守护主线程,为了防止服务器不能彻底关闭
        client_thread.setDaemon(True)
        # 开启子线程
        client_thread.start()


if __name__ == '__main__':
    # 创建服务器的socket套接字
    main()

读者是不是发现我这段代码跟上面的前面的差不多,那是因为我就是复制上面的代码改写的,因为学习这个是具有连贯性的,有的东西不需要我们重复去敲,当然读者想重复去敲的话那当然更好啦。毕竟写代码还是需要多敲的。

注意点:1.读者千万记得写响应体的时候一定要加上"\r\n",作者在学习的时候也因为忘记了,走了很多弯路。切记切记!!!

到这里我们的一个基本的mini-web静态服务器就搭建好了,这里用的是面向过程的编程思想,接下来就来用面向对象编程吧

同上,还是该代码,示例代码:

import socket
from threading import Thread


class WebServer:
    def __init__(self):
        server_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
        server_socket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEPORT, True)
        server_socket.bind(("", 9090))
        server_socket.listen(2)
        self.server_socket = server_socket

    def start(self):
        while True:  # 这里用一个死循环,就是有多少个浏览器来访问我,我就会产生多少个线程和new_socket_client来为浏览器服务
            new_socket_client, client_address_info = self.server_socket.accept()
            client_thread = Thread(target=self.client_handler, args=(new_socket_client,))
            # 设置守护主线程,为了防止服务器不能彻底关闭
            client_thread.setDaemon(True)
            # 开启子线程
            client_thread.start()

    def client_handler(self, new_socket_client):
        request_data = new_socket_client.recv(4096)
        # 判断客户端是否退出链接,避免客户端断开导致服务器崩溃
        if len(request_data) == 0:
            new_socket_client.close()
            return
        # 从客户端的请求行里面获取到资源路径
        request_list = request_data.decode().split(" ", maxsplit=2)
        file_path = request_list[1]
        # 判断资源路径是否为空,如果为空,则返回一个错误404页面(别忘了修改状态码)
        if file_path == "/":
            file_path = "/index.html"
        # try一下避免客户端用户访问的资源路径不存在导致程序崩溃
        try:
            with open("static" + file_path, "rb") as file:
                file_data = file.read()
        except Exception as e:
            with open("static/error.html", "rb") as file:
                file_data = file.read()
            # 拼接响应提数据(下同)
            response_line = "HTTP/1.1 404 OK\r\n"
            response_head = "Server: NBWS/1.1\r\n"
            response_body = file_data
            response_data = (response_line + response_head + "\r\n").encode() + response_body
            new_socket_client.send(response_data)
            new_socket_client.close()

        else:
            response_line = "HTTP/1.1 200 OK\r\n"
            response_head = "Server: NBWS/1.1\r\n"
            response_body = file_data
            response_data = (response_line + response_head + "\r\n").encode() + response_body
            new_socket_client.send(response_data)
            new_socket_client.close()


if __name__ == '__main__':
    # 创建服务器的socket套接字
    nb_server = WebServer()
    nb_server.start()

对于面向对象编程,读者一定要多多体会,多试试。

拓展:到这里我们的服务器就算搭建好了,但是还有一点要注意这里面的端口号我们不能自定义,也不能由不懂python的人来更改,如果我们的服务器上已经运行一个程序正占用着当前(9090)这个端口号的话,那么我们的服务器是开不起来的,又不能随便去关闭服务器的其他程序,这怎么办呢?接下来就来教你在命令行自定义端口号

对于sys标准库里面的argv的使用:

代码示例:

#此文件名:study_sys_argv.py  windows:在命令行运行  ubuntu:在终端运行
import sys
a = sys.argv
print(a)

运行结果如下:
python@PC:~python3 ~/Desktop/web_static_server/study_sys_argv.py ['/home/python/Desktop/web_static_server/study_sys_argv.py'] 这里运行结果是个字典格式,sys.argv方法就是获取终端(命令行)的参数并生成一个列表,例如在python3 ~/Desktop/web_static_server/study_sys_argv.py这个命令里面python3就是命令,而/home/python/Desktop/web_static_server/study_sys_argv.py就是参数。这个参数还可以有多个。 如: python@PC:~ python3 ~/Desktop/web_static_server/study_sys_argv.py a b c d
输出(终端运行的内容):['/home/python/Desktop/web_static_server/study_sys_argv.py', 'a', 'b', 'c', 'd']
这里读者可能就明白了,下来我们就把他运用到自定义端口上去。

动态自定义端口mini-web多任务服务器代码示例:

import socket
from threading import Thread
import sys


class WebServer:

    def __init__(self, port):
        server_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
        server_socket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEPORT, True)
        server_socket.bind(("", port))
        server_socket.listen(2)
        self.server_socket = server_socket

    def start(self):
        while True:  # 这里用一个死循环,就是有多少个浏览器来访问我,我就会产生多少个线程和new_socket_client来为浏览器服务
            new_socket_client, client_address_info = self.server_socket.accept()
            client_thread = Thread(target=self.client_handler, args=(new_socket_client,))
            # 设置守护主线程,为了防止服务器不能彻底关闭
            client_thread.setDaemon(True)
            # 开启子线程
            client_thread.start()

    @staticmethod
    def client_handler(new_socket_client):
        request_data = new_socket_client.recv(4096)
        # 判断客户端是否退出链接,避免客户端断开导致服务器崩溃
        if len(request_data) == 0:
            new_socket_client.close()
            return
        # 从客户端的请求行里面获取到资源路径
        request_list = request_data.decode().split(" ", maxsplit=2)
        file_path = request_list[1]
        # 判断资源路径是否为空,如果为空,则返回一个错误404页面(别忘了修改状态码)
        if file_path == "/":
            file_path = "/index.html"
        # try一下避免客户端用户访问的资源路径不存在导致程序崩溃
        try:
            with open("./static" + file_path, "rb") as file:
                file_data = file.read()
        except Exception as e:
            with open("./static/error.html", "rb") as file:
                file_data = file.read()
            # 拼接响应提数据(下同)
            response_line = "HTTP/1.1 404 OK\r\n"
            response_head = "Server: NBWS/1.1\r\n"
            response_body = file_data
            response_data = (response_line + response_head + "\r\n").encode() + response_body
            new_socket_client.send(response_data)
            new_socket_client.close()

        else:
            response_line = "HTTP/1.1 200 OK\r\n"
            response_head = "Server: NBWS/1.1\r\n"
            response_body = file_data
            response_data = (response_line + response_head + "\r\n").encode() + response_body
            new_socket_client.send(response_data)
            new_socket_client.close()


if __name__ == '__main__':
    argv_list = sys.argv
    str_port = argv_list[1]
    port = int(str_port)
    print(port)
    nb_server = WebServer(port)
    nb_server.start()

总结:用了sys.argv方法就相当于在外部去灵活的定义服务器的端口,不用去修改程序。

注意:运行此代码时,一定要在终端(命令行),并且一定要进入项目目录里面,不然会报错。

你可能感兴趣的:(python进阶--搭建一个多任务的静态web服务器)