python就业班 miniweb框架_Python 18 MiniWEB项目

本文介绍了MiniWEB项目的开发目标,重点讲解了HTTP服务器的运行原理、程序解耦的概念和重要性。通过重构原始服务器代码,实现了面向对象的优化,降低了耦合性。同时,区分了静态数据和动态数据,并通过WSGI接口实现了服务器与框架应用的功能分离,简化了服务器迁移和维护。最后,通过WSGI协议的application接口,实现了服务器与框架之间的通信,进一步完善了功能分离。
摘要由CSDN通过智能技术生成

MiniWEB项目、程序解耦和耦合关系、区分动态数据和静态数据、WSGI、WSGI接口中的接口函数中参数的函数回调

3.1 MiniWEB项目

学习目标

1. 能够说出WEB服务器在访问时的执行过程

2. 能够说出实现框架的意义

3. 能够说出为什么要进行程序的解耦

总结:

1. 代码在开发过程中,应该遵循高内聚低耦合的思想

2. 静态数据是指在访问时不会发生变化的数据

3. 动态数据是指在访问时会服务的状态,条件等发生不同的变化,得到的数据不同

4. 通过WSGI接口,实现了服务器和框架的功能分离

5. 服务器和框架应用的功能分离,使服务器的迁移,维护更加简单

--------------------------------------------------------------------------------

3.1.1 HTTP 服务器运行原理

之前在实现的程序中,主要代码都实现在上图的左半部分。服务器的运行和 WEB 应用的处理,都是在一个文件中实现的。

这几天的工作,就是把程序解耦,将功能分离,服务器只用来提供WEB服务,WEB应用用来实现数据处理。

大家可以了解一下开发中比较常用的WEB框架,比如 Apache ,Nigix,Tomcat等。

没有一个服务器框架安装完成后,就完成了WEB应用的开发的。

因为服务器根本不知道你要完成的功能是什么,所以只提供给你服务,而应用的功能按照服务的接口来完成。然后让服务器响应处理。

3.1.2 原始服务器回顾分析

在前面的课程中,我们实现过一个 HTTP 服务器,我们就在这个服务器的基础上,来实现这阶段的 MiniWEB 框架。

首先,先来回顾一下这个HTTP服务器的代码

注意:将代码复制到工程文件中之后,还需要将资源文件复制到工程目录中

原始服务器 WebServer.py

#  代码实现:

import socket

import re

import multiprocessing

def service_client(new_socket):

"""为客户端返回数据"""

# 1. 接收浏览器发送过来的请求 ,即http请求相关信息

# GET / HTTP/1.1

# .....

request = new_socket.recv(1024).decode("utf-8")

#将请求头信息进行按行分解存到列表中

request_lines = request.splitlines()

# GET /index.html HTTP/1.1

file_name = ""

#正则:  [^/]+ 不以/开头的至少一个字符 匹配到/之前

#      (/[^ ]*) 以分组来匹配第一个字符是/,然后不以空格开始的0到多个字符,也就是空格之前

#      最后通过匹配可以拿到 请求的路径名  比如:index.html

ret = re.match(r"[^/]+(/[^ ]*)", request_lines[0])

#如果匹配结果 不为none,说明请求地址正确

if ret:

#利用分组得到请求地址的文件名,正则的分组从索引1开始

file_name = ret.group(1)

print('FileName:  ' + file_name)

#如果请求地址为 / 将文件名设置为index.html,也就是默认访问首页

if file_name == "/":

file_name = "/index.html"

# 2. 返回http格式的数据,给浏览器

try:

#拼接路径,在当前的html目录下找访问的路径对应的文件进行读取

f = open("./html" + file_name, "rb")

except:

#如果没找到,拼接响应信息并返回信息

response = "HTTP/1.1 404 NOT FOUND\r\n"

response += "\r\n"

response += "------file not found-----"

new_socket.send(response.encode("utf-8"))

else:

#如果找到对应文件就读取并返回内容

html_content = f.read()

f.close()

# 2.1 准备发送给浏览器的数据---header

response = "HTTP/1.1 200 OK\r\n"

response += "\r\n"

#如果想在响应体中直接发送文件内的信息,那么在上面读取文件时就不能用rb模式,只能使用r模式,所以下面将响应头和响应体分开发送

#response += html_content

# 2.2 准备发送给浏览器的数据

# 将response header发送给浏览器

new_socket.send(response.encode("utf-8"))

# 将response body发送给浏览器

new_socket.send(html_content)

# 关闭套接

new_socket.close()

def main():

"""用来完成整体的控制"""

# 1. 创建套接字

tcp_server_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)

#用来重新启用占用的端口

tcp_server_socket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)

# 2. 绑定IP和端口号

tcp_server_socket.bind(("", 7890))

# 3. 设置套接字监听连接数(最大连接数)

tcp_server_socket.listen(128)

while True:

# 4. 等待新客户端的链接

new_socket, client_addr = tcp_server_socket.accept()

# 5. 为连接上来的客户端去创建一个新的进程去运行

p = multiprocessing.Process(target=service_client, args=(new_socket,))

p.start()

#因为新进程在创建过程中会完全复制父进程的运行环境,所以父线程中关闭的只是自己环境中的套接字对象

#而新进程中因为被复制的环境中是独立存在的,所以不会受到影响

new_socket.close()

# 关闭监听套接字

tcp_server_socket.close()

if __name__ == "__main__":

main()

3.1.3 程序解耦

<1>概念理解 什么是耦合关系?

耦合关系是指某两个事物之间如果存在一种相互作用、相互影响的关系,那么这种关系就称"耦合关系"。

在软件工程中的耦合就是代码之间的依赖性。

代码之间的耦合度越高,维护成本越高。

<2>代码开发原则之一:高内聚,低耦合。

这句话的意思就是程序的每一个功能都要单独内聚在一个函数中,让代码之间的耦合度达到最小。也就是相互之间的依赖性达到最小。

实现面向对象的思想的代码重构

以面向对象的思想来完成服务器的代码实现 实现过程:

■ 1.封装类

■ 2.初始化方法中创建socket对象

■ 3.启动服务器的方法中进行服务监听

■ 4.实现数据处理的方法

■ 5.对象属性的相应修改

■ 6.重新实现main方法,创建WEBServer类对象并启动服务

WebServer.py

# 面向对象修改数据

import socket

import re

import multiprocessing

class WEBServer(object):

#在初始化方法中完成服务器Socket对象的创建

def __init__(self):

"""用来完成整体的控制"""

# 1. 创建套接字

self.tcp_server_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)

# 用来重新启用占用的端口

self.tcp_server_socket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)

# 2. 绑定IP和端口号

self.tcp_server_socket.bind(("", 7890))

# 3. 设置套接字监听连接数(最大连接数)

self.tcp_server_socket.listen(128)

def service_client(self,new_socket):

"""为这个客户端返回数据"""

# 1. 接收浏览器发送过来的请求 ,即http请求相关信息

# GET / HTTP/1.1

# .....

request = new_socket.recv(1024).decode("utf-8")

#将请求头信息进行按行分解存到列表中

request_lines = request.splitlines()

# GET /index.html HTTP/1.1

# get post put del

file_name = ""

#正则:  [^/]+ 不以/开头的至少一个字符 匹配到/之前

#      (/[^ ]*) 以分组来匹配第一个字符是/,然后不以空格开始的0到多个字符,也就是空格之前

#      最后通过匹配可以拿到 请求的路径名  比如:index.html

ret = re.match(r"[^/]+(/[^ ]*)", request_lines[0])

#如果匹配结果 不为none,说明请求地址正确

if ret:

#利用分组得到请求地址的文件名,正则的分组从索引1开始

file_name = ret.group(1)

print('FileName:  ' + file_name)

#如果请求地址为 / 将文件名设置为index.html,也就是默认访问首页

if file_name == "/":

file_name = "/index.html"

# 2. 返回http格式的数据,给浏览器

try:

#拼接路径,在当前的html目录下找访问的路径对应的文件进行读取

f = open("./html" + file_name, "rb")

except:

#如果没找到,拼接响应信息并返回信息

response = "HTTP/1.1 404 NOT FOUND\r\n"

response += "\r\n"

response += "------file not found-----"

new_socket.send(response.encode("utf-8"))

else:

#如果找到对应文件就读取并返回内容

html_content = f.read()

f.close()

# 2.1 准备发送给浏览器的数据---header

response = "HTTP/1.1 200 OK\r\n"

response += "\r\n"

#如果想在响应体中直接发送文件内的信息,那么在上面读取文件时就不能用rb模式,只能使用r模式,所以下面将响应头和响应体分开发送

#response += html_content

# 2.2 准备发送给浏览器的数据

# 将response header发送给浏览器

new_socket.send(response.encode("utf-8"))

# 将response body发送给浏览器

new_socket.send(html_content)

# 关闭套接

new_socket.close()

def run(self):

while True:

# 4. 等待新客户端的链接

new_socket, client_addr = self.tcp_server_socket.accept()

# 5. 为这个客户端服务

p = multiprocessing.Process(target=self.service_client, args=(new_socket,))

p.start()

#因为新线程在创建过程中会完全复制父线程的运行环境,所以父线程中关闭的只是自己环境中的套接字对象

#而新线程中因为被复制的环境中是独立存在的,所以不会受到影响

new_socket.close()

# 关闭监听套接字

self.tcp_server_socket.close()

def main():

webServer = WEBServer()

webServer.run()

if __name__ == "__main__":

main()

通过使用面向对象的思想,将代码重构后,耦合性降低,但还没有完全实现功能的分离。 目前还是在一个文件中实现所有的程序功能,也就是说,目前只是完成了在原理图中,左半侧的功能。后面会继续改进。

3.1.4 区分动态数据和静态数据

静态数据:是指在页面进行访问时,无论何时访问,得到的内容都是同样的,不会发生任意变化

(比如我们现在实现的API网页的访问效果,这些API文件都是保存在本地(或服务器上)的一些固定的文档说明,无论在何时何地访问这些数据,都是相同的,不会发生变化)

动态数据:是指在页面进行访问时,得到的数据是经过服务器进行计算,加工,处理过后的数据,称为动态数据,哪怕只是加了一个空格

比如:实时新闻,股票信息,购物网站显示的商品信息等等都动态数据

在这部分代码实现中,先来实现不同形式的页面访问,服务器返回不同的数据(数据暂时还是静态的,假的数据,真正的动态数据会在完成框架后,在数据库中读取返回)

这里设定: xxx.html 访问时,返回的是静态数据 API 文档中的内容, xxx.py 访问时,返回的是动态数据(数据先以静态数据代替)

实现过程:

1.先根据访问页面地址判断访问数据的类型,是py的动态还是html的静态

2.根据动态请求的路径名的不同来返回不同的数据,不在使用html获取数据,而使用py来获取

WebServer.py

# ...

# 前面的代码不需要修改

if ret:

#利用分组得到请求地址的文件名,正则的分组从索引1开始

file_name = ret.group(1)

print('FileName:  ' + file_name)

#如果请求地址为 / 将文件名设置为index.html,也就是默认访问首页

if file_name == "/":

file_name = "/index.html"

# ------------- 这里开始修改代码------------

#判断访问路径的类型

if file_name.endswith('.py'):

#根据不同的文件名来确定返回的响应信息

if file_name == '/index.py':                #首页

header =  "HTTP/1.1 200 OK\r\n"        #响应头

body = 'Index Page ...'                #响应体

data = header + '\r\n' + body          #拼接响应信息

new_socket.send(data.encode('utf-8'))  #返回响应信息

elif file_name == '/center.py':            #个人中心页面

header =  "HTTP/1.1 200 OK\r\n"

body = 'Center Page ...'

data = header + '\r\n' + body

new_socket.send(data.encode('utf-8'))

else:                                      #其它页面

header =  "HTTP/1.1 200 OK\r\n"

body = 'Other Page ...'

data = header + '\r\n' + body

new_socket.send(data.encode('utf-8'))

else:

# 2. 返回http格式的数据,给浏览器

try:

#拼接路径,在当前的html目录下找访问的路径对应的文件进行读取

f = open("./html" + file_name, "rb")

except:

#如果没找到,拼接响应信息并返回信息

response = "HTTP/1.1 404 NOT FOUND\r\n"

response += "\r\n"

response += "------file not found-----"

new_socket.send(response.encode("utf-8"))

else:

#如果找到对应文件就读取并返回内容

html_content = f.read()

f.close()

# 2.1 准备发送给浏览器的数据---header

response = "HTTP/1.1 200 OK\r\n"

response += "\r\n"

#如果想在响应体中直接发送文件内的信息,那么在上面读取文件时就不能用rb模式,只能使用r模式,所以下面将响应头和响应体分开发送

#response += html_content

# 2.2 准备发送给浏览器的数据

# 将response header发送给浏览器

new_socket.send(response.encode("utf-8"))

# 将response body发送给浏览器

new_socket.send(html_content)

3.1.5 实现动态数据的响应优化

虽然前面的代码实现了设计需求,但是实现过程太过冗余,不符合代码开发原则。 一个服务器中提供可以访问的页面肯定不止这么几个,如果每一个都实现一次响应信息的编写,那冗余代码就太多了,不符合代码的开发规范 通过分析我们可以看出,代码中大部分内容都是相同的,只有在响应信息的响应体部分不同,那么就可以将代码优化一下。

实现过程: 因为所有页面的响应信息都是相同的,所以让这些页面共用一块代码

1. 将响应头和空行代码放到判断页面之前

2. 将发拼接和发送代码放到判断之后

3. 页面判断中,只根据不同的页面设计不同的响应体信息

实现代码: WebServer.py

# ...

# 前面的代码不需要修改

#判断访问路径的类型

# ------------- 这里开始修改代码------------

if file_name.endswith('.py'):

header = "HTTP/1.1 200 OK\r\n"  # 响应头

#根本不同的文件名来确定返回的响应信息

if file_name == '/index.py':

body = 'Index Page ...'                #响应体

elif file_name == '/center.py':

body = 'Center Page ...'

else:

body = 'Other Page ...'

data = header + '\r\n' + body  # 拼接响应信息

new_socket.send(data.encode('utf-8'))  # 返回响应信息

# ------------- 这里开始修改代码结束------------

else:

# 后面的代码不需要修改

3.1.6 实现功能的分离

代码被进一步优化,但是还是存在问题。网络请求和数据处理还是没有分开,还是在同一个文件中实现的。

实际开发中WEB服务器有很多种,比如Apache,Nigix等等。

如果在开发过程中,需要对 WEB 服务器进行更换。那么我们现在的做法就要花费很大的精力,因为 WEB 服务和数据处理都在一起。

如果能将程序的功能进行进行分离,提供 WEB 请求响应的服务器只管请求的响应,而响应返回的数据由另外的程序来进行处理。

这样的话,WEB 服务和数据处理之间的耦合性就降低了,这样更便于功能的扩展和维护

■ 比如:

■ 一台电脑,如果要是所有的更件都是集成在主板上的,那么只要有一个地方坏了。那整个主板都要换掉。成本很高

■ 如果所有的硬件都是以卡槽接口的形式插在主板上,那么如果哪一个硬件坏了或要进行升级扩展都会很方便,降低了成本。

在实际开发过程中,代码的模块化思想就是来源于生活,让每个功能各司其职。

实现思想: 将原来的服务器文件拆分成两个文件,一个负责请求响应,一个负责数据处理。 那么这里出现一个新的问题,两个文件中如何进行通信呢?负责数据处理的文件怎么知道客户端要请求什么数据呢?

想一下主板和内存之间是如何连接的?

实现过程:

1.WebServer 文件只用来提供请求的接收和响应

2.WebFrame 文件只用来提供请求数据的处理和返回

3.文件之间利用一个函数来传递请求数据和返回的信息

实现代码 WebServer.py

# ------------- 这里需要修改代码------------

# 因为在这里需要使用框架文件来处理数据,所以需要进行模块导入

import WebFrame

#...

# 前面的代码不需要修改

# ------------- 这里开始修改代码------------

#判断访问路径的类型

if file_name.endswith('.py'):

header = "HTTP/1.1 200 OK\r\n"  # 响应头

# 根本不同的访问路径名来向框架文件获取对应的数据

# 通过框架文件中定义的函数将访问路径传递给框架文件

body = WebFrame.application(file_name)

#将返回的数据进行拼接

data = header + '\r\n' + body  # 拼接响应信息

new_socket.send(data.encode('utf-8'))  # 返回响应信息

# ------------- 这里开始修改代码结束------------

else:

# 后面的代码不需要修改

# ...

WebFrame.py

# 在框架文件中,实现一个函数,做为 Web 服务器和框架文件之间的通信接口

# 在这个接口函数中,根据 Web 服务器传递过来的访问路径,判断返回的数据

def application(url_path):

if url_path == '/index.py':

body = 'Index Page ...'                #响应体

elif url_path == '/center.py':

body = 'Center Page ...'

else:

body = 'Other Page ...'

return body

代码实现到这里,基本将功能进行了分离,初步完成了前面原理图中的功能分离。

但是还没有真正的完成框架,到这里只是完成了框架中的一小步。

3.1.7 WSGI

<1>WSGI是什么?

WSGI,全称 Web Server Gateway Interface,

是为 Python 语言定义的 Web 服务器和 Web 应用程序或框架之间的一种简单而通用的接口。

是用来描述web server如何与web application通信的规范。

<2>WSGI协议中,定义的接口函数就是 application ,定义如下:

def application(environ, start_response):

start_response('200 OK', [('Content-Type', 'text/html')])

return [b'

Hello, web!

']

这个函数有两个参数:

参数一: web服务器向数据处理文件中传递请求相关的信息,一般为请求地址,请求方式等,传入类型约定使用字典

参数二: 传入一个函数,使用函数回调的形式,将数据处理的状态结果返回给服务器

■ 服务器的函数一般用来存储返回的信息,用来组合响应头信息,这里只是在框架中调用这个函数的时候传入定义时的参数(此处参数包括2个,第一个是响应状态和状态描述,第二个是响应头信息),其中描述响应头信息的参数是以列表装元组的形式返回,列表中的每一个元素都是以元组形式存放的一条响应头的信息,元组中有两个数据,分别对应着响应头信息中:前后的部分,所以要得到里面的数据应该先遍历列表,得到的是列表里的数据元组,'%s:%s\r\n' %t是对元组的拆包然后拼接响应头信息

返回值: 用来返回具体的响应体数据。

服务器和框架应用程序在共同遵守了这个协议后,就可以通过 application 函数进行通信。完成请求的转发和响应数据的处理返回。

实现过程:

1.在服务器中调用application函数

2.定义用来储存返回的响应头信息的回调函数,函数有两个参数,一个是状态,一个是其它信息,以字典形式传入

3.以字典传入请求地址名,传入回调的函数名

4.当处理完数据后,调用传入的函数并返回数据 5

.服务器收到返回的信息后进行响应信息的拼接处理.

代码实现: WebServer.py

import WebFrame

#...

# 前面的代码不需要修改

# ------------- 这里开始修改代码------------

#判断访问路径的类型

if file_name.endswith('.py'):

#要先调用这个函数,如果不调用,那么回调函数不能执行,下面拼接数据就会出错

#根本不同的文件名来向数据处理文件获取对应的数据

#并将回调函数传入进去

env = {'PATH_INFO':file_name}

body = WEBFrame.application(env,self.start_response)

#拼接返回的状态信息

header = "HTTP/1.1 %s\r\n"%self.status  # 响应头

#拼接返回的响应头信息

#因为是返回是以列表装元组的形式返回,所以遍历列表,得到的是列表里的数据元组,

#'%s:%s\r\n'%t是对元组的拆包,然后拼接元组里的信息

for t in self.params:

header += '%s:%s\r\n'%t

data = header + '\r\n' + body  # 拼接响应信息

new_socket.send(data.encode('utf-8'))  # 返回响应信息

# ------------- 这里开始修改代码结束------------

else:

# 后面的代码不需要修改

# ...

# ------------- 这里需要修改代码------------

#定义一个成员函数 ,用来回调保存数据使用

def start_response(self,status,params):

#保存返回回来的响应状态和其它响应信息

self.status = status

self.params = params

WebFrame.py

# 实现 WSGI 协议中的 application 接口方法

def application(environ, start_response):

# 从服务器传过来的字典中将访问路径取出来

url_path = environ['PATH_INFO']

# 判断访问路径,确定响应数据内容,保存到body中

if url_path == '/index.py':

body = 'Index Page ...'                #响应体

elif url_path == '/center.py':

body = 'Center Page ...'

else:

body = 'Other Page ...'

# 回调 start_response 函数,将响应状态信息回传给服务器

start_response('200 OK', [('Content-Type', 'text/html;charset=utf-8')])

# 返回响应数据内容

return body

通过代码的优化,到这里,基本已经将服务器和框架应用的功能分离。

3.1.8 总结:

1. 代码在开发过程中,应该遵循高内聚低耦合的思想

2. 静态数据是指在访问时不会发生变化的数据

3. 动态数据是指在访问时会服务的状态,条件等发生不同的变化,得到的数据不同

4. 通过WSGI接口,实现了服务器和框架的功能分离

5. 服务器和框架应用的功能分离,使服务器的迁移,维护更加简单

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
好的,让我来为您介绍一个简单的 Python Web 框架,它类似于 Django,名字叫做 MiniWebMiniWeb 实现了一个基本的 Web 框架功能,包括 URL 路由、请求处理、模板渲染等。下面是 MiniWeb 的代码示例: ```python # 引入必要的库 import re import os import sys import time import socket import select # 定义 MiniWeb 类 class MiniWeb: # 初始化方法 def __init__(self, host='', port=80): self.host = host self.port = port self.routes = [] # 添加路由方法 def route(self, pattern): def decorator(handler): self.routes.append((re.compile(pattern), handler)) return handler return decorator # 处理请求方法 def handle_request(self, request): # 提取请求方法和路径 method, path, _ = request.split(' ', 2) # 遍历路由规则 for pattern, handler in self.routes: # 如果路径匹配路由规则,则调用对应的处理函数 if pattern.match(path): return handler(request) # 如果没有匹配的路由规则,则返回 404 错误 return 'HTTP/1.1 404 Not Found\r\n\r\n' # 运行框架方法 def run(self): # 创建套接字并绑定地址 server_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM) server_socket.bind((self.host, self.port)) server_socket.listen(5) # 循环等待客户端连接 while True: # 使用 select 函数监听套接字 readable, _, _ = select.select([server_socket], [], [], 1) if readable: # 接受客户端连接并处理请求 client_socket, client_address = server_socket.accept() request = client_socket.recv(1024).decode() response = self.handle_request(request) client_socket.sendall(response.encode()) client_socket.close() # 定义路由处理函数 web = MiniWeb() @web.route('^/$') def index(request): return 'HTTP/1.1 200 OK\r\n\r\nHello, World!' @web.route('^/hello/(.*)$') def hello(request): name = request.split('/')[2] return f'HTTP/1.1 200 OK\r\n\r\nHello, {name}!' # 运行框架 if __name__ == '__main__': web.run() ``` 以上是 MiniWeb 的代码示例,您可以通过调用 `route` 方法来添加路由规则,然后编写对应的路由处理函数。当客户端发起请求时,MiniWeb 会根据路由规则来调用对应的路由处理函数,并返回处理结果。 当然,MiniWeb 还需要进一步的完善和优化,例如添加静态文件处理、错误处理、中间件等功能。但它足以作为一个简单的 Python Web 框架示例,帮助您更好地理解 Web 框架的实现原理。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值