基于Python装饰器实现HTTP服务器

装饰器本质上是一个Python函数,它可以让其他函数在不需要做任何代码变动的前提下增加额外功能,装饰器的返回值也是一个函数对象。它经常用于有切面需求的场景,比如:插入日志、性能测试、事务处理、缓存、权限校验等场景。装饰器是解决这类问题的绝佳设计,有了装饰器,我们就可以抽离出大量与函数功能本身无关的雷同代码并继续重用。

一、服务器代码部分                                          

from socket import *
from select import *
import os,mimetypes,json,inspect,re,multipart,threading,math
from collections import deque
from concurrent.futures import ThreadPoolExecutor

class Cookie:
        def __init__(self,name:str=None,value:str=None
                     ,path:str=None,HttpOnly:bool=False
                     ,domain:str=None,maxAge:int=0) -> None:
            self.name = name
            self.value = value
            self.path = path
            self.HttpOnly = HttpOnly
            self.domain = domain
            self.maxAge = maxAge
            #multipart.parse_options_header()
            # multipart.BytesIO()
            # multipart.parse_qs()
        def __str__(self) -> str:
            data=['%s=%s' % (self.name,self.value)]
            if self.path and len(self.path)>0:
                data.append(' path=%s' % self.path)
            if self.maxAge>0:
                data.append(' Max-Age=%s' % self.maxAge)
            if self.domain and len(self.domain)>0:
                data.append(' domain=%s' % self.domain)
            if self.HttpOnly:
                data.append(' HttpOnly')
            return ';'.join(data)
        


class Request:
    GET = 'GET'
    POST = 'POST'
    DELETE = 'DELETE'
    PUT = 'PUT'
    def __init__(self,data:bytes) -> None:
        data_line = data[0:data.find(b'\r\n'*2)].decode(encoding='utf-8').splitlines()
        #data_line = bytes.decode(data,encoding='utf-8')
        #print(data_line[0].split(' '))
        method,path,version = data_line[0].split(' ')
        self.method = method
        self.path = path
        self.url = self.urls()
        self.version = version
        self.headers = {val[0:val.find(':')].strip():val[val.find(':')+1:].strip() for val in data_line[1:]}
        # for key,val in self.headers.items():
        #     setattr(self,key,val)

    def getParams(self,keys:tuple[str]=())->dict[str,str]:
        params = {}
        index1,index2 = self.__splits('?',1),self.__splits('/',2)
        if self.method=='GET':
            if index1>0:
                params = {val.split('=',2)[0]:val.split('=',2)[1] for val in self.path[index1+1:].split('&')}
            elif index2>0 and len(keys)>0:
                lists = self.path[index2+1:].split('/')
                a,b = len(keys),len(lists)
                lengths = b if a>b else a
                params = {keys[val]:lists[val] for val in range(lengths)}
        elif re.match('(?:POST|PUT|DELETE)',self.method,re.I):
            pass
        return params

    def __str__(self) -> str:
        methods = {k:v for k,v in vars(self).items() if not callable(getattr(self,k)) and not k.startswith('__')}
        return '\n'.join(['%s : %s'%(k,v) for k,v in methods.items()])

    def getHeader(self,key:str)->str: 
        self.headers.get(key)

    def getCookie(self,key:str)->str: 
        if 'Cookie' not in self.headers:return None
        for val in self.headers['Cookie'].split(';'):
            k,v = val.split('=')
            if key==k:return v

    def __splits(self,val:str,count:int)->int: 
        index,count2= -1,0
        for index2 in range(len(self.path)):
            if val==self.path[index2]:count2+=1
            if count2==count:
                index = index2
                break
        return index if count>0 else count2
    
    def urls(self)->str: 
        urls:str = None
        index1,index2 = self.__splits('/',2),self.path.find('?')
        if index1>0:
            urls = self.path[1:index1]
        elif index2>0:
            urls = self.path[1:index2]
        else:
            urls = self.path[1:]
        return urls


class Response:
    JOSN = 'JOSN'
    HTML = 'HTML'
    TEXT = 'TEXT'
    FILE = 'FILE'
    def __init__(self,clientSocket:socket,static:str,request:Request) -> None:
        self.static = static
        self.socket = clientSocket
        self.request = request
        self.headers = ['HTTP/1.1 200 OK',
                        'Server: YouFather',
                        'Vary: Accept-Encoding',
                        'Content-Type: text/html',
                        'Content-Length: 0'
                        ]
        
    def __sendHeaders(self) -> None:
        #print('__sendHeaders')
        join_str = "\r\n".join(self.headers)
        datas = f'{join_str}\r\n\r\n'.encode(encoding='utf-8')
        #print(f'datas = {datas}')
        self.socket.sendall(datas)
        #print('self.socket',self.socket)
        pass
    def addCookie(self,data:dict[str,str],cookie:Cookie) -> None:
        for k,v in data.items():
            cookie.name = k;cookie.value = v
            self.headers.append(f'Set-Cookie: {cookie}')
            pass
    def setHeader(self,header:dict[str,str]) -> None:
        for k1,v1 in header.items():
            if k1 not in [val.split(':')[0] for val in self.headers]:
                self.headers.append(f'{k1}: {v1}')
                continue
            for index in range(len(self.headers)):
                list = self.headers[index].split(':')  
                if k1 == list[0]:self.headers[index] = f'{k1}: {v1}'
        #[self.headers.append(f'{k}: {v}') for k,v in header.items()]
        pass
    def redirect(self,url:str) -> None:
        self.headers[0]='HTTP/1.1 302 FOUND'
        self.headers.append(f'Location: {url}')
        self.__sendHeaders()
        pass
    def status(self,code:int,msg:str) -> None:
        print('__sendstatusl')
        self.headers[0]=f'HTTP/1.1 {code} {msg}'
        self.__sendHeaders()
        pass
    def html(self,html:str) -> None:
        print('__sendhtml')
        static_paths = os.path.join(self.static,html)
        if not os.path.exists(static_paths):
            self.status(404,'NOT FOUND')
            return None
        header = {'Content-Type':'text/html;charset=utf-8','Content-Length':os.path.getsize(static_paths)}
        self.setHeader(header)
        self.__sendHeaders()
        self.socket.sendfile(static_paths)
        pass

    def file(self,name:str) -> None:
        #print('__sendfile')
        static_paths = os.path.join(self.static,name)
        if not os.path.exists(static_paths):
            self.status(404,'NOT FOUND')
            return None
        header = {
                'Content-Type':mimetypes.guess_type(static_paths)[0],
                  'Content-Length':os.path.getsize(static_paths),
                  'Connection':'keep-alive'
                  }
        range1,is_range = 0,self.request.headers.get('Range')
        if is_range and re.search('bytes=(\d+)-(\d+)?',is_range,re.I):
            range_list = is_range.split('=',2)[1].split('-')
            range1,range2 = int(range_list[0]),int(range_list[1]) if len(range_list)>1 and len(range_list[1])>0 else header['Content-Length']
            header['Content-Range'] = 'bytes %s-%s/%s'%(range1,range2-1,header['Content-Length'])
            header['Content-Length'] = (range2 - range1)
            self.headers[0]=f'HTTP/1.1 206 Partial Content'
        else:header['Accept-Ranges']='bytes'
        self.setHeader(header)
        self.__sendHeaders()
        blockSize = 1024*500
        count = math.ceil(header['Content-Length']/blockSize)
        print(f'count = {count}')
        try:
            with open(static_paths,'rb') as file:
                file.seek(range1)
                for index in range(1,count+1):
                    if header['Content-Length']>blockSize and index == count:
                        blockSize = header['Content-Length'] - blockSize*(index-1)
                    datas = file.read(blockSize)
                    self.socket.send(datas)
        except:
            pass
        #pass


    def text(self,text:str) -> None:
        print('__sendtext')
        datas = text.encode(encoding='utf-8')
        header = {'Content-Type':'text/plain;charset=utf-8',
                  'Content-Length':len(datas)}
        self.setHeader(header)
        self.__sendHeaders()
        self.socket.sendall(datas)
        pass
    def json(self,objs) -> None:
        print('__sendjson')
        datas = json.dumps(objs).encode(encoding='utf-8')
        header = {'Content-Type':'application/json;charset=utf-8',
                  'Content-Length':len(datas)}
        self.setHeader(header)
        self.__sendHeaders()
        self.socket.sendall(datas)
        pass






class HttpBaseServer(object):
    def app(self,path:str,method=Request.GET,result=Response.JOSN):
        def desder(func):
            self.mapping[path] = (func,method,result)
            print("self.mapping",self.mapping)
        return desder

    def strat(self):
        sock = socket(AF_INET, SOCK_STREAM)
        sock.bind(self.address)
        sock.listen(10)
        sock.setblocking(False)
        rlist = deque([sock])
        wlist = deque()
        xlist = deque()
        print(f'非阻塞服务器已创立 {self.address}')
        while True:
            try:
                read_list, write_list, error_list = select(rlist, wlist, xlist)
                for socket_item in read_list:
                    if socket_item == sock:
                        conn, addr = socket_item.accept()
                        conn.setblocking(True)  # 设置成非阻塞
                        rlist.append(conn)
                    else:
                        self.hander(socket_item)
                for write_item in write_list:
                    pass 
                for error_item in error_list:
                    error_item.close()
                    rlist.remove(error_item)
            except Exception as err:
                print(f'err = {err.args}')
                pass


    def hander(self,connfd:socket):
                connfd.setblocking(True)
                data = connfd.recv(1024*500)
                if not len(data)>0:
                    connfd.close()
                    return
                self.request = Request(data)
                self.response = Response(connfd,self.static,self.request)
                if self.request.url not in self.mapping:
                    self.response.status(404,'NOT FOUND')
                    return
                func,method,result = self.mapping[self.request.url]
                if method != self.request.method:
                    self.response.json({'code':500,'msg':f'method is not {method}'})
                    return
                signature = inspect.signature(func)
                params = self.request.getParams()
                params['request'] = self.request
                params['response'] = self.response
                parameters = [params.get(param_name) for param_name in signature.parameters]
                results = func(*parameters)
                {Response.JOSN:lambda:(self.response.json(results)) ,
                Response.TEXT:lambda:(self.response.text(results)),
                Response.HTML:lambda:(self.response.html(results)),
                Response.FILE:lambda:(self.response.file(results))
                }[result]()

    def __init__(self,address=('localhost',8084),static='D:/桌面/2023.10.10') -> None:
        self.executor = ThreadPoolExecutor(max_workers=20,thread_name_prefix='download_')
        self.mapping = {}
        self.address = address
        self.static = static
        t = threading.Thread(target=self.strat,args=())
        t.setDaemon(False)
        t.start()

一、测试部分代码

from HttpServer import *
# from tkinter import *
# from datetime import datetime
# from PIL import ImageGrab
# import tkinter,tkinter.filedialog as dialog,cv2,os,numpy as np,time

server = HttpBaseServer()

@server.app('index',method=Request.GET,result=Response.TEXT)
def test(name,age,request,response):
    print(f'name = {name} age = {age}')
    return f'name = {name} age = {age} request = {request} response = {response}'


@server.app('file',result=Response.FILE)
def test(request:Request,response:Response):
    #print(f'name = {request} age = {response}')
    return str(request.getParams(('name',)).get('name'))

 一、浏览器访问http://localhost:8084/index

  • 10
    点赞
  • 7
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值