模拟建立小型apache服务器
闲余时间研究了一下apache,饶有兴趣的模仿着写了个简化版的,可以完成基本需求的。
应用事例
在同一IP和端口上配置三家公司的服务器,可以通过不同的域名访问不同的服务器。其中A公司的访问量较大,使用反向代理实现负载均衡,如下图所示。
用到的技术:负载均衡反向代理
反向代理负载均衡普通代理方式是代理内部网络用户访问internet上服务器的连接请求,客户端必须指定代理服务器,并将本来要直接发送到internet上服务器的连接请求发送给代理服务器处理。反向代理(Reverse Proxy)方式是指以代理服务器来接受internet上的连接请求,然后将请求转发给内部网络上的服务器,并将从服务器上得到的结果返回给internet上请求连接的客户端,此时代理服务器对外就表现为一个服务器。反向代理负载均衡技术是把将来自internet上的连接请求以反向代理的方式动态地转发给内部网络上的多台服务器进行处理,从而达到负载均衡的目的。
用到的技术:静态web服务
客户端发送请求,tcp三次握手,握手成功后发起http request包,服务器收到后利用正则获取 request 头部信息。抓取 url 中的 path 资源路径。连接默认documentroot 路径
形成完整路径,申请 fd 访问资源内容,构建 response报头+内容进行 send 实现访问。
源码展示
包组织
配置文件
[default]
ip = 172.16.2.12
port = 8080
documentroot=E:\补课学习\补课学习\8月26\apatcp
redirect="/stock/a.html" "172.16.2.12:8080/stock/a.html"
[www.a.com]
ip = 172.16.2.12
port = 8080
documentroot=E:\补课学习\补课学习\8月26\apatcpa
#upstream=True
[www.b.com]
ip = 172.16.2.12
port = 8080
documentroot=E:\补课学习\补课学习\8月26\apatcpb
deny=\stock
[www.c.com]
ip = 172.16.2.12
port = 8080
documentroot=E:\补课学习\补课学习\8月26\apatcpc
deny=\stock\a.html
[upstream:www.a.com]
algo = leastsession
server01 = 172.16.2.12:8080
server02 = 172.16.2.12:8080
server03 = 172.16.2.12:8080
源码
apache.py模块
class server:
def __init__(self,ip,port):
self.socket = socket.socket()
self.iport = (ip,port)
self.event = threading.Event()
self.serverroot = "E:\\补课学习\\补课学习\\8月26\\apatcp"
self.clients = {}
self.webserver = {}
self.num = 0
# self.config = Confread(contents="E:\\python-3.7.0\\conf")
def reg(self,bytestring,sok):
regx = re.compile("GET (?P<url>\S+) (?P<method>\S+)\r\nHost: (?P<host>\S+)\r\n.*")
data = {}
dic = read("peizhi.conf")
tag = False
try:
data = regx.match(bytestring.decode("utf-8")).groupdict()
domain,port = data["host"].split(":")
if domain[0:1] in "0123456789" or domain not in dic.keys():
documentroot = self.serverroot
else:
documentroot = dic[domain]["documentroot"]
tag = dic[domain].get("upstream",False)
if tag:
name = threading.current_thread().getName()
self.clients[name] = sok
self.addserver(domain)
if data["url"] == "/":
# redirect = dic[domain]["redirect"]
data["url"] = Path(documentroot + "/" + "index.html")
else:
p = Path(documentroot + "/" + data["url"])
if p.exists() and p.is_file():
data["url"] = p
else:
data["url"] = Path(documentroot + "/" + "error.html")
pathfile = str(data["url"])[len(documentroot) + 1:]
print(pathfile)
except Exception as f:
longer.info(f)
return data,tag,pathfile
def addserver(self,domain):
servers = dic["upstream:{}".format(domain)]
for key in servers.keys():
if not key.endswith("w") and key.startswith("s"):
self.webserver[servers[key]] = 0
def algo(self):
if len(self.clients) < 10:
for server in self.webserver.keys():
return server
lst = sorted(self.webserver.items(),key=lambda x:x[1])
return lst[0][0]
def resdata(self,data,tag,pathfile):
string = ""
# <html>
# <head>
# <meta http-equiv="refresh" content="0; url='http://172.16.2.12:8080/stock/a.html'" />
# <title><h1>301 Moved Permanently</h1></title>
# </head>
# </html>
# """
if not tag:
with open(data["url"],"r") as f:
lines = f.readlines()
for line in lines:
string = string + line
else:
server = self.algo()
url = "http://{}/{}".format(self.algo(),pathfile)
# url = "http://www.baidu.com"
self.webserver[server] += 1
print(url)
string = requests.get(url).content.decode("utf-8")
self.webserver[server] -= 1
return string
def response(self,string):
head = '''HTTP/1.1 200 OK
Connection: keep-alive
Data: Thur, 22 Mar ''' + "500" + ''' GMT
Server:PythonWebServer 1.0
Last-Modified: Thur, 22 Mar 2017 19:26:03 GMT
Content-Lenth: ''' + str(len(string)) + '\nContent-Type:text/html\n\n'
# "<html>
# <head>
# <meta http-equiv="refresh" content="0; url='http://www.baidu.com'" />
# <title><h1>301 Moved Permanently</h1></title>
# </head>
# </html>
# """
databytes = (head + string).encode("utf-8")
return databytes
def start(self):
self.socket.bind(self.iport)
self.socket.listen()
threading.Thread(target=self.accept).start()
def accept(self):
while not self.event.is_set():
try:
sok, radd = self.socket.accept()
self.num += 1
print(sok,radd)
longer.info("sok:{},radd:{}".format(sok,radd))
threading.Thread(target=self.reves, args=(sok,),name="thread{}".format(self.num)).start()
except OSError:
break
def reves(self, sok):
try:
data = sok.recv(1024)
requestdict, tag, pathfile = self.reg(data, sok)
bstring = self.response(self.resdata(requestdict, tag, pathfile))
if bstring:
sok.send(bstring)
self.clients.pop(threading.current_thread().getName())
sok.close()
except Exception as f:
longer.info(f)
def stop(self):
self.event.set()
self.socket.close()
return True
if __name__ == "__main__":
Server = server("172.16.2.12",8080)
Server.start()
while True:
cmd = input("config#")
if cmd == "quit" or cmd =="退出":
Server.stop()
break
print("lll:",cmd.strip("? ?"))
confread.py读配置模块
import configparser
import json
import pathlib
class Confread:
def __init__(self,file=None,jfile=None,contents=None,suffix=None):
self.file = file
self.contet = contents
self.suffix = suffix
self.jfile = jfile
self.parser = configparser.ConfigParser()
def pread(self):
self.parser.read(self.file)
return self.parser
def read(file):#self,
confdict = {}
#key = "line"
with open(file,"r",encoding="utf-8")as f:
lines = f.readlines()
lines = filter(lambda x: not x.startswith("\n"),lines)
lines = filter(lambda x: not x.startswith("#"),lines)
for line in lines:
if line.startswith("["):
key = line.strip("[ ] \n")
confdict[key] = {}
else:
k,v = line.strip('\n').split('=')
confdict[key][k] = v
return confdict
p = pathlib.Path(self.content)
if p.is_dir():
for file in p.iterdir():
if file.suffix ==".conf":
confdict.update(self.read(file))
return confdict
cc = Confread(contents)
longer.py日志模块
import logging
def log():
logger = logging.getLogger(__name__)
logger.setLevel(logging.INFO)
handler = logging.FileHandler("file.log",mode='w')
formatter = logging.Formatter("%(asctime)s - %(name)s - %(levelname)s - %(message)s")
handler.setFormatter(formatter)
logger.addHandler(handler)
return logger
longer = log()