python网络-动态Web服务器案例(30)
一、浏览器请求HTML页面的过程
了解了HTTP协议和HTML文档,其实就明白了一个Web应用的本质就是:
-
浏览器发送一个HTTP请求;
-
服务器收到请求,生成一个HTML文档;
-
服务器把HTML文档作为HTTP响应的Body发送给浏览器;
-
浏览器收到HTTP响应,从HTTP Body取出HTML文档并显示。
二、浏览器请求动态页面的过程
三、WSGI
1、WSGI介绍
PythonWeb服务器网关接口(Python Web Server Gateway Interface,缩写为WSGI)是Python应用程序或框架和Web服务器之间的一种接口,
WSGI接口定义非常简单,它只要求Web开发者实现一个函数,就可以响应HTTP请求。
# WSGI 规范的函数
def application(environ, start_response): start_response('200 OK', [('Content-Type', 'text/html')]) return '<h1>Hello, Se7eN_HOU!</h1>'
上面的application()
函数就是符合WSGI标准的一个HTTP处理函数,它接收两个参数:
-
-
environ:一个包含所有HTTP请求信息的
dict
对象; -
start_response:一个发送HTTP响应的函数。
-
在application()
函数中,调用:
start_response('200 OK', [('Content-Type', 'text/html')])
2、运行WSGI服务
1、 Python内置了一个WSGI服务器,这个模块叫wsgiref,首先我们先实现一个hello.py文件,实现Web应用程序的WSGI处理函数
def application(environ, start_response): start_response("200 OK",[("Content-Type","text/html")]) return "<h1>Hello,Se7eN_HOU!</h1>"
2、然后,再编写一个server.py
,负责启动WSGI服务器,加载application()
函数:
#coding:utf-8 # 导入wsgiref模块 from wsgiref.simple_server import make_server from hello import application # 创建一个服务器,IP地址为空,端口号为7788,处理的函数是application httpServer = make_server("", 7788, application) # 开始监听HTTP请求 httpServer.serve_forever()
3、确保以上两个文件在同一个目录下,然后使用命令行输入python server.py
来启动WSGI服务器:
houleideMacPro:WSGI Se7eN_HOU$ python server.py 127.0.0.1 - - [19/Jun/2019 15:52:37] "GET / HTTP/1.1" 200 24 127.0.0.1 - - [19/Jun/2019 15:52:37] "GET /favicon.ico HTTP/1.1" 200 24
4、 按Ctrl+C
终止服务器。如果你觉得这个Web应用太简单了,可以稍微改造一下,从environ
里读取PATH_INFO
,这样可以显示更加动态的内容:
def application(environ, start_response): start_response("200 OK",[("Content-Type","text/html")]) return "<h1>Hello,%s</h1>"%(environ["PATH_INFO"][1:] or "Se7eN_HOU")
5、你可以在地址栏输入用户名作为URL的一部分,将返回Hello, xxx
四、动态web服务器案例
1、Daynamic.py
#coding=utf-8 import socket import sys from multiprocessing import Process import re class WSGIServer(object): addressFamily = socket.AF_INET socketType = socket.SOCK_STREAM requestQueueSize = 5 def __init__(self, serverAddress): #创建一个tcp套接字 self.listenSocket = socket.socket(self.addressFamily,self.socketType) #允许重复使用上次的套接字绑定的port self.listenSocket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) #绑定 self.listenSocket.bind(serverAddress) #变为被动,并制定队列的长度 self.listenSocket.listen(self.requestQueueSize) self.servrName = "localhost" self.serverPort = serverAddress[1] def serveForever(self): '循环运行web服务器,等待客户端的链接并为客户端服务' while True: #等待新客户端到来 self.clientSocket, client_address = self.listenSocket.accept() #方法2,多进程服务器,并发服务器于多个客户端 newClientProcess = Process(target = self.handleRequest) newClientProcess.start() #因为创建的新进程中,会对这个套接字+1,所以需要在主进程中减去依次,即调用一次close self.clientSocket.close() def setApp(self, application): '设置此WSGI服务器调用的应用程序入口函数' self.application = application def handleRequest(self): '用一个新的进程,为一个客户端进行服务' self.recvData = self.clientSocket.recv(2014) requestHeaderLines = self.recvData.splitlines() for line in requestHeaderLines: print(line) httpRequestMethodLine = requestHeaderLines[0] getFileName = re.match("[^/]+(/[^ ]*)", httpRequestMethodLine).group(1) print("file name is ===>%s"%getFileName) #for test if getFileName[-3:] != ".py": if getFileName == '/': getFileName = documentRoot + "/index.html" else: getFileName = documentRoot + getFileName print("file name is ===2>%s"%getFileName) #for test try: f = open(getFileName) except IOError: responseHeaderLines = "HTTP/1.1 404 not found\r\n" responseHeaderLines += "\r\n" responseBody = "====sorry ,file not found====" else: responseHeaderLines = "HTTP/1.1 200 OK\r\n" responseHeaderLines += "\r\n" responseBody = f.read() f.close() finally: response = responseHeaderLines + responseBody self.clientSocket.send(response) self.clientSocket.close() else: #处理接收到的请求头 self.parseRequest() #根据接收到的请求头构造环境变量字典 env = self.getEnviron() #调用应用的相应方法,完成动态数据的获取 bodyContent = self.application(env, self.startResponse) #组织数据发送给客户端 self.finishResponse(bodyContent) def parseRequest(self): '提取出客户端发送的request' requestLine = self.recvData.splitlines()[0] requestLine = requestLine.rstrip('\r\n') self.requestMethod, self.path, self.requestVersion = requestLine.split(" ") def getEnviron(self): env = {} env['wsgi.version'] = (1, 0) env['wsgi.input'] = self.recvData env['REQUEST_METHOD'] = self.requestMethod # GET env['PATH_INFO'] = self.path # /index.html return env def startResponse(self, status, response_headers, exc_info=None): serverHeaders = [ ('Date', 'Tue, 31 Mar 2016 10:11:12 GMT'), ('Server', 'WSGIServer 0.2'), ] self.headers_set = [status, response_headers + serverHeaders] def finishResponse(self, bodyContent): try: status, response_headers = self.headers_set #response的第一行 response = 'HTTP/1.1 {status}\r\n'.format(status=status) #response的其他头信息 for header in response_headers: response += '{0}: {1}\r\n'.format(*header) #添加一个换行,用来和body进行分开 response += '\r\n' #添加发送的数据 for data in bodyContent: response += data self.clientSocket.send(response) finally: self.clientSocket.close() #设定服务器的端口 serverAddr = (HOST, PORT) = '', 8888 #设置服务器静态资源的路径 documentRoot = './html' #设置服务器动态资源的路径 pythonRoot = './wsgiPy' def makeServer(serverAddr, application): server = WSGIServer(serverAddr) server.setApp(application) return server def main(): if len(sys.argv) < 2: sys.exit('请按照要求,指定模块名称:应用名称,例如 module:callable') #获取module:callable appPath = sys.argv[1] #根据冒号切割为module和callable module, application = appPath.split(':') print("module=%s"%module) #添加路径套sys.path sys.path.insert(0, pythonRoot) #动态导入module变量中指定的模块 module = __import__(module) #获取module变量中制定的模块的application变量指定的属性 application = getattr(module, application) httpd = makeServer(serverAddr, application) print('WSGIServer: Serving HTTP on port {port} ...\n'.format(port=PORT)) httpd.serveForever() if __name__ == '__main__': main()
2、应用程序代码ctime.py
#coding:utf-8 import time def app(environ, start_response): status = "200 OK" response_headers = [ ("Content-Type", "text/plain") ] start_response(status, response_headers) return [str(environ)+'--->%s\n'%time.ctime()]
3、命令行执行代码
4、浏览器运行结果
侯哥语录:我曾经是一个职业教育者,现在是一个自由开发者。我希望我的分享可以和更多人一起进步。分享一段我喜欢的话给大家:"我所理解的自由不是想干什么就干什么,而是想不干什么就不干什么。当你还没有能力说不得时候,就努力让自己变得强大,拥有说不得权利。"