Python3实现Web服务器框架思路Demo(五)

目录

介绍(单一功能Demo介绍)

1.Demo(静态固定资源模拟)

2.Demo(静态路径定位资源模拟)

3.抽象(静态资源)Demo

Web服务器框架Demo

1.未解耦Demo

2.解耦最终Web服务器框架Demo

DJango如何运行(WSGI & uwsgi)


介绍(单一功能Demo介绍)

上一个文章介绍了TCP,UDP,HTTP等相关的原理和访问流程,这里主要通过Demo记录一下

网络底层原理介绍

1.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 = "<h1>Hello Mikejing</h1>"
    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''

2.Demo(静态路径定位资源模拟)

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访问

 

3.抽象(静态资源)Demo

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()

 

Web服务器框架Demo

1.未解耦Demo

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

 

2.解耦最终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参数进行请求路径的解析,根据回调函数处理响应头,再由服务器拼接返回给客户端

 

DJango如何运行(WSGI & uwsgi)

https://blog.csdn.net/geekz93/article/details/53246479

https://www.jianshu.com/p/679dee0a4193

web client <-> web server <-> the socket <-> uwsgi <-> Django

  • web client:顶层 - 浏览器 - 浏览器显示的页面,用户的操作界面
  • web server:服务器 - ngnix - 处理静态请求,转发动态请求 - 接收uwsgi的数据
  • the socket:服务器之间的通信,ngnix通过socket将动态请求传输传输至uwsgi
  • uwsgi:服务器 - 传送动态请求至Django - 接收Django的数据 - 传送数据至ngnix
  • Django:底层 - python的web框架 
    对web通信过程的理解:用户在浏览器输入网址 http://www.localhost:8000 按下回车后浏览器所做的事情就是将页面数据以 http协议规定的格式发给ngnix,ngnix收到数据后解析,如果是静态请求(如访问图片,视频等文件)就直接将文件包装成http格式返回给浏览器,用户就可以直接看到内容了;如果是动态请求(如刷新验证码) 动态和静态的区别,ngnix将请求通过socket发送给uwsgi,uwsgi解析http协议,将数据部分以wsgi协议规定的格式发送给Django;Django解析、处理数据,将处理的结果以wsgi格式发送给uwsgi;uwsgi收到处理结果后通过socket发送至nginx,nginx将结果包装成http格式发送给浏览器完成整个通信

 

 

 

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值