Python: 练习 简单http server 简单web框架Python: 练习 简单http server 简单web框架Python: 练习 简单http server 简单web框架Python: 练习 简单http server 简单web框架
  • 首页
  • 博客
  • 书签
  • 文件
  • 分析
  • 登录

Python: 练习 简单http server 简单web框架

发表 admin at 2022年6月22日
类别
  • Practice
标签

simple_http_server_web_framework.zip

import sys
import threading
import socket
import os.path
import json
import struct
import hashlib
import re
import framework
from content_type import content_type


class NetworkCommunicateRole(object):
    def role_choose(self):
        while True:
            role = input("请选择角色 Http服务(h)/服务端(s)/客户端(c):")
            if role == "h":
                return "http_server",
            elif role == "s":
                return "server",
            elif role == "c":
                while True:
                    server_ip_ = input("请输入服务端IP地址和端口(x.x.x.x:xx):")
                    server_ip = self.check_ip(server_ip_)
                    if not server_ip:
                        print("IP地址和端口输入错误,请重新输入!")
                    else:
                        return "client", server_ip
            else:
                print("输入错误,请重新输入!")

    @staticmethod
    def check_ip(ip_port):
        try:
            ip, port_ = ip_port.split(":", maxsplit=1)
        except:
            return False
        else:
            re_ = r'^(1\d{2}|2[0-4]\d|25[0-5]|[1-9]\d|[1-9])\.(1\d{2}|2[0-4]\d|25[0-5]|[1-9]\d|\d)\.(1\d{2}|2[0-4]\d|25[0-5]|[1-9]\d|\d)\.(1\d{2}|2[0-4]\d|25[0-5]|[1-9]\d|\d)$'
            compile_ip = re.compile(re_)
            if not compile_ip.match(ip):
                return False
            else:
                try:
                    port = int(port_)
                except:
                    return False
                else:
                    if int(port) not in range(0, 65536):
                        return False
                    else:
                        return ip, port


class NetworkCommunicate(object):
    def __init__(self, role, argv):
        self.role = role
        self.argv = argv
        self.tcp_socket_create()

    def tcp_socket_create(self):
        self.tcp_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
        self.tcp_socket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, True)

        def converse_type(converse, port):
            server_port = self.get_server_port(port)
            try:
                self.tcp_socket.bind(("", server_port))
            except:
                print(f"{server_port}端口已被占用!")
                return
            self.tcp_socket.listen(128)
            self.server(converse)

        if self.role[0] == "http_server":
            print(f"当前角色:Http服务", end="")
            converse_type(self.http_converse, 80)
        elif self.role[0] == "server":
            print(f"当前角色:服务端", end="")
            converse_type(self.converse, 9090)
        elif self.role[0] == "client":
            print(f"当前角色:客户端", end="")
            self.client()

    def get_server_port(self, default_port):
        try:
            server_port = int(self.argv[1])
        except:
            server_port = default_port
        else:
            if int(server_port) not in range(0, 65536):
                server_port = default_port
            else:
                pass
        return server_port

    def server(self, converse):
        print(f"{self.tcp_socket.getsockname()[0]}:{self.tcp_socket.getsockname()[1]} 等待客户端连接...")
        while True:
            service_socket, client_ip = self.tcp_socket.accept()
            client_ip = f"{client_ip[0]}:{client_ip[1]}"
            print(f"service_socket: {service_socket.getsockname()[0]}:{service_socket.getsockname()[1]}, "
                  f"client_ip: {client_ip} ")
            threading_for_client = threading.Thread(target=converse, args=(service_socket, client_ip))
            threading_for_client.setDaemon(True)
            threading_for_client.start()

    def http_converse(self, socket_, client_ip):
        while True:
            try:
                date = self.receive_date(socket_).decode("utf-8")
            except:
                print(f"客户端{client_ip}关闭了连接")
                break
            else:
                if len(date) != 0:
                    print(f"收到来自{client_ip}的信息:\n{date}")
                    request_type, request_path = self.request_parse(date)
                    # 只写了GET请求的响应
                    if request_type == "GET":
                        try:
                            self.http_response(socket_, request_path, client_ip)
                        except:
                            print(f"客户端{client_ip} 发送失败!")
                    else:
                        pass
                else:
                    print(f"客户端{client_ip}关闭了连接")
                    break
        socket_.close()

    def converse(self, socket_, client_ip):
        while True:
            try:
                date = self.receive_date(socket_).decode("utf-8")
            except:
                print(f"客户端{client_ip}关闭了连接")
                break
            else:
                if len(date) != 0:
                    print(f"收到来自{client_ip}的信息:{date}")
                    if date.split(":", 1)[0] == "get":
                        try:
                            self.send_file(socket_, client_ip, date.split(":", 1)[1])
                        except:
                            print(f"客户端{client_ip} 发送失败!")
                    else:
                        try:
                            self.send_date(socket_, client_ip)
                        except:
                            print(f"客户端{client_ip} 发送失败!")
                else:
                    print(f"客户端{client_ip}关闭了连接")
                    break
        socket_.close()

    def client(self):
        self.tcp_socket.connect(self.role[1])
        receiver = f"{self.role[1][0]}:{self.role[1][1]}"
        while True:
            print('发送"get:filename"请求文件')
            request_file_flag = self.send_date(self.tcp_socket, receiver)
            if request_file_flag:
                self.receive_file(self.tcp_socket, receiver)
            else:
                date_receiving = self.receive_date(self.tcp_socket)
                print(f"收到来自{receiver}的信息:{date_receiving.decode('utf-8')}")

    def send_date(self, socket_, receiver="", date=None, encode_=False):
        request_file_flag = False
        if date is None:
            date_to_send = input(f"请输入要发送给{receiver}的内容:").encode("utf-8")
            if date_to_send.decode("utf-8").split(":", 1)[0] == "get":
                request_file_flag = True
            else:
                request_file_flag = False
        else:
            if not encode_:
                date_to_send = date
            else:
                date_to_send = date.encode("utf-8")
        socket_.send(date_to_send)
        return request_file_flag

    def receive_date(self, socket_, size=1024):
        date_receiving = socket_.recv(size)
        return date_receiving

    def request_parse(self, request):
        request_parsed = request.split(" ", maxsplit=2)
        request_type = request_parsed[0]
        request_path = request_parsed[1]
        return request_type, request_path

    def http_response(self, socket_, request_path, receiver=""):
        if request_path == "/":
            request_path = "/index.html"
        root_dir = "www"
        suffix = "." + request_path.split(".")[-1]
        response_file_path = root_dir + request_path

        if suffix == ".html":
            handle_dynamic_request = framework.HandleDynamicRequest(root_dir, request_path)
            response_line, response_header, response_content = handle_dynamic_request.handle_dynamic_request()
            response_date = response_line + response_header + "\r\n"
            print(f"发送: {response_file_path} Size:{len(response_content)} 到 {receiver} ", end="")
            self.send_date(socket_, receiver, response_date, encode_=True)
            self.send_chunk(socket_, receiver, response_content.encode("utf-8"))
            self.send_chunk(socket_, receiver, "")
            print(f"OK!\n")
        else:
            def not_found():
                response_line_ = "HTTP/1.1 404 Not Found\r\n"
                response_header_ = f"Server: PWS/1.0\r\nContent-Type: text/html;charset=utf-8\r\nTransfer-Encoding: chunked\r\n"
                response_date_ = response_line_ + response_header_ + "\r\n"
                response_content_ = "404 Not Found!"
                print(f"发送: 404 Not Found! Size:{len(response_content_)} 到 {receiver} ", end="")
                self.send_date(socket_, receiver, response_date_, encode_=True)
                self.send_chunk(socket_, receiver, response_content_.encode("utf-8"))
                self.send_chunk(socket_, receiver, "")
                print(f"OK!\n")

            if os.path.exists(response_file_path):
                if os.path.isfile(response_file_path):
                    os.path.getsize(response_file_path)
                    response_line = "HTTP/1.1 200 OK\r\n"
                    if suffix in content_type:
                        content_type_ = content_type[suffix]
                        if content_type_.find("text") == 0:
                            content_type_ += "; charset=utf-8"
                    else:
                        content_type_ = content_type[".*"]
                    response_header = f"Server: PWS/1.0\r\nContent-Type: {content_type_}\r\nTransfer-Encoding: chunked\r\n"
                    response_date = response_line + response_header + "\r\n"
                    self.send_date(socket_, receiver, response_date, encode_=True)
                    print(f"发送: {response_file_path} Size:{os.path.getsize(response_file_path)} 到 {receiver} ", end="")
                    with open(response_file_path, "rb") as fd:
                        while True:
                            file_chunk = fd.read(1024)
                            done = self.send_chunk(socket_, receiver, file_chunk)
                            if done:
                                break
                        fd.close()
                    print(f"OK!\n")
                else:
                    not_found()
            else:
                not_found()

    def send_chunk(self, socket_, receiver, chunk):
        chunk_length = len(chunk)
        if chunk_length != 0:
            """Chunk Size是以十六进制的ASCII码表示,比如:头部是3134这两个字节,表示的是1和4这两个ascii字符,
            被Http协议解释为十六进制数14,也就是十进制的20,后面紧跟[\r\n](0d 0a),再接着是连续的20个字节的Chunk正文。
            Chunk数据以0长度的Chunk块结束,也就是(30 0d 0a 0d 0a)"""
            response_chunk_length = str(hex(chunk_length)[2:]) + "\r\n"
            response_chunk = chunk
            self.send_date(socket_, receiver, response_chunk_length, encode_=True)
            self.send_date(socket_, receiver, response_chunk, encode_=False)
            self.send_date(socket_, receiver, "\r\n", encode_=True)
        else:
            response_chunk_end = hex(0)[2:] + "\r\n\r\n"
            self.send_date(socket_, receiver, response_chunk_end, encode_=True)
            return True

    def send_file(self, socket_, receiver, file):
        file_size = os.path.getsize(file)
        file_md5 = self.md5_calculate(file)
        file_info_ = {"file_name": file, "file_size": file_size, "file_md5": file_md5}
        file_info = json.dumps(file_info_)
        file_info_size = struct.pack("i", len(file_info))
        print(f"准备发送文件 {file} 到 {receiver}。文件头长度 {len(file_info)} 文件头 {file_info} >>> ", end="")
        socket_.send(file_info_size)
        date_receiving = self.receive_date(socket_, size=4)
        if date_receiving == file_info_size:
            self.send_date(socket_, receiver, file_info)
            with open(file, "rb") as fd:
                while True:
                    file_content = fd.read(1024)
                    if not file_content:
                        break
                    socket_.send(file_content)
            print(f"发送完毕!")

    def receive_file(self, socket_, receiver):
        date_receiving = self.receive_date(socket_, size=4)
        file_info_size = struct.unpack("i", date_receiving)[0]
        socket_.send(date_receiving)
        date_receiving = self.receive_date(socket_, size=file_info_size)
        file_info = json.loads(date_receiving)
        file_name = "receive_" + file_info["file_name"]
        re_ = r".*?(?=\.[^\.]*$)|[^\.]+"
        file_name_prefix = re.match(re_, file_name).group()
        re_ = r"\.[^\.]*$"
        file_name_extension_ = re.search(re_, file_name)
        if not file_name_extension_:
            file_name_extension = ""
        else:
            file_name_extension = file_name_extension_.group()

        def check_file_exist(count, file_name_prefix, new_file_name_prefix):
            if os.path.exists(new_file_name_prefix + file_name_extension):
                if os.path.isfile(new_file_name_prefix + file_name_extension):
                    count += 1
                    new_file_name_prefix = file_name_prefix + ("_" + str(count))
                    return check_file_exist(count, file_name_prefix, new_file_name_prefix)
                else:
                    return new_file_name_prefix
            else:
                return new_file_name_prefix

        file_name = check_file_exist(0, file_name_prefix, file_name_prefix) + file_name_extension
        file_size = 0
        buffer_size = 1024
        with open(file_name, "ab+") as fd:
            while file_size < file_info["file_size"]:
                if file_info["file_size"] - file_size >= buffer_size:
                    date_receiving = self.receive_date(socket_, size=buffer_size)
                elif file_info["file_size"] - file_size < buffer_size:
                    buffer_size_ = file_info["file_size"] - file_size
                    date_receiving = self.receive_date(socket_, size=buffer_size_)
                fd.write(date_receiving)
                file_size += len(date_receiving)
            fd.close()
        file_md5 = self.md5_calculate(file_name)
        if file_md5 == file_info["file_md5"]:
            print(f"文件接收完毕。 文件名 {file_name} MD5:{file_md5}")
        else:
            print(f'错误!MD5不一致!\n发送{file_info["file_md5"]}\n收到{file_md5}')

    @staticmethod
    def md5_calculate(file):
        file_md5 = hashlib.md5()
        with open(file, "rb") as fd:
            while True:
                file_content = fd.read(1024)
                if not file_content:
                    break
                file_md5.update(file_content)
        return file_md5.hexdigest()


def main():
    while True:
        role = NetworkCommunicateRole().role_choose()
        network = NetworkCommunicate(role, sys.argv)


if __name__ == '__main__':
    main()

# framework.py -----------------------------------------------------------------
import json
import re

import pymysql


def route(route_list, path):
    def decorator(func):
        route_list.append((path, func))

        def inner(*args, **kwargs):
            return func(*args, **kwargs)

        return inner

    return decorator


class HandleDynamicRequest(object):
    route_list = []

    def __init__(self, root_dir, request_path):
        self.root_dir = root_dir
        self.request_path = request_path
        self.path = self.root_dir + self.request_path

    @route(route_list, "/data.html")
    def data_interface(self):
        response_line = "HTTP/1.1 200 OK\r\n"
        response_header = "Server: PWS/1.0\r\nContent-Type: text/html;charset=utf-8\r\nTransfer-Encoding: chunked\r\n"
        data = self.sql_handle("show create table `students`;")[0][1]
        pattern = "COMMENT='(.*?)'$"
        table_header = re.search(pattern, data).group(1).split(" ")
        table_header_ = self.sql_handle("DESC students;")
        # print(table_header_)
        json_data = {}
        header_ = {}
        header_["header"] = {}
        for i in range(len(table_header_)):
            header_["header"][table_header_[i][0]] = table_header[i]
        table_content = self.sql_handle("select * from `students`;")
        content_ = {}
        k = 0
        for i in table_content:
            content_[str(k)] = {}
            m = 0
            for j in i:
                content_[str(k)][table_header_[m][0]] = j
                m += 1
            k += 1
        json_data = dict(json_data, **header_)
        json_data["content"] = content_
        json_data = json.dumps(json_data, ensure_ascii=False)
        response_content = json_data
        return response_line, response_header, response_content

    @route(route_list, "/person.html")
    def person(self):
        response_line = "HTTP/1.1 200 OK\r\n"
        response_header = "Server: PWS/1.0\r\nContent-Type: text/html;charset=utf-8\r\nTransfer-Encoding: chunked\r\n"
        response_content = self.file_handle(self.path)
        response_content = response_content.replace(r"{%content%}", '<div id="d1"></div>')
        return response_line, response_header, response_content

    @route(route_list, "/index.html")
    def index(self):
        response_line = "HTTP/1.1 200 OK\r\n"
        response_header = "Server: PWS/1.0\r\nContent-Type: text/html;charset=utf-8\r\nTransfer-Encoding: chunked\r\n"
        response_content = self.file_handle(self.path)
        data = self.sql_handle("show create table `students`")[0][1]
        pattern = "COMMENT='(.*?)'$"
        table_header = re.search(pattern, data).group(1).split(" ")
        table_content = self.sql_handle("select * from `students`")
        table = '<table border="1"><tr>'
        for i in table_header:
            table += f"<td>{i}</td>"
        table += "</tr>"
        td_content = ""
        for i in table_content:
            td_content += "<tr>"
            for j in i:
                td_content += f"<td> {j}</td>"
            td_content += "</tr>"
        table += td_content + "</table>"
        response_content = response_content.replace(r"{%content%}", table)
        return response_line, response_header, response_content

    def not_found(self):
        response_line = "HTTP/1.1 404 Not Found\r\n"
        response_header = "Server: PWS/1.0\r\nContent-Type: text/html;charset=utf-8\r\nTransfer-Encoding: chunked\r\n"
        response_date = "404 Not Found!"
        return response_line, response_header, response_date

    def handle_dynamic_request(self):
        for path, func in self.route_list:
            if path == self.request_path:
                return func(self)
        else:
            return self.not_found()

    def file_handle(self, path):
        with open(path, "r", encoding="utf-8") as fd:
            return fd.read()

    def sql_handle(self, sql):
        mysql_connector = pymysql.connect(host="localhost", port=3306, user="root", password="", database="www",
                                          charset="utf8")
        cursor = mysql_connector.cursor()
        data = ""
        try:
            cursor.execute(sql)
        except:
            return data
        else:
            data = cursor.fetchall()
        cursor.close()
        mysql_connector.close()
        return data


if __name__ == '__main__':
    handle_request = HandleDynamicRequest("www", "/index.html")
    print(handle_request.handle_dynamic_request())
    # print(handle_request.route_list)
# MySQL ------------------------------------------------------------------------
CREATE DATABASE www;
USE www;
CREATE TABLE `students` (
`id` int(10) unsigned NOT NULL AUTO_INCREMENT,
`name` varchar(20) NOT NULL,
`gender` enum('男','女') DEFAULT NULL,
`age` tinyint(3) unsigned DEFAULT 0,
PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=6 DEFAULT CHARSET=utf8 COMMENT='序号 姓名 姓别 年龄';
INSERT into students VALUES (0,"小李","男",19);
INSERT into students VALUES (0,"张三","男",25);
INSERT into students VALUES (0,"小红","女",18);
INSERT into students VALUES (0,"茗茗","女",22);
INSERT into students VALUES (0,"赵六","男",23);

发表回复 取消回复

您的电子邮箱地址不会被公开。 必填项已用*标注

类别

  • Cat
  • Python
  • MySQL
  • Django
  • Html/CSS
  • JavaScript
  • Vue
  • RegExp
  • php
  • Practice
  • Virtualization
  • Linux
  • Windows
  • Android
  • NAS
  • Software
  • Hardware
  • Network
  • Router
  • Office
  • WordPress
  • SEO
  • English
  • Games
  • Recipes
  • living
  • Memorandum
  • Essays
  • 未分类

归档

©2015-2023 艾丽卡 Blog support@alaica.com
      ajax-loader