Python用TCP协议实现http服务器的四个版本+自定义WSGI服务器

第一个实现版本

import socket
import os
from urllib import parse


def main():
    server_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
    server_socket.bind(("", 8088))
    server_socket.listen(5)

    while True:
        print("waiting for connect")
        client_socket, client_address = server_socket.accept()
        print("client_socket = " + str(client_socket))
        print("client_address = " + str(client_address))
        data = client_socket.recv(1024)

        print(data.decode("utf-8"))
        request_path = data.decode("utf-8").split("\r\n")[0].split(" ")[1]
        print("请求的链接地址:" + request_path)

        # 修改 / 请求路径为 /index.html
        if request_path == "/":
            request_path = "/index.html"
            print("修改后的链接地址:" + request_path)

        # 删除请求路径中后面的 ?传递的参数
        flag = request_path.find("?")
        if flag != -1:
            request_path = request_path[0: flag]
            print("修改后的链接地址:" + request_path)

        # 将请求路径中存在的 % 编码转换为中文
        flag = request_path.find("%")
        if flag != -1:
            request_path = parse.unquote(request_path)
            print("修改后的链接地址:" + request_path)

        print()
        response_data = "HTTP/1.1 200 ok\r\n\r\n"  # 返回状态
        try:
            with open(os.path.dirname(os.path.dirname(__file__)) + "/Python高级-全部(html版)" + request_path, 'rb') as f:
                d = f.read()
                response_data = response_data.encode("gbk") + d
        except FileNotFoundError as e:
            response_data = response_data.encode("gbk") + "文件无法找到".encode("gbk")
        client_socket.send(response_data)
        client_socket.close()
        print("关闭一个" + str(client_address) + "连接")

    server_socket.close()


if __name__ == '__main__':
    main()

请求路径为:http://127.0.0.1:8088/01day/02_操作系统的发展史(科普章节).html.html?id=100
打印结果:
waiting for connect
client_socket = <socket.socket fd=560, family=AddressFamily.AF_INET, type=SocketKind.SOCK_STREAM, proto=0, laddr=('127.0.0.1', 8088), raddr=('127.0.0.1', 1082)>
client_address = ('127.0.0.1', 1082)
GET /01day/02_%E6%93%8D%E4%BD%9C%E7%B3%BB%E7%BB%9F%E7%9A%84%E5%8F%91%E5%B1%95%E5%8F%B2%EF%BC%88%E7%A7%91%E6%99%AE%E7%AB%A0%E8%8A%82%EF%BC%89.html?id=100 HTTP/1.1
Host: 127.0.0.1:8088
Connection: keep-alive
Upgrade-Insecure-Requests: 1
User-Agent: Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/72.0.3626.121 Safari/537.36
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,**;q=0.8
Accept-Encoding: gzip, deflate, br
Accept-Language: zh-CN,zh;q=0.9,en;q=0.8

请求的链接地址:/01day/02_%E6%93%8D%E4%BD%9C%E7%B3%BB%E7%BB%9F%E7%9A%84%E5%8F%91%E5%B1%95%E5%8F%B2%EF%BC%88%E7%A7%91%E6%99%AE%E7%AB%A0%E8%8A%82%EF%BC%89.html?id=100
修改后的链接地址:/01day/02_%E6%93%8D%E4%BD%9C%E7%B3%BB%E7%BB%9F%E7%9A%84%E5%8F%91%E5%B1%95%E5%8F%B2%EF%BC%88%E7%A7%91%E6%99%AE%E7%AB%A0%E8%8A%82%EF%BC%89.html
修改后的链接地址:/01day/02_操作系统的发展史(科普章节).html

效果截图,中文路径、Get方式请求是OK的,图片、css等也没有问题


第二个版本,多进程实现http服务器

import socket
import os
from urllib import parse
import multiprocessing


def main():
    server_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
    server_socket.bind(("", 8088))
    server_socket.listen(5)

    while True:
        print("waiting for connect")
        try:
            client_socket, client_address = server_socket.accept()
            multiprocessing.Process(target=handle, args=(client_socket, client_address)).start()

            # 对于客户端的socket,主进程有一个指向,子进程也有一个指向,
            # 仅仅是在子进程中关闭socket并不会真正的关闭,所以需要在主进程中再次关闭socket
            # 要不然浏览器会一直在堵塞(谷歌浏览器的小圆圈一直在转)
            client_socket.close()
        except Exception as e:
            server_socket.close()

def handle(client_socket, client_address):
    print("client_socket = " + str(client_socket))
    print("client_address = " + str(client_address))
    data = client_socket.recv(1024)

    print(data.decode("utf-8"))
    request_path = data.decode("utf-8").split("\r\n")[0].split(" ")[1]
    print("请求的链接地址:" + request_path)

    # 修改 / 请求路径为 /index.html
    if request_path == "/":
        request_path = "/index.html"
        print("修改后的链接地址:" + request_path)

    # 删除请求路径中后面的 ?传递的参数
    flag = request_path.find("?")
    if flag != -1:
        request_path = request_path[0: flag]
        print("修改后的链接地址:" + request_path)

    # 将请求路径中存在的 % 编码转换为中文
    flag = request_path.find("%")
    if flag != -1:
        request_path = parse.unquote(request_path)
        print("修改后的链接地址:" + request_path)

    print()
    response_data = "HTTP/1.1 200 ok\r\n\r\n"  # 返回状态
    try:
        with open(os.path.dirname(os.path.dirname(__file__)) + "/Python高级-全部(html版)" + request_path, 'rb') as f:
            d = f.read()
            response_data = response_data.encode("gbk") + d
    except FileNotFoundError as e:
        response_data = response_data.encode("gbk") + "文件无法找到".encode("gbk")
    client_socket.send(response_data)
    client_socket.close()
    print("关闭一个" + str(client_address) + "连接")


if __name__ == '__main__':
    main()

第三个版本,多线程实现http服务器

import socket
import os
from urllib import parse
import threading


def main():
    server_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
    server_socket.bind(("", 8088))
    server_socket.listen(5)

    while True:
        print("waiting for connect")
        try:
            client_socket, client_address = server_socket.accept()
            threading.Thread(target=handle, args=(client_socket, client_address)).start()

        except Exception as e:
            server_socket.close()


def handle(client_socket, client_address):
    print("client_socket = " + str(client_socket))
    print("client_address = " + str(client_address))
    data = client_socket.recv(1024)

    print(data.decode("utf-8"))
    request_path = data.decode("utf-8").split("\r\n")[0].split(" ")[1]
    print("请求的链接地址:" + request_path)

    # 修改 / 请求路径为 /index.html
    if request_path == "/":
        request_path = "/index.html"
        print("修改后的链接地址:" + request_path)

    # 删除请求路径中后面的 ?传递的参数
    flag = request_path.find("?")
    if flag != -1:
        request_path = request_path[0: flag]
        print("修改后的链接地址:" + request_path)

    # 将请求路径中存在的 % 编码转换为中文
    flag = request_path.find("%")
    if flag != -1:
        request_path = parse.unquote(request_path)
        print("修改后的链接地址:" + request_path)

    print()
    response_data = "HTTP/1.1 200 ok\r\n\r\n"  # 返回状态
    try:
        with open(os.path.dirname(os.path.dirname(__file__)) + "/Python高级-全部(html版)" + request_path, 'rb') as f:
            d = f.read()
            response_data = response_data.encode("gbk") + d
    except FileNotFoundError as e:
        response_data = response_data.encode("gbk") + "文件无法找到".encode("gbk")
    client_socket.send(response_data)
    client_socket.close()
    print("关闭一个" + str(client_address) + "连接")


if __name__ == '__main__':
    main()

第四个版本,协程实现http服务器(效率最高)

import socket
import os
from urllib import parse
import gevent
from gevent import monkey

# 必须加上这一句,要不然gevent切换不了
monkey.patch_all()


def main():
    server_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
    server_socket.bind(("", 8088))
    server_socket.listen(5)

    while True:
        print("waiting for connect")
        try:
            client_socket, client_address = server_socket.accept()
            gevent.spawn(handle, client_socket, client_address)

        except Exception as e:
            server_socket.close()


def handle(client_socket, client_address):
    print("client_socket = " + str(client_socket))
    print("client_address = " + str(client_address))
    data = client_socket.recv(1024)

    print(data.decode("utf-8"))
    request_path = data.decode("utf-8").split("\r\n")[0].split(" ")[1]
    print("请求的链接地址:" + request_path)

    # 修改 / 请求路径为 /index.html
    if request_path == "/":
        request_path = "/index.html"
        print("修改后的链接地址:" + request_path)

    # 删除请求路径中后面的 ?传递的参数
    flag = request_path.find("?")
    if flag != -1:
        request_path = request_path[0: flag]
        print("修改后的链接地址:" + request_path)

    # 将请求路径中存在的 % 编码转换为中文
    flag = request_path.find("%")
    if flag != -1:
        request_path = parse.unquote(request_path)
        print("修改后的链接地址:" + request_path)

    print()
    response_data = "HTTP/1.1 200 ok\r\n\r\n"  # 返回状态
    try:
        with open(os.path.dirname(os.path.dirname(__file__)) + "/Python高级-全部(html版)" + request_path, 'rb') as f:
            d = f.read()
            response_data = response_data.encode("gbk") + d
    except FileNotFoundError as e:
        response_data = response_data.encode("gbk") + "文件无法找到".encode("gbk")
    client_socket.send(response_data)
    client_socket.close()
    print("关闭一个" + str(client_address) + "连接")


if __name__ == '__main__':
    main()

自定义WSGI服务器

用的是上面进程实现http服务器代码修改的

模版文件放在/template,/static 放css、js文件,/dynamic 放mini_frame.py文件
实现的整体的效果是可以读取模版文件,并返回给客户端

import multiprocessing
import socket
import sys
from urllib import parse
from dynamic import mini_frame  # 这个是自定义模块,代码在下面


class WSGIServer():
    def __init__(self, port, app):
        self.header = "HTTP/1.1 "
        self.app = app
        self.server_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
        self.server_socket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
        self.server_socket.bind(("", int(port)))
        self.server_socket.listen(5)

    def run_forever(self):
        while True:
            print("waiting for connect")
            try:
                client_socket, client_address = self.server_socket.accept()
                multiprocessing.Process(target=self.handle, args=(client_socket, client_address)).start()

                # 对于客户端的socket,主进程有一个指向,子进程也有一个指向,
                # 仅仅是在子进程中关闭socket并不会真正的关闭,所以需要在主进程中再次关闭socket
                client_socket.close()
            except Exception as e:
                self.server_socket.close()

    def handle(self, client_socket, client_address):
        print("client_socket = " + str(client_socket))
        print("client_address = " + str(client_address))
        data = client_socket.recv(1024)

        print(data.decode("utf-8"))
        request_path = data.decode("utf-8").split("\r\n")[0].split(" ")[1]
        print("请求的链接地址:" + request_path)

        # 修改 / 请求路径为 /index.html
        if request_path == "/":
            request_path = "/index.html"
            print("修改后的链接地址:" + request_path)

        # 删除请求路径中后面的 ?传递的参数
        flag = request_path.find("?")
        if flag != -1:
            request_path = request_path[0: flag]
            print("修改后的链接地址:" + request_path)

        # 将请求路径中存在的 % 编码转换为中文
        flag = request_path.find("%")
        if flag != -1:
            request_path = parse.unquote(request_path)
            print("修改后的链接地址:" + request_path)

        print()

        response_data = ""
        if not request_path.endswith(".py"):
            response_data = self.header + "200 ok\r\n"
            if request_path.endswith(".css"):
                response_data += "content-type: text/css" + "\r\n\r\n"
            if request_path.endswith(".js"):
                response_data += "content-type: application/javascript" + "\r\n\r\n"
            try:
                with open("./" + request_path, 'r', encoding="utf-8") as f:
                    d = f.read()
                    response_data = response_data + d
            except FileNotFoundError as e:
                response_data += "文件无法找到"

        else:
            env = dict()
            env["request_path"] = request_path
            body = mini_frame.application(env, self.start_response)

            response_data = self.header + body

        client_socket.send(response_data.encode("utf-8"))
        client_socket.close()
        print("关闭一个" + str(client_address) + "连接")

    def start_response(self, status, headers):
        self.header = self.header + status
        for h in headers:
            self.header = self.header +"\r\n" + h[0] + ":" + h[1]
        self.header = self.header + "\r\n\r\n"
        print(self.header)


def main():
    if len(sys.argv) == 3:
        port = sys.argv[1]
        frame_app_name = sys.argv[2]
        print("您输入的端口为:" + port +", 框架为:" + frame_app_name)
    else:
        print("输入无效,使用默认端口:8088, 默认框架:mini_frame:application")
        frame_app_name = "mini_frame:application"
        port = 8088
    frame_name = frame_app_name.split(":")[0]
    app_name = frame_app_name.split(":")[1]
    
    要将模块所在的目录添加到sys路径中,才能找得到模块
    sys.path.append("./dynamic")
     
    由文件名动态导入模块
    frame = __import__(frame_name)
    app = getattr(frame, app_name)

    server = WSGIServer(port, app)
    server.run_forever()


if __name__ == '__main__':
    main()
mini_frame.py 文件
def application(environ, start_response):
    start_response('200 OK', [('Content-Type', 'text/html;charset=utf-8')])
    request_path = environ["request_path"]

    if request_path == "/index.py":
        body = index()
    elif request_path == "/login.py":
        body = login()
    elif request_path == "/center.py":
        body = center()
    else:
        body = "页面飞到了外星球"
    return body


def index():
    with open("./templates/index.html", "r", encoding="utf-8") as f:
        data = f.read()
    return data


def login():
    return "登陆页面"


def center():
    with open("./templates/center.html", "r", encoding="utf-8") as f:
        data = f.read()
    return data
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 220,192评论 6 511
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 93,858评论 3 396
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 166,517评论 0 357
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 59,148评论 1 295
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 68,162评论 6 397
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,905评论 1 308
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,537评论 3 420
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 39,439评论 0 276
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,956评论 1 319
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 38,083评论 3 340
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 40,218评论 1 352
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,899评论 5 347
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 41,565评论 3 331
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 32,093评论 0 23
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 33,201评论 1 272
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 48,539评论 3 375
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 45,215评论 2 358

推荐阅读更多精彩内容