装饰器本质上是一个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'))