目录
介绍(单一功能Demo介绍)
1.Demo(静态固定资源模拟)
2.Demo(静态路径定位资源模拟)
3.抽象(静态资源)Demo
Web服务器框架Demo
1.未解耦Demo
2.解耦最终Web服务器框架Demo
DJango如何运行(WSGI & uwsgi)
上一个文章介绍了TCP,UDP,HTTP等相关的原理和访问流程,这里主要通过Demo记录一下
网络底层原理介绍
import socket
from multiprocessing import Process
def handleBindClientSer(clientSocket):
# 客户端请求数据接收逐行打印
request_data = clientSocket.recv(1024)
requestDt = request_data.splitlines()
for line in requestDt:
print("*"*50)
print(line)
# 伪造服务端响应数据
respons_start_line = "HTTP/ 200 OK\r\n"
respons_header = "Server:My Server\r\n"
respons_body = "Hello Mikejing
"
respons = respons_start_line + respons_header + "\r\n" + respons_body
# 发送数据给客户端
clientSocket.send(bytes(respons,"utf-8"))
# 关闭连接
clientSocket.close()
if __name__ == "__main__":
# 创建服务器socket
serverSocket = socket.socket(socket.AF_INET,socket.SOCK_STREAM)
# 确保socket端口重用
serverSocket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
# 绑定默认 ip 和 端口
serverSocket.bind(("",8000))
# 开启监听模式
serverSocket.listen(128)
while True:
# 客户端连接
client_socket,client_address = serverSocket.accept()
print("[%s--%s]"%client_address)
# 创建进程并开启
client_process = Process(target=handleBindClientSer,args=(client_socket,))
client_process.start()
# 关闭客服端一次引用
client_socket.close()
这是一个简单的本地服务,通过Process进程的方式监听,我们模拟HTTP的报文格式,报文数据的格式都是以\r\n结束,body数据是检测到两套\r\n,这样的报文数据才能被识别和解析
开启服务之后,通过网络调试助手来模拟
还可以通过浏览器来访问本地服务,Body里面的内容就会被浏览器解析并且渲染,所有的报文数据,在body之前都会有两个\r\n
[127.0.0.1--58235]
**************************************************
b'GET / HTTP/1.1'
**************************************************
b'Host: 127.0.0.1:8000'
**************************************************
b'Connection: keep-alive'
**************************************************
b'Cache-Control: max-age=0'
**************************************************
b'Upgrade-Insecure-Requests: 1'
**************************************************
b'User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_13_4) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/67.0.3396.99 Safari/537.36'
**************************************************
b'Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8'
**************************************************
b'Accept-Encoding: gzip, deflate, br'
**************************************************
b'Accept-Language: zh-CN,zh;q=0.9'
**************************************************
b''
import socket
import re
from multiprocessing import Process
HTML_ROOT_DIR = "./source"
def handleBindClientSer(clientSocket):
# 客户端请求数据接收逐行打印
request_data = clientSocket.recv(1024)
# 换行分割
requestDt = request_data.splitlines()
for line in requestDt:
print("*"*50)
print(line)
httpHeaderMethodLine = requestDt[0]
fileName = re.match("[^/]+(/[^ ]*)", httpHeaderMethodLine.decode("utf-8")).group(1)
print("file path is ===>%s"%fileName)
if fileName == "/":
fileName = HTML_ROOT_DIR + "/index.html"
else:
fileName = HTML_ROOT_DIR + fileName
try:
file = open(fileName,"rb")
except Exception as e:
respons_start_line = "HTTP/1.1 404 Not Found\r\n"
respons_header = "Server:My Server\r\n"
respons_body = "The file is not found"
else:
file_data = file.read()
file.close()
respons_start_line = "HTTP/1.1 200 OK\r\n"
respons_header = "Server:My Server\r\n"
respons_body = file_data.decode("utf-8")
finally:
respons = respons_start_line + respons_header + "\r\n" + respons_body
# 发送数据给客户端
clientSocket.send(bytes(respons,"utf-8"))
# 关闭连接
clientSocket.close()
if __name__ == "__main__":
# 创建服务器socket
serverSocket = socket.socket(socket.AF_INET,socket.SOCK_STREAM)
# 确保socket端口重用
serverSocket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
# 绑定默认 ip 和 端口
serverSocket.bind(("",8000))
# 开启监听模式
serverSocket.listen(128)
while True:
# 客户端连接
client_socket,client_address = serverSocket.accept()
print("[%s--%s]"%client_address)
# 创建进程并开启
client_process = Process(target=handleBindClientSer,args=(client_socket,))
client_process.start()
# 关闭客服端一次引用
client_socket.close()
http://127.0.0.1:8000/ 或者 http://127.0.0.1:8000/index.html访问
import socket
import re
import sys
from multiprocessing import Process
HTML_ROOT_DIR = "./source"
class HttpServer(object):
""""""
def __init__(self,port):
super(HttpServer, self).__init__()
self.serverSocket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
# 确保socket端口重用
self.serverSocket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
# 绑定默认 ip 和 端口
self.serverSocket.bind(("", port))
def startServer(self):
self.serverSocket.listen(128)
while True:
# 客户端连接
client_socket, client_address = self.serverSocket.accept()
print("[%s--%s]" % client_address)
# 创建进程并开启
client_process = Process(target=self.handleBindClientSer, args=(client_socket,))
client_process.start()
# 关闭客服端一次引用
client_socket.close()
def handleBindClientSer(self,clientSocket):
# 客户端请求数据接收逐行打印
request_data = clientSocket.recv(1024)
# 换行分割
requestDt = request_data\
.splitlines()
for line in requestDt:
print("*" * 50)
print(line)
httpHeaderMethodLine = requestDt[0]
fileName = re.match("[^/]+(/[^ ]*)", httpHeaderMethodLine.decode("utf-8")).group(1)
print("file path is ===>%s" % fileName)
if fileName == "/":
fileName = HTML_ROOT_DIR + "/index.html"
else:
fileName = HTML_ROOT_DIR + fileName
try:
file = open(fileName, "rb")
except Exception as e:
respons_start_line = "HTTP/1.1 404 Not Found\r\n"
respons_header = "Server:My Server\r\n"
respons_body = "The file is not found"
else:
file_data = file.read()
file.close()
respons_start_line = "HTTP/1.1 200 OK\r\n"
respons_header = "Server:My Server\r\n"
respons_body = file_data.decode("utf-8")
finally:
respons = respons_start_line + respons_header + "\r\n" + respons_body
print(respons)
# 发送数据给客户端
clientSocket.send(bytes(respons, "utf-8"))
# 关闭连接
clientSocket.close()
def main():
httpServer = HttpServer(8000)
httpServer.startServer()
if __name__ == "__main__":
main()
import socket
import re
from multiprocessing import Process
import sys
# import wgsipython # 这里导入包是一个知识点
HTML_ROOT_DIR = "./source"
PYTHON_ROOT_DIR = "./wgsipython/"
class HttpServer(object):
""""""
def __init__(self,port):
super(HttpServer, self).__init__()
self.serverSocket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
# 确保socket端口重用
self.serverSocket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
# 绑定默认 ip 和 端口
self.serverSocket.bind(("", port))
# 回调保存header属性
self.respons_header = ""
def startServer(self):
self.serverSocket.listen(128)
while True:
# 客户端连接
client_socket, client_address = self.serverSocket.accept()
print("[%s--%s]" % client_address)
# 创建进程并开启
client_process = Process(target=self.handleBindClientSer, args=(client_socket,))
client_process.start()
# 关闭客服端一次引用
client_socket.close()
# 拼接响应头
def start_response(self,status,headers):
responsHeader = "HTTP/1.1" + status + "\r\n"
for header in headers:
responsHeader += "%s:%s\r\n"%header
self.respons_header = responsHeader
def handleBindClientSer(self,clientSocket):
# 客户端请求数据接收逐行打印
request_data = clientSocket.recv(1024)
# 换行分割
requestDt = request_data.splitlines()
for line in requestDt:
print("*" * 50)
print(line)
httpHeaderMethodLine = requestDt[0]
fileName = re.match("[^/]+(/[^ ]*)", httpHeaderMethodLine.decode("utf-8")).group(1)
print("file path is ===>%s" % fileName)
# 判断脚本还是静态资源
if fileName.endswith(".py"):
# 该方法和上面直接import xxx一样的
print("fileName-->%s"%fileName[1:-3])
try:
m = __import__(fileName[1:-3])
except Exception as e:
print(e)
self.respons_header = "HTTP/1.1 404 NOT FOUND \r\n"
respons_body = "NOT FOUND"
else:
# 解析报文头部的一些参数可以通过env传递
env = {}
respons_body = m.application(env,self.start_response)
print("返回数据\r\n%s"%respons_body)
finally:
pass
respons = self.respons_header + "\r\n" + respons_body
else:
if fileName == "/":
fileName = HTML_ROOT_DIR + "/index.html"
else:
fileName = HTML_ROOT_DIR + fileName
try:
file = open(fileName, "rb")
except Exception as e:
respons_start_line = "HTTP/1.1 404 Not Found\r\n"
respons_header = "Server:My Server\r\n"
respons_body = "The file is not found"
else:
file_data = file.read()
file.close()
respons_start_line = "HTTP/1.1 200 OK\r\n"
respons_header = "Server:My Server\r\n"
respons_body = file_data.decode("utf-8")
finally:
respons = respons_start_line + respons_header + "\r\n" + respons_body
print(respons)
# 发送数据给客户端
clientSocket.send(bytes(respons, "utf-8"))
# 关闭连接
clientSocket.close()
def main():
sys.path.insert(1,PYTHON_ROOT_DIR)
# 包的查找模块是在path目录下面查找的,因此需要在模块下面插入指定路径
httpServer = HttpServer(8000)
httpServer.startServer()
if __name__ == "__main__":
main()
这里直接把开启Web服务和解析请求都放在了服务器里面,脚本文件和静态文件有各自的路径,根据解析分别执行或者展示,通过过渡,在来看下最终版本的解耦Demo
├── __pycache__
│ └── webFramework.cpython-36.pyc
├── server.py
├── source 静态文件
│ ├── bird.jpg
│ └── index.html
├── webFramework.py web服务解析框架
├── webServer.py web服务器
├── wgsiServer.py
└── wgsipython py脚本
├── __init__.py
├── __pycache__
│ ├── __init__.cpython-36.pyc
│ ├── mkjTime.cpython-36.pyc
│ └── sayHello.cpython-36.pyc
├── mkjTime.py
└── sayHello.py
服务器Demo
import socket
import re
from multiprocessing import Process
import sys
# import wgsipython # 这里导入包是一个知识点
PYTHON_ROOT_DIR = "./wgsipython/"
class HttpServer(object):
"""web服务器框架"""
def __init__(self,port,application):
super(HttpServer, self).__init__()
self.serverSocket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
# 确保socket端口重用
self.serverSocket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
# 绑定默认 ip 和 端口
self.serverSocket.bind(("", port))
# 保存web框架对象
self.app = application
# 回调保存header属性
self.respons_header = ""
def startServer(self):
self.serverSocket.listen(128)
while True:
# 客户端连接
client_socket, client_address = self.serverSocket.accept()
print("[%s--%s]" % client_address)
# 创建进程并开启
client_process = Process(target=self.handleBindClientSer, args=(client_socket,))
client_process.start()
# 关闭客服端一次引用
client_socket.close()
# 拼接响应头
def start_response(self,status,headers):
responsHeader = "HTTP/1.1" + status + "\r\n"
for header in headers:
responsHeader += "%s:%s\r\n"%header
self.respons_header = responsHeader
def handleBindClientSer(self,clientSocket):
# 客户端请求数据接收逐行打印
request_data = clientSocket.recv(1024)
# 换行分割
requestDt = request_data.splitlines()
for line in requestDt:
print("*" * 50)
print(line)
# 获取第一行请求信息 HTTP 1.1 路径信息
httpHeaderMethodLine = requestDt[0]
fileName = re.match("[^/]+(/[^ ]*)", httpHeaderMethodLine.decode("utf-8")).group(1)
method = re.match("[^/]+(/[^ ]*)", httpHeaderMethodLine.decode("utf-8")).group(1)
print("file path is ===>%s" % fileName)
# 构造字典参数
env = {
"PATH_INFO" : fileName,
"METHOD" : method
}
# 传给对象并给回调函数组装响应头
respons_body = self.app(env,self.start_response)
# 拼接response
respons = self.respons_header + "\r\n" + respons_body
# 发送数据给客户端
clientSocket.send(bytes(respons, "utf-8"))
# 关闭连接
clientSocket.close()
def main():
# python3 xxx.py 参数1 参数2 如果无参数,报错
if len(sys.argv) < 2:
sys.exit("Error Python3 webServer.py Module(web模块):argvName(模块属性名)")
# 包的查找模块是在path目录下面查找的,因此需要在模块下面插入指定路径
sys.path.insert(1,PYTHON_ROOT_DIR)
# 获取第一个参数
appPath = sys.argv[1]
# webFramework:app 分割
module,argvName = appPath.split(":")
# 动态导入模块
module = __import__(module)
# 根据属性获取模块内对象
app = getattr(module,argvName)
# 启动服务并传入web服务对象
httpServer = HttpServer(8000,app)
httpServer.startServer()
if __name__ == "__main__":
main()
WebFramework
import time
HTML_ROOT_DIR = "./source"
class Application(object):
"""docstring for Application"""
def __init__(self, urls):
super(Application, self).__init__()
# 保存路由信息
self.urls = urls
# __call__外部通过 object()的方式直接调用即可
def __call__(self,env,respons_header_callback):
path = env.get("PATH_INFO","/")
# /static/index.html 静态文件
if path.startswith("/static"):
file_name = path[7:]
if file_name == "" or file_name == "/":
file_name = "/index.html"
try:
file = open(HTML_ROOT_DIR + file_name, "rb")
except IOError:
status = "404 Not Found"
headers = []
respons_header_callback(status,headers)
return "Not Found"
else:
file_data = file.read()
file.close()
status = "200 OK"
headers = []
respons_header_callback(status,headers)
return file_data.decode("utf-8")
# 脚本
for url,handler in self.urls:
print("%s---%s"%(url,handler))
if url == path:
print("匹配成功--->%s"%path)
return handler(env,respons_header_callback)
# 找不到
status = "404 Not Found"
headers = []
respons_header_callback(status,headers)
return "Not Found"
# 脚本执行函数
def showTime(env,respons_header_callback):
status = "200 OK"
headers = [
("Content-Type","text/plain")
]
respons_header_callback(status,headers)
return time.ctime()
def sayHello(env,respons_header_callback):
status = "200 OK"
headers = [
("Content-Type","text-plain")
]
respons_header_callback(status,headers)
return "mikejing"
# 路由表
urls = [
("/",showTime),
("/mkjTime",showTime),
("/sayHello",sayHello)
]
# __import__的时候创建对象给别人通过getAttr获取
app = Application(urls)
至此,两个文件解耦完毕,通过调用
python3 webServer.py webFramework(框架名称):app(框架内可供使用的对象名)
http://127.0.0.1:8000/mkjTime 执行脚本访问
http://127.0.0.1:8000/static/index.html 访问静态文件
web服务器通过开启TCP Socket,监听HTTP请求,期间通过sys.argv的方式拿到的参数获取framework参数进行请求路径的解析,根据回调函数处理响应头,再由服务器拼接返回给客户端
https://blog.csdn.net/geekz93/article/details/53246479
https://www.jianshu.com/p/679dee0a4193
web client <-> web server <-> the socket <-> uwsgi <-> Django
对web通信过程的理解:用户在浏览器输入网址http://www.localhost:8000
按下回车后浏览器所做的事情就是将页面数据以http
协议规定的格式发给ngnix,ngnix收到数据后解析,如果是静态请求(如访问图片,视频等文件)就直接将文件包装成http格式返回给浏览器,用户就可以直接看到内容了;如果是动态请求(如刷新验证码) 动态和静态的区别,ngnix将请求通过socket发送给uwsgi,uwsgi解析http协议,将数据部分以wsgi协议规定的格式发送给Django;Django解析、处理数据,将处理的结果以wsgi格式发送给uwsgi;uwsgi收到处理结果后通过socket发送至nginx,nginx将结果包装成http格式发送给浏览器完成整个通信