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);