Python实现简易HTTP服务器与MINI WEB框架(利用WSGI实现服务器与框架解耦)

本文描述如果简单实现自定义Web服务器与自定义简易框架,并且不断进行版本迭代,从而清晰的展现服务器与Web框架之间是如何结合、如何配合工作的。以及WSGI是什么。

一、选取一个自定义的服务器版本

参照 https://www.cnblogs.com/leokale-zz/p/11957768.html 中的各种服务器实现版本,我们选择比较简单的多进程版本作为演示版本

代码如下:

import socket
import re
import multiprocessing

def handle_request(new_socket):
    # 接收请求
    recv_msg = ""
    recv_msg = new_socket.recv(1024).decode("utf-8")
    if recv_msg == "":
        print("recv null")
        new_socket.close()
        return

    # 从请求中解析出URI
    recv_lines = recv_msg.splitlines()
    print(recv_lines.__len__())
    # 使用正则表达式提取出URI
    ret = re.match(r"[^/]+(/[^ ]*)", recv_lines[0])
    if ret:
        # 获取URI字符串
        file_name = ret.group(1)
        # 如果URI是/,则默认返回index.html的内容
        if file_name == "/":
            file_name = "/index.html"

    try:
        # 根据请求的URI,读取相应的文件
        fp = open("." + file_name, "rb")
    except:
        # 找不到文件,响应404
        response_msg = "HTTP/1.1 404 NOT FOUND\r\n"
        response_msg += "\r\n"
        response_msg += "<h1>----file not found----</h1>"
        new_socket.send(response_msg.encode("utf-8"))
    else:
        html_content = fp.read()
        fp.close()
        # 响应正确 200 OK
        response_msg = "HTTP/1.1 200 OK\r\n"
        response_msg += "\r\n"

        # 返回响应头
        new_socket.send(response_msg.encode("utf-8"))
        # 返回响应体
        new_socket.send(html_content)

    # 关闭该次socket连接
    new_socket.close()

def main():
    # 创建TCP SOCKET实例
    tcp_server_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
    # # 设置重用地址
    # tcp_server_socket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
    # 绑定地址(默认本机IP)和端口
    tcp_server_socket.bind(("", 7890))
    # 监听
    tcp_server_socket.listen(128)
    # 循环接收客户端连接
    while True:
        new_socket, client_addr = tcp_server_socket.accept()
        # 启动一个子进程来处理客户端的请求
        sub_p = multiprocessing.Process(target=handle_request, args=(new_socket,))
        sub_p.start()
        # 这里要关闭父进程中的new_socket,因为创建子进程会复制一份new_socket给子进程
        new_socket.close()

    # 关闭整个SOCKET
    tcp_server_socket.close()

if __name__ == "__main__":
    main()

二、将代码用面向对象思想改写

import socket
import re
import multiprocessing

class WSGIServer(object):
    def __init__(self):
        # 创建socket实例
        self.tcp_server_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
        # 设置资源重用
        self.tcp_server_socket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
        # 绑定IP端口
        self.tcp_server_socket.bind(("", 7890))
        # 开始监听
        self.tcp_server_socket.listen(128)

    # 请求处理函数
    def handle_request(self, new_socket):
        # 接收请求
        recv_msg = ""
        recv_msg = new_socket.recv(1024).decode("utf-8")
        if recv_msg == "":
            print("recv null")
            new_socket.close()
            return

        # 从请求中解析出URI
        recv_lines = recv_msg.splitlines()
        # 使用正则表达式提取出URI
        ret = re.match(r"[^/]+(/[^ ]*)", recv_lines[0])
        if ret:
            # 获取URI字符串
            file_name = ret.group(1)
            # 如果URI是/,则默认返回index.html的内容
            if file_name == "/":
                file_name = "/index.html"

        try:
            # 根据请求的URI,读取相应的文件
            fp = open("." + file_name, "rb")
        except:
            # 找不到文件,响应404
            response_msg = "HTTP/1.1 404 NOT FOUND\r\n"
            response_msg += "\r\n"
            response_msg += "<h1>----file not found----</h1>"
            new_socket.send(response_msg.encode("utf-8"))
        else:
            html_content = fp.read()
            fp.close()
            # 响应正确 200 OK
            response_msg = "HTTP/1.1 200 OK\r\n"
            response_msg += "\r\n"

            # 返回响应头
            new_socket.send(response_msg.encode("utf-8"))
            # 返回响应体
            new_socket.send(html_content)

        # 关闭该次socket连接
        new_socket.close()

    # 开始无限循环,接受请求
    def run_forever(self):
        while True:
            new_socket, client_addr = self.tcp_server_socket.accept()
            # 启动一个子进程来处理客户端的请求
            sub_p = multiprocessing.Process(target=self.handle_request, args=(new_socket,))
            sub_p.start()
            # 这里要关闭父进程中的new_socket,因为创建子进程会复制一份new_socket给子进程
            new_socket.close()

        # 关闭整个SOCKET
        tcp_server_socket.close()

def main():
    wsgi_server = WSGIServer()
    wsgi_server.run_forever()

if __name__ == "__main__":
    main()

在构造函数内创建监听socket实例,然后使用run_forever开始运行。

三、静态资源和动态资源

静态资源:例如html页面、css文件、js文件、图片等都属于静态资源。

动态资源:每次请求返回的数据都不一样,例如从数据库中获取的数据,或者变化的时间等,都叫动态资源。

如下图所示:

解释:

1.当服务器收到请求,判断请求内容为xxx.html、xxxx.css、xxxx.js、xxxx.png等,则直接从磁盘获取静态文件,读取并返回。

2.当服务器收到请求,判断请求内容为xxx.py(或其他自定义的特殊形式),则会调用web框架中的函数来获取数据。

3.HTTP服务器除了返回静态数据,以及从web框架获取reponse header和reponse body,将两者组装起来返回给客户端,他不做其他事情。

四、给HTTP服务器加上处理动态请求的逻辑

import socket
import re
import multiprocessing

class WSGIServer(object):
    def __init__(self):
        # 创建socket实例
        self.tcp_server_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
        # 设置资源重用
        self.tcp_server_socket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
        # 绑定IP端口
        self.tcp_server_socket.bind(("", 7890))
        # 开始监听
        self.tcp_server_socket.listen(128)

    # 请求处理函数
    def handle_request(self, new_socket):
        # 接收请求
        recv_msg = ""
        recv_msg = new_socket.recv(1024).decode("utf-8")
        if recv_msg == "":
            print("recv null")
            new_socket.close()
            return

        # 从请求中解析出URI
        recv_lines = recv_msg.splitlines()
        # 使用正则表达式提取出URI
        ret = re.match(r"[^/]+(/[^ ]*)", recv_lines[0])
        if ret:
            # 获取URI字符串
            file_name = ret.group(1)
            # 如果URI是/,则默认返回index.html的内容
            if file_name == "/":
                file_name = "/index.html"

        if not file_name.endswith(".py"):
            try:
                # 根据请求的URI,读取相应的文件
                fp = open("." + file_name, "rb")
            except:
                # 找不到文件,响应404
                response_msg = "HTTP/1.1 404 NOT FOUND\r\n"
                response_msg += "\r\n"
                response_msg += "<h1>----file not found----</h1>"
                new_socket.send(response_msg.encode("utf-8"))
            else:
                html_content = fp.read()
                fp.close()
                # 响应正确 200 OK
                response_msg = "HTTP/1.1 200 OK\r\n"
                response_msg += "\r\n"

                # 返回响应头
                new_socket.send(response_msg.encode("utf-8"))
                # 返回响应体
                new_socket.send(html_content)
        else:
            header = "HTTP/1.1 200 OK\r\n"
            header += "\r\n"
            body = "dynamic request" + "  %s" % time.ctime()
            response = header + body
            new_socket.send(response.encode("utf-8"))

        # 关闭该次socket连接
        new_socket.close()

    # 开始无限循环,接受请求
    def run_forever(self):
        while True:
            new_socket, client_addr = self.tcp_server_socket.accept()
            # 启动一个子进程来处理客户端的请求
            sub_p = multiprocessing.Process(target=self.handle_request, args=(new_socket,))
            sub_p.start()
            # 这里要关闭父进程中的new_socket,因为创建子进程会复制一份new_socket给子进程
            new_socket.close()

        # 关闭整个SOCKET
        tcp_server_socket.close()

def main():
    wsgi_server = WSGIServer()
    wsgi_server.run_forever()

if __name__ == "__main__":
    main()

添加判断分支,如果请求内容以.py结尾,则判断为动态数据,返回dynamic request + 时间。如下图:

五、服务器与动态请求解耦

另外实现一个py模块,叫mini_frame.py

import time

def login():
    return "----login page----\r\n %s" % time.ctime()

def register():
    return "----register page----\r\n %s" % time.ctime()

def application(file_name):
    if file_name == "/login.py":
        return login()
    elif file_name == "register.py":
        return register()
    else:
        return "Not Found You Page..."

服务器一旦判断收到的请求为动态数据请求,则调用application(file_name),并将请求交给mini_frame来处理。

服务器端代码:

import socket
import re
import multiprocessing
import time

import mini_frame

class WSGIServer(object):
    def __init__(self):
        # 创建socket实例
        self.tcp_server_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
        # 设置资源重用
        self.tcp_server_socket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
        # 绑定IP端口
        self.tcp_server_socket.bind(("", 7890))
        # 开始监听
        self.tcp_server_socket.listen(128)

    # 请求处理函数
    def handle_request(self, new_socket):
        # 接收请求
        recv_msg = ""
        recv_msg = new_socket.recv(1024).decode("utf-8")
        if recv_msg == "":
            print("recv null")
            new_socket.close()
            return

        # 从请求中解析出URI
        recv_lines = recv_msg.splitlines()
        # 使用正则表达式提取出URI
        ret = re.match(r"[^/]+(/[^ ]*)", recv_lines[0])
        if ret:
            # 获取URI字符串
            file_name = ret.group(1)
            # 如果URI是/,则默认返回index.html的内容
            if file_name == "/":
                file_name = "/index.html"

        if not file_name.endswith(".py"):
            try:
                # 根据请求的URI,读取相应的文件
                fp = open("." + file_name, "rb")
            except:
                # 找不到文件,响应404
                response_msg = "HTTP/1.1 404 NOT FOUND\r\n"
                response_msg += "\r\n"
                response_msg += "<h1>----file not found----</h1>"
                new_socket.send(response_msg.encode("utf-8"))
            else:
                html_content = fp.read()
                fp.close()
                # 响应正确 200 OK
                response_msg = "HTTP/1.1 200 OK\r\n"
                response_msg += "\r\n"

                # 返回响应头
                new_socket.send(response_msg.encode("utf-8"))
                # 返回响应体
                new_socket.send(html_content)
        else:
            header = "HTTP/1.1 200 OK\r\n"
            header += "\r\n"
            body = mini_frame.application(file_name)
            response = header + body
            new_socket.send(response.encode("utf-8"))

        # 关闭该次socket连接
        new_socket.close()

    # 开始无限循环,接受请求
    def run_forever(self):
        while True:
            new_socket, client_addr = self.tcp_server_socket.accept()
            # 启动一个子进程来处理客户端的请求
            sub_p = multiprocessing.Process(target=self.handle_request, args=(new_socket,))
            sub_p.start()
            # 这里要关闭父进程中的new_socket,因为创建子进程会复制一份new_socket给子进程
            new_socket.close()

        # 关闭整个SOCKET
        tcp_server_socket.close()

def main():
    wsgi_server = WSGIServer()
    wsgi_server.run_forever()

if __name__ == "__main__":
    main()

服务器只需调用mini_frame.application(file_name),获取body数据即可。

六、WSGI介绍

如图,WSGI主要有以下流程:

1.服务器默认调用WEB 框架提供的application函数,参数为env和func_name,env是一个字典,用于存放请求中的一些数据,例如"/login.py"等,func_name是函数引用,这个函数的服务器WSGIServer类的一个成员函数,用于提供给application调用,从而将response header先返回给服务器,并保存在self.header中。

2.Web框架再调用func_name指向的函数之后,再准备response body数据,并通过return返回给服务器。

3.服务器将self.header和body数据组合起来,返回给客户端,完成一次完整的请求流程。

七、WSGI标准接口

def application(environ, start_response):
    start_response(‘200 OK‘,[(‘Content-Type‘,‘text/html‘)])
    return ‘Hello World...‘

上述代码是一个最简单符合WSGI标准的HTTP处理函数,接收两个参数,environ是包含所有HTTP请求信息的dict对象,start_response提供给web框架调用的函数。

将我们的nimi_frame框架代码修改一下:

def application(env, start_response):
    start_response(‘200 OK‘, [(‘Content-Tpye‘, ‘text/html‘)])
    return "Hello World.."

将服务器代码修改一下:

import socket
import re
import multiprocessing
import time

import mini_frame

class WSGIServer(object):
    def __init__(self):
        self.headers = list()
        self.status = ""
        # 创建socket实例
        self.tcp_server_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
        # 设置资源重用
        self.tcp_server_socket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
        # 绑定IP端口
        self.tcp_server_socket.bind(("", 7890))
        # 开始监听
        self.tcp_server_socket.listen(128)

    # 请求处理函数
    def handle_request(self, new_socket):
        # 接收请求
        recv_msg = ""
        recv_msg = new_socket.recv(1024).decode("utf-8")
        if recv_msg == "":
            print("recv null")
            new_socket.close()
            return

        # 从请求中解析出URI
        recv_lines = recv_msg.splitlines()
        # 使用正则表达式提取出URI
        ret = re.match(r"[^/]+(/[^ ]*)", recv_lines[0])
        if ret:
            # 获取URI字符串
            file_name = ret.group(1)
            # 如果URI是/,则默认返回index.html的内容
            if file_name == "/":
                file_name = "/index.html"

        if not file_name.endswith(".py"):
            try:
                # 根据请求的URI,读取相应的文件
                fp = open("." + file_name, "rb")
            except:
                # 找不到文件,响应404
                response_msg = "HTTP/1.1 404 NOT FOUND\r\n"
                response_msg += "\r\n"
                response_msg += "<h1>----file not found----</h1>"
                new_socket.send(response_msg.encode("utf-8"))
            else:
                html_content = fp.read()
                fp.close()
                # 响应正确 200 OK
                response_msg = "HTTP/1.1 200 OK\r\n"
                response_msg += "\r\n"

                # 返回响应头
                new_socket.send(response_msg.encode("utf-8"))
                # 返回响应体
                new_socket.send(html_content)
        else:
            env = dict()
            body = mini_frame.application(env, self.set_response_header)

            # 将框架返回的status组合进response header中
            header = "HTTP/1.1 %s\r\n" % self.status
            # 将框架返回的响应头键值对加入到header中
            for temp in self.headers:
                header += "%s:%s\r\n" % (temp[0], temp[1])
            # 最后加上一个分割行
            header += "\r\n"

            # 将响应体数据加在header后面
            response = header + body
            # 返回给客户端
            new_socket.send(response.encode("utf-8"))

        # 关闭该次socket连接
        new_socket.close()

    # 该成员函数提供给web框架的applicaiton函数调用,并将status,headers设置到服务器中
    def set_response_header(self, status, headers):
        self.status = status
        self.headers = headers

    # 开始无限循环,接受请求
    def run_forever(self):
        while True:
            new_socket, client_addr = self.tcp_server_socket.accept()
            # 启动一个子进程来处理客户端的请求
            sub_p = multiprocessing.Process(target=self.handle_request, args=(new_socket,))
            sub_p.start()
            # 这里要关闭父进程中的new_socket,因为创建子进程会复制一份new_socket给子进程
            new_socket.close()

        # 关闭整个SOCKET
        tcp_server_socket.close()

def main():
    wsgi_server = WSGIServer()
    wsgi_server.run_forever()

if __name__ == "__main__":
    main()

解释:

1.代码的开头导入了mini_frame模块,这样导入显然是不太合理的,因为我们使用开源的服务器,不可能直接导入我们的框架代码。我们在后续版本中会解决这个问题。

2.动态请求的逻辑代码中,服务器调用mini_frame.application,这里传递了一个空的env字典(后续版本会将请求信息通过env传递给mini_frame)。

3.服务器定义了一个成员函数,set_response_header(),并将其引用传递给了application函

4.在mini_frame.application中,框架通过set_response_header()函数向服务器返回了response header(分为status和headers列表)。

5.服务器通过拼接self.status和self.headers,得到真正的response header。

6.application返回了一个固定的字符串(Hello World..这个字符串模拟response body),并将其串接在response header后,形成最终的响应数据。

八、

原文地址:https://www.cnblogs.com/leokale-zz/p/11966302.html

时间: 2024-10-11 18:53:08

Python实现简易HTTP服务器与MINI WEB框架(利用WSGI实现服务器与框架解耦)的相关文章

[Python WEB开发] 使用WSGI开发类Flask框架 (二)

WSGI     Web服务器网关接口 WSGI主要规定了Web服务器如何与Web应用程序进行通信,以及如何将Web应用程序链接在一起来处理一个请求. wsgiref Python中的WSGI参考模块 一.WSGI 应用程序端: 1. 根据WSGI定义,应用程序应该是可调用对象 2.该可调用对象必须有两个固定参数:一个是含有服务器环境变量的字典,另一个是可调用对象,该对象使用HTTP状态码和会返回给客户端的HTTP头来初始化响应 environ 变量包含一些熟悉的环境变量,如HTTP_HOST,

python搭建简易服务器实例参考

有关python搭建简易服务器的方法. 需求分析: 省油宝用户数 已经破了6000,原有的静态报表 已经变得臃肿不堪, 每次打开都要缓上半天,甚至浏览器直接挂掉 采用python搭建一个最最简易的 web 服务 请求一个nick 就返回 对应的 报表数据 参数用GET方式传送 调研与实现: 园里没找到靠谱的,google了半天,最终还是成功了. 以下是源码,里面记录了 其中的 一些问题 复制代码 代码如下: #! /usr/bin/env python # -*- coding: utf-8 -

Mini WEB服务器设计

MINI WEB服务器设计 以下是曾经Watchmen一个朋友学习网络编程时设计的一个简单的MiniWEB服务器.以下是其工作计划: 1.查看HTTP/1.0协议 参见:RFC1945, <>第9章 2.配置文件格式定义<详见minim.conf> option=val1, val2,... # 注释文本 #选项集开始 option=val #选项值定义 #选项集结束 3.定义所要实现的功能 主功能: 3.1 日志记录 服务器程序日志:由配置文件minim.conf的全局变量(is

Python编写简易木马程序(转载乌云)

Python编写简易木马程序 light · 2015/01/26 10:07 0x00 准备 文章内容仅供学习研究.切勿用于非法用途! 这次我们使用Python编写一个具有键盘记录.截屏以及通信功能的简易木马.依然选用Sublime text2 +JEDI(python自动补全插件)来撸代码,安装配置JEDI插件可以参照这里: /tips/?id=4413 首先准备好我们需要的依赖库,python hook和pythoncom. 下载安装python hook 下载安装pythoncom模块:

mini web框架-2-显示页面

dynamic/my_web.py (更新) import time import os template_root = "./templates" def index(file_name): """返回index.py需要的页面内容""" # return "hahha" + os.getcwd() # for test 路径问题 try: file_name = file_name.replace(&q

为 Python Server Pages 和 Oracle 构建快速 Web 开发环境。

为 Python Server Pages 和 Oracle 构建快速 Web 开发环境. - 在水一方 - 博客频道 - CSDN.NET 为 Python Server Pages 和 Oracle 构建快速 Web 开发环境. 分类: 技术空间 2008-06-12 10:43 301人阅读 评论(0) 收藏 举报 pythonoracleserverwebapache数据库 目录(?)[+] Python 和 Python server Pages 的背景 解决方案组件 oracle 数

用Python+Django在Eclipse环境下开发web网站【转】

一.创建一个项目如果这是你第一次使用Django,那么你必须进行一些初始设置.也就是通过自动生成代码来建立一个Django项目--一个Django项目的设置集,包含了数据库配置.Django详细选项设置和应用 特性配置,具体操作步骤如下所示. 1.新建Django项目选择sqlite数据库 2.创建网站模块app 3.测试新建的模块是否正常 Validating models... 0 errors found March 12, 2014 - 10:26:53 Django version 1

Python Web 应用:WSGI基础

在Django,Flask,Bottle和其他一切Python web 框架底层的是Web Server Gateway Interface,简称WSGI.WSGI对Python来说就像 Servlets对Java一样——一种用于web服务器并允许不同web服务器和应用框架基于通用API交互的通用规范.然而,对于大多数事情,Python版本实现相当简单. WSGI被定义在PEP 3333协议里面,如果在读完本文之后你想学到更多东西的话,作者建议读者先阅读一下简介. 本文将从一个应用开发者的角度来

Parallel Python——一个简易的分布式计算系统

如何搭建一个快速的分布式计算平台?Parallel python提供了简易的方式来实现此目的. Parallel Python(http://www.parallelpython.com/content/view/15/30/#QUICKCLUSTERS)是Python进行分布式计算的开源模块,能够将计算压力分布到多核CPU或集群的多台计算机上,能够非常方便的在内网中搭建一个自组织的分布式计算平台. 在不同节点运行服务器程序,并自动发现运行服务器的节点,命令如下: node-1> ./ppser