用python实现轻量级的Web服务器框架

学校python大作业,结合刚结束的计算机网络课程,意图巩固已学过的知识,拓展底层相关工程知识水平及能力,用python从底层实现一个Web服务器框架,提供部分Web后端API,尽量向主流的Web开发框架的功能学习靠拢,框架命名为HuoServer。手动实现这个Web服务器框架并实现开源共享可以促进技术交流与个人水平成长。(这是个代码行数比较少的玩意,有点混子行为~~~)

以下部分文字为应付报告的官话,适当理解即可。

该轻量级Web服务器框架命名为HuoServer,支持cgi协议外部文件请求,支持Web容器类型的文件GET请求,同时也支持GET形式的JSON类型数据交互。

其Web容器请求文件仿照主流的Apache的功能实现,实现其最基本的按路径请求与访问服务器本地文件的功能,同时也可以作为服务器本地文件的索引功能以供搭建如个人博客、官方网站、资源平台等形式的网站,默认Web端口为80。

GET形式的JSON类型后端数据交互仿照主流的springboot的部分功能进行实现,对数据采用JSON化的格式处理,类似格式的路径处理以及参数处理。并给予API供框架使用用户进行调用,以实现根据请求与参数做到不同处理的后端模式。

cgi协议外部文件请求则是通过实现cgi程序接受格式化的信息,并根据具体要求完成如操作服务器本地脚本、向服务器扩展应用程序发送信息等操作,已实现静态html无法完成的部分操作,达到和目前众多Web服务器相同的功能。

要实现一个Web服务器的方法可以有多种,比如从socket底层结合多线程写起,也可以直接从封装好的http协议栈写起,这里采用后者,使用python封装好的http协议栈直接进行底层架构(可以理解为水平不够,也可以理解为有点懒),这样的好处在于避免了http协议复杂的封装过程,直接将工作重点转移到底层工程层面上,更有利于进行Web服务器实现与拓展。(一个课程大作业没有理由让我去从socket tcp开始手撸一个http协议栈,况且也不太会~~~)

学过计算机网络,且对Web技术稍微有理解的人对这个服务框架模型的工作机理不是很陌生的,大体的请求就是按照需求分析中的那几种,JSON类型、文件类型、CGI类型,然后根据范围逐步缩小的原则去筛选选择提供的服务类型,最后选定类型提供服务即可。

简要画了个流程图陈述了服务的工作流程:

具体的工作流程确定了下来,具体比较复杂的模块是下面的两个:

Web容器式文件访问:利用os库对目标路径进行判别处理,按照流程图的顺序进行以此判别,如果请求目标文件存在且路径正确,则通过读入方法全部读入并写入数据体中,然后对请求主机返回响应内容。

JSON类型后端交互:通过设置中转站进行定位,中转站定位路径所指示的操作方法入口,将传入的路径及参数格式化处理后交给用户的处理函数,经过相关操作后返回操作结果。

轮上了具体细节的设计与编码了,通过上面大体的交代,再稍微参考参考python的http操作类以及相关的工程写法,便可以开工了。老师有点苟,必须让画类图,在我看来,python这个语言在大多数时间中我根本没用过它的面向对象,JAVA才是面向对象的王者,既然让画类图,那就按照流程走一遍,把各个模块封装成类就可以了。画出的类图如下图:

大体上的模块就是这些了(其中有的类到后面都没用到,因为要么确实没有必要要么是直接用了静态方法去表示了,还是水平有限,python的面向对象真的用不来,还是JAVA比较舒服一点),现在根据这些模块分布去进行编码实现。

首先当然是把HuoServer的主体架构架构出来,先将HuoServerCaseBase这个请求处理的基类实现出来,它里面得有一个用于判断的标志量函数,即judgeFlag(),如果这个标志量为True则说明满足条件便进入该类的处理函数进行处理,反之则按照优先级向后移动,同时还有文件处理的方法,即为打开文件进行读入的处理方法,所以实现的代码如下:

class HuoServerCaseBase(object):
    def handle_file(self,handler):
        try:
            with open(handler.full_path,'rb') as reader:
                # 打开对应路径的文件
                content=reader.read()
            handler.send_content(content)
            # 将内容送入返回数据体
        except IOError as msg:
            # 打开文件读入失败的异常
            msg = "'{0}' cannot be read: {1}".format(handler.full_path, msg)
            handler.handle_error(msg)

    def judgeFlag(self, handler):
        # 判断方法
        assert False,Constant.ERROR_TIP

    def performFunc(self, handler):
        # 执行方法
        assert False,Constant.ERROR_TIP

以上代码中是和下面的HuoServerRequestHandler类相关联的,HuoServerRequestHandler类是继承自BaseHTTPRequestHandler的自定义HTTP处理类,参照上面的类图,他是运行服务的承载者,具体代码如下:

class HuoServerRequestHandler(BaseHTTPRequestHandler):
    #请求判别的处理优先级数组
    cases = [notExitPath(),cgiFile(),isAFile(),defaultIndexFile(),notAFile()]

    #GET请求的函数重写
    def do_GET(self):
        try:
            self.full_path = os.getcwd()+self.path
            for case in self.cases:
                if(case.judgeFlag(self)):
                    case.performFunc(self)
                    break
        #处理异常
        except Exception as msg:
            #写入错误处理函数并执行相关操作
            self.handle_error(msg)

    #错误显示页面html
    Error_Page = Constant.ERROR_PAGE

    #错误请求处理
    def handle_error(self,msg):
        content=self.Error_Page.format(path=self.path,msg=msg)
        self.send_content(content.encode('utf-8'),404)

    #返回数据体拼接
    def send_content(self,page,status=200,type="html"):
        self.send_response(status)
        self.send_header("Content-Type","text/"+type)
        self.send_header("Content-Length",str(len(page)))
        self.end_headers()
        self.wfile.write(page)

其中的do_GET方法是在每接受一次HTTP GET请求时触发的方法,将判断处理过程放在其中最合适了,其中的优先级数组参照类图中HuoServerCaseBase的5个子类,第一个notExitPath()有一个最重要的作用,是进行JSON数据的判断,所以加入了与中转站连接的处理形式和相关路径的判断,具体代码如下:

# 不存在该路径,检测是否为JSON类型的请求
class notExitPath(HuoServerCaseBase):

    #是否为JSON类型请求标志量
    isJsonGet = False

    #返回数据体的内容
    jsonData = 0
    def judgeFlag(self,handler):
        #当路径不存在是则可能是JSON请求
        if not os.path.exists(handler.full_path):
            #对路径进行分割
            temp = handler.path.split('?')
            if len(temp) == 1:
                self.jsonData = JsonServerFuncEdit.transfer(temp[0],"")
            elif len(temp) == 2:
                self.jsonData = JsonServerFuncEdit.transfer(temp[0],temp[1])
            else:
                return True
            if self.jsonData == 0:
                return True
            else:
                self.isJsonGet = True
                return True
        return False

    # 命令行检验路径的存在性
    def performFunc(self,handler):
        #如果不是JSON请求则抛出异常
        if not self.isJsonGet:
            raise Exception("{0} not found".format(handler.path))
        #否则完成JSON数据的返回
        else:
            #字典转换为字符串写入数据体,并返回相应的状态码
            handler.send_content(str(self.jsonData),self.jsonData.code,"json")

将路径与参数交给中转站处理,这里有一点注意,JSON请求的路径不能与已有文件的路径重合,不然会直接跳过中转站处理,这也为避免产生请求歧义上了保险。中转站负责将传递进来的路径进行索引查找,并且将传递进来的参数进行格式化处理,如果有符合的请求则进入用户自定义的操作方法,这里的switch类引用自http://code.activestate.com/recipes/410692/,具体代码如下:

#用户导入自己写的py文件
#import
#import
#import
#

#方便选择设置的switch类
#引自 http://code.activestate.com/recipes/410692/
class switch(object):

    def __init__(self, value):
        self.value = value
        self.fall = False

    def __iter__(self):
        yield self.match
        raise StopIteration

    def match(self, *args):
        if self.fall or not args:
            return True
        elif self.value in args:
            self.fall = True
            return True
        else:
            return False

#中转站函数,目前只适用于GET类型请求,不支持POST带请求体的类型请求
def transfer(path,parameters):

    #创建参数字典
    dic = {}

    #如果参数不为空则按照格式分割写入字典
    if parameters != "":
        paras = parameters.split('&')
        for i in range(0,len(paras)):
            tempItem = paras.split('=')
            dic[tempItem[0]] = tempItem[1]

    for case in switch(path):
        #中转站示例代码,根据路径选择操作函数,传入参数字典
        #if case('/pathA/pathB'):
        #    return testFunc(dic)

        #未查找到对应的路径
        if case():
            return 0
    return 0

如果不是JSON请求,且传入路径存在,则进行下面的判断,是否为cgi协议的请求,如果判断成功,则进行cgi协议的处理,执行cgiRunner()方法,代码如下:

#利用cgi协议处理外部文件
class cgiFile(HuoServerCaseBase):

    #cgi协议对外部文件进行判定
    def judgeFlag(self,handler):
        return os.path.isfile(handler.full_path) and handler.full_path.endswith(".py")

    #运行cgi处理函数
    def performFunc(self,handler):
        self.cgiRunner(handler)

    #cgi协议运行
    def cgiRunner(self,handler):
        data = subprocess.check_output(["python",handler.full_path],shell=False)
        handler.send_content(data)

接下来则是判别是否为一个文件,如果该文件存在则利用基类的文件读方法进行处理即可,代码如下:

#该路径是一个文件
class isAFile(HuoServerCaseBase):

    #命令行判断是否为文件
    def judgeFlag(self,handler):
        return os.path.isfile(handler.full_path)

    # 读文件并返回
    def performFunc(self,handler):
        self.handle_file(handler)

如果路径为空即为根url,这时HuoServer默认会指向一个本地路径,拥有首页的index.html文件,如果这个文件在本地不存在,那么则默认调用错误页面的显示,代码如下:

#访问根url的时候默认的主页html
class defaultIndexFile(HuoServerCaseBase):

    #处理默认页路径
    def judgeFlag(self,handler):
        handler.full_path = handler.full_path + Constant.DEFAULT_INDEX
        return True

    #针对不同情况的处理方式
    def performFunc(self,handler):
        #如果默认文件存在
        if os.path.isfile(handler.full_path):
            return self.handle_file(handler)
        else:
            handler.send_content(Constant.NO_INDEX.encode('utf-8'),404)

如果前面的都不符合要求,那就只能属于优先级最低的最后一种状况的了,即请求对象不存在,不是一个文件NotAFile(),那么默认返回错误的模板html,代码如下:

#该路径不是一个文件
class notAFile(HuoServerCaseBase):

    #最终判断不为文件
    def judgeFlag(self,handler):
        return True

    #抛出未知对象的异常
    def performFunc(self,handler):
        raise Exception("{0} unknown object".format(handler.path))

到此,整个框架的大体结构已经搭建的差不多了,剩下的就是进行封装与完善了,我们还需要一个客户端类供用户开启服务,以及一个自定义的异常类用于完善,顺便把默认的配置数据写到一个常量文件中,供程序调用即可。

1.加入POST类型请求,只有GET类型的Web服务器是很不够的,POST的功能以及特性是GET所不能比的,而且POST的实现其实很简单,即在do_POST()方法中进行操作,利用请求头部的长度值来从请求体中获取数据信息,大体如self.rfile.read(int(self.headers['content-length']))中获取数据信息,获取到信息之后,其他的操作就同GET的操作很类似了,仿照着写就可以了;

2.中转站寻找处理方法的形式具体看来确实可以解决问题,但是性能的低下是必须要考虑的问题,因为之前没有好的方法去索引操作函数,像springboot中用注解的方法去解决这个问题,注解可以很高效率的解决性能的问题,如果后期需要优化的话首先当然是从中转站上下手,大体方向即利用注解,或者采用其他更高效率的算法;

3.服务器的整体功能,现在只完成了可以接受最基础的少量请求的形式,如果遇到高并发,达到了拥塞状态,这就需要一个比较好分配算法,或者从流程上下功夫,优化请求处理的时间与判断步骤,如果遭到了恶意攻击,或者需要承载一个规模很大的项目时应该所具有的反应与调节功能,这些需要从现有的流行的Web服务器中去学习去借鉴。

  • 3
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值