浏览器进行http请求的时候,不单单会请求静态资源,还可能需要请求动态页面。
那么什么是静态资源,什么是动态页面呢?
静态资源 : 例如html文件、图片文件、css、js文件等,都可以算是静态资源
动态页面:当请求例如登陆页面、查询页面、注册页面等可能会变化的页面,则是动态页面。
浏览器请求动态页面过程
通过下图来了解一下页面HTTP请求的过程,如下:

可以看到web服务器是用wsgi
协议调用应用程序框架的,这里我们先不讲什么是wsgi
协议,先看看我之前写的静态web服务端。
多进程web服务端代码 - 面向过程
#coding=utf-8 from socket import * import re import multiprocessing def handle_client(client_socket): """为一个客户端服务""" # 接收对方发送的数据 recv_data = client_socket.recv(1024).decode("utf-8") # 1024表示本次接收的最大字节数 # 打印从客户端发送过来的数据内容 #print("client_recv:",recv_data) request_header_lines = recv_data.splitlines() for line in request_header_lines: print(line) # 返回浏览器数据 # 设置内容body # 使用正则匹配出文件路径 print("------>",request_header_lines[0]) print("file_path---->","./html/" + re.match(r"[^/]+/([^s]*)",request_header_lines[0]).group(1)) ret = re.match(r"[^/]+/([^s]*)",request_header_lines[0]) if ret: file_path = "./html/" + ret.group(1) if file_path == "./html/": file_path = "./html/index.html" print("file_path *******",file_path) try: # 设置返回的头信息 header response_headers = "HTTP/1.1 200 OK " # 200 表示找到这个资源 response_headers += " " # 空一行与body隔开 # 读取html文件内容 file_name = file_path # 设置读取的文件路径 f = open(file_name,"rb") # 以二进制读取文件内容 response_body = f.read() f.close() # 返回数据给浏览器 client_socket.send(response_headers.encode("utf-8")) #转码utf-8并send数据到浏览器 client_socket.send(response_body) #转码utf-8并send数据到浏览器 except: # 如果没有找到文件,那么就打印404 not found # 设置返回的头信息 header response_headers = "HTTP/1.1 404 not found " # 200 表示找到这个资源 response_headers += " " # 空一行与body隔开 response_body = "<h1>sorry,file not found</h1>" response = response_headers + response_body client_socket.send(response.encode("utf-8")) #client_socket.close() def main(): # 创建套接字 server_socket = socket(AF_INET, SOCK_STREAM) # 设置当服务器先close 即服务器端4次挥手之后资源能够立即释放,这样就保证了,下次运行程序时 可以立即绑定7788端口 server_socket.setsockopt(SOL_SOCKET, SO_REUSEADDR, 1) # 设置服务端提供服务的端口号 server_socket.bind(('', 7788)) # 使用socket创建的套接字默认的属性是主动的,使用listen将其改为被动,用来监听连接 server_socket.listen(128) #最多可以监听128个连接 # 开启while循环处理访问过来的请求 while True: # 如果有新的客户端来链接服务端,那么就产生一个新的套接字专门为这个客户端服务 # client_socket用来为这个客户端服务 # server_socket就可以省下来专门等待其他新的客户端连接while True: client_socket, clientAddr = server_socket.accept() # handle_client(client_socket) # 设置子进程 new_process = multiprocessing.Process(target=handle_client,args=(client_socket,)) new_process.start() # 开启子进程 # 因为子进程已经复制了父进程的套接字等资源,所以父进程调用close不会将他们对应的这个链接关闭的 client_socket.close() if __name__ == "__main__": main()
先来回顾一下运行的情况:

好了,看到运行也是正常的,那么下面就要来分析一下,如何将代码封装为对象。
封装对象分析
首先我需要定义一个webServer类,然后将访问静态资源的功能都封装进去。
#coding=utf-8 from socket import * import re import multiprocessing class WebServer: def __init__(self): # 创建套接字 self.server_socket = socket(AF_INET, SOCK_STREAM) # 设置当服务器先close 即服务器端4次挥手之后资源能够立即释放,这样就保证了,下次运行程序时 可以立即绑定7788端口 self.server_socket.setsockopt(SOL_SOCKET, SO_REUSEADDR, 1) # 设置服务端提供服务的端口号 self.server_socket.bind(('', 7788)) # 使用socket创建的套接字默认的属性是主动的,使用listen将其改为被动,用来监听连接 self.server_socket.listen(128) #最多可以监听128个连接 def start_http_service(self): # 开启while循环处理访问过来的请求 while True: # 如果有新的客户端来链接服务端,那么就产生一个新的套接字专门为这个客户端服务 # client_socket用来为这个客户端服务 # self.server_socket就可以省下来专门等待其他新的客户端连接while True: client_socket, clientAddr = self.server_socket.accept() # handle_client(client_socket) # 设置子进程 new_process = multiprocessing.Process(target=self.handle_client,args=(client_socket,)) new_process.start() # 开启子进程 # 因为子进程已经复制了父进程的套接字等资源,所以父进程调用close不会将他们对应的这个链接关闭的 client_socket.close() def handle_client(self,client_socket): """为一个客户端服务""" # 接收对方发送的数据 recv_data = client_socket.recv(1024).decode("utf-8") # 1024表示本次接收的最大字节数 # 打印从客户端发送过来的数据内容 #print("client_recv:",recv_data) request_header_lines = recv_data.splitlines() for line in request_header_lines: print(line) # 返回浏览器数据 # 设置内容body # 使用正则匹配出文件路径 print("------>",request_header_lines[0]) print("file_path---->","./html/" + re.match(r"[^/]+/([^s]*)",request_header_lines[0]).group(1)) ret = re.match(r"[^/]+/([^s]*)",request_header_lines[0]) if ret: file_path = "./html/" + ret.group(1) if file_path == "./html/": file_path = "./html/index.html" print("file_path *******",file_path) try: # 设置返回的头信息 header response_headers = "HTTP/1.1 200 OK " # 200 表示找到这个资源 response_headers += " " # 空一行与body隔开 # 读取html文件内容 file_name = file_path # 设置读取的文件路径 f = open(file_name,"rb") # 以二进制读取文件内容 response_body = f.read() f.close() # 返回数据给浏览器 client_socket.send(response_headers.encode("utf-8")) #转码utf-8并send数据到浏览器 client_socket.send(response_body) #转码utf-8并send数据到浏览器 except: # 如果没有找到文件,那么就打印404 not found # 设置返回的头信息 header response_headers = "HTTP/1.1 404 not found " # 200 表示找到这个资源 response_headers += " " # 空一行与body隔开 response_body = "<h1>sorry,file not found</h1>" response = response_headers + response_body client_socket.send(response.encode("utf-8")) def main(): webserver = WebServer() webserver.start_http_service() if __name__ == "__main__": main()
好了,从上面的代码来看,我已经将前面面向过程的代码修改为面向对象了。
运行一下看看有没有错误:

思考:已经封装为对象了,下一步还要优化什么呢?
请求静态资源的页面已经可以了,那么如果请求动态的页面呢?
如果web服务端是java写的话,通常http请求就是http:xxxx/xxx.jsp
如果web服务端是php写的话,通常http请求就是http:xxxx/xxx.php
那么,既然这次我使用python来写,就可以定义动态资源的请求为http:xxxx/xxx.py
那么如果来识别并执行 http:xxxx/xxx.py
的请求呢?
增加识别动态资源请求的功能
需求:识别并返回http:xxxx/xxx.py
的请求
那么让我想一下,先做个简单的,例如:我请求一个http的请求 http:xxxx/time.py
则返回一个当前服务端的时间给浏览器。
那么如果http请求了一个py结尾的请求,我需要在哪里处理呢?

还有我可以用什么方法来判断 .py 后缀的文件呢?
用正则匹配?
其实可以使用endswith("文件后缀")
的方法来判断处理。
In [1]: file_name = "time.py" # 匹配后缀为 .html ,直接报False In [3]: file_name.endswith(".html") Out[3]: False # 匹配后缀为 .py ,则报True In [4]: file_name.endswith(".py") Out[4]: True
那么下面就可以来写写这里判断的处理分支了。

测试执行一下:
首先请求HTML等静态资源页面

请求动态资源页面

先简单地写一串HTML+当前服务器时间的内容吧。
if file_path.endswith(".py"): # 请求动态资源 print("这个是请求动态资源的!") # 设置返回的头信息 head response_headers = "HTTP/1.1 200 OK " # 200 表示找到这个资源 response_headers += " " # 空一行与body隔开 # 设置返回浏览器的body内容 response_body = "<h1>hello this is xxx.py</h1><br>" response_body += time.ctime() response = response_headers + response_body # 返回数据给浏览器 client_socket.send(response.encode("utf-8"))
从这里已经可以正常返回动态页面的内容的了。
思考 :如果将动态处理页面的代码在web服务端不断地写,代码就会很庞大。是否可以拆分出来,服务端只接受浏览器的消息,判断静态还是动态以及其他业务功能放到另一个模块进行编写呢?
这里就涉及到 web服务端 与 业务处理服务端 之间的一个协议了,这个业界内通用的协议就是 WSGI协议。
为什么需要 WSGI协议
在讲WSGI
协议之前,我先把处理动态页面的功能拆分到另一个模块文件中。
import time def application(client_socket): # 请求动态资源 print("这个是请求动态资源的!") # 设置返回的头信息 head response_headers = "HTTP/1.1 200 OK " # 200 表示找到这个资源 response_headers += " " # 空一行与body隔开 # 设置返回浏览器的body内容 response_body = "<h1>hello this is xxx.py</h1><br>" response_body += time.ctime() response = response_headers + response_body # 返回数据给浏览器 client_socket.send(response.encode("utf-8"))
那么在原来的webserver.py
模块只要import该模块文件,使用application()
方法就可以处理刚才的业务了。

好了,做了这个解耦的操作之后,下面来运行测试一下:

从上面的调用结果来看,的确是调用成功啦,理解大概如下图:

可以看出来,webserver想要调用 framework处理业务的话,就要这样去写,如下:
framework.application(client_socket)
这种方式虽然可行,但是在业界中是不通用的。也就是说这种调用方法扔给别人写的框架,就无法兼容了。
例如:假设我后面改用Django、Flask框架来处理业务,此时一定就不是用这种方式来通讯调用的。
那么该用什么方式呢?
是否可以修改服务器和架构代码而确保可以在多个架构下,保证与web服务器之间的通讯调用呢?
WSGI协议的介绍
WSGI允许开发者将选择web框架和web服务器分开。可以混合匹配web服务器和web框架,选择一个适合的配对。
比如,可以在Gunicorn 或者 Nginx/uWSGI 或者 Waitress上运行 Django, Flask, 或 Pyramid。真正的混合匹配,得益于WSGI同时支持服务器和架构。
web服务器必须具备WSGI接口,所有的现代Python Web框架都已具备WSGI接口,它让你不对代码作修改就能使服务器和特点的web框架协同工作。
定义WSGI接口
WSGI接口定义非常简单,它只要求Web开发者实现一个函数,就可以响应HTTP请求。def application(environ, start_response): start_response('200 OK', [('Content-Type', 'text/html')]) return 'Hello World!'
上面的application()函数就是符合WSGI标准的一个HTTP处理函数,它接收两个参数:
- environ:一个包含所有HTTP请求信息的dict对象;
- start_response:一个发送HTTP响应的函数。
编写framwork支持WSGI协议,实现浏览器显示 hello world
直接协议规范代码复制进去。
那么在webserver.py的部分,就需要接受application返回的信息。
首先,start_response
就是在framwork设置http请求header信息的。而return 就是返回http请求body信息的。
那么知道了这两点之后,下一步要做的。就是想办法来接受这个application的设置header以及body信息。
那么怎么处理呢?
pycharm
同时打开两个视图窗口来查看。
好了,下面来继续看看。
下面来创建这两个形参:
- environ:一个包含所有HTTP请求信息的dict对象;
- start_response:一个发送HTTP响应的函数。
先随便写个空的,来填入WSGI规范所需要的参数。
其中response_body
通过return
的返回值就可以接受到了。
那么response_header
该怎么处理呢?
可以从代码中看出start_response
在webserver.py 传入到 framwork.py 的application中调用。
其中在application中就直接设置header信息到start_response的参数中。然后我在webserver.py能否直接将其取出来,拼接成header信息呢?
编写start_response
接收 header
信息
那么首先编写一个类变量来保存信息,然后测试打印一下看看。
运行测试一下看看:
那么只要将其保存到self.application_header
中,我就可以在类方法的任意一个地方进行拆分或者拼接成所需要的http header返回值了。
编写如下:
运行测试看看。
这样就得到了完成的header内容啦,那么下面将其拼接body内容,然后返回浏览器中显示。
运行测试如下:
本次开发的完整代码如下:
webserver.py
#coding=utf-8 from socket import * import re import multiprocessing import time import framework class WebServer: def __init__(self): # 创建套接字 self.server_socket = socket(AF_INET, SOCK_STREAM) # 设置当服务器先close 即服务器端4次挥手之后资源能够立即释放,这样就保证了,下次运行程序时 可以立即绑定7788端口 self.server_socket.setsockopt(SOL_SOCKET, SO_REUSEADDR, 1) # 设置服务端提供服务的端口号 self.server_socket.bind(('', 7788)) # 使用socket创建的套接字默认的属性是主动的,使用listen将其改为被动,用来监听连接 self.server_socket.listen(128) #最多可以监听128个连接 def start_http_service(self): # 开启while循环处理访问过来的请求 while True: # 如果有新的客户端来链接服务端,那么就产生一个新的套接字专门为这个客户端服务 # client_socket用来为这个客户端服务 # self.server_socket就可以省下来专门等待其他新的客户端连接while True: client_socket, clientAddr = self.server_socket.accept() # handle_client(client_socket) # 设置子进程 new_process = multiprocessing.Process(target=self.handle_client,args=(client_socket,)) new_process.start() # 开启子进程 # 因为子进程已经复制了父进程的套接字等资源,所以父进程调用close不会将他们对应的这个链接关闭的 client_socket.close() def handle_client(self,client_socket): """为一个客户端服务""" # 接收对方发送的数据 recv_data = client_socket.recv(1024).decode("utf-8") # 1024表示本次接收的最大字节数 # 打印从客户端发送过来的数据内容 #print("client_recv:",recv_data) request_header_lines = recv_data.splitlines() for line in request_header_lines: print(line) # 返回浏览器数据 # 设置内容body # 使用正则匹配出文件路径 print("------>",request_header_lines[0]) print("file_path---->","./html/" + re.match(r"[^/]+/([^s]*)",request_header_lines[0]).group(1)) ret = re.match(r"[^/]+/([^s]*)",request_header_lines[0]) if ret: file_path = "./html/" + ret.group(1) if file_path == "./html/": file_path = "./html/index.html" print("file_path *******",file_path) # 判断file_path是否py文件后缀,如果是则请求动态资源,否则请求静态资源 if file_path.endswith(".py"): # framework.application(client_socket) # 支撑WGSI协议的调用方式 environ = {} response_body = framework.application(environ, self.start_response) # 设置返回的头信息header # 1.拼接第一行HTTP/1.1 200 OK + 换行符内容 response_headers = "HTTP/1.1 " + self.application_header[0] + " " # 2.循环拼接第二行或者多行元组内容:Content-Type:text/html for var in self.application_header[1]: response_headers += var[0]+":"+var[1] + " " # 3.空一行与body隔开 response_headers += " " # 4.打印看看header的内容信息 print("response_header=") print(response_headers) # 设置返回的浏览器的内容 response = response_headers + response_body client_socket.send(response.encode("utf-8")) else: # 请求静态资源 try: # 设置返回的头信息 header response_headers = "HTTP/1.1 200 OK " # 200 表示找到这个资源 response_headers += " " # 空一行与body隔开 # 读取html文件内容 file_name = file_path # 设置读取的文件路径 f = open(file_name,"rb") # 以二进制读取文件内容 response_body = f.read() f.close() # 返回数据给浏览器 client_socket.send(response_headers.encode("utf-8")) #转码utf-8并send数据到浏览器 client_socket.send(response_body) #转码utf-8并send数据到浏览器 except: # 如果没有找到文件,那么就打印404 not found # 设置返回的头信息 header response_headers = "HTTP/1.1 404 not found " # 200 表示找到这个资源 response_headers += " " # 空一行与body隔开 response_body = "<h1>sorry,file not found</h1>" response = response_headers + response_body client_socket.send(response.encode("utf-8")) def start_response(self,status,header): self.application_header = [status,header] print("application_header=",self.application_header) def main(): webserver = WebServer() webserver.start_http_service() if __name__ == "__main__": main()
framework.py
# 支撑WGSI协议 def application(environ, start_response): start_response('200 OK', [('Content-Type', 'text/html')]) return 'Hello World!'
作者:DevOps海洋的渔夫
链接:https://www.jianshu.com/p/417cd1989781