摘要
本文主要是介绍用python自带的BaseHTTPRequestHandler,HTTPServer类实现一个简易的web服务器,从而加深对http协议和web服务器实现、运行原理的理解,同时对web服务器与客户端的交互过程进行详细介绍,明白服务器是如何处理客户端对其请求后,将服务器资源响应给客户端的,更重要的是通过本项目的实现可以了解python的网络开发基础模块和CGI协议,从而拥有能够深入学习python网络开发体系知识基础。
1.引言
对于web应用服务器,相信只要是接触B/S架构开发的都会非常熟悉,动态网页的运行是要在web服务器这样的容器中进行处理,然后将数据返回到客户端进行显示的,对于php,最常见的是Apache,而jsp则是tomcat等轻量级应用服务器的使用是很普遍的,而这些服务器部署起来很容易,但是其实现原理却值得探究。
正好这段时间学习了python,想着实现一个最简单的web服务器应用,可以对客户端的请求进行响应,从而正常显示资源页面。
2. 系统结构
对使用的相关技术,相关模块进,实现功能的原理进行介绍,采用框架图,示例图等进行表述,使人可以对系统的框架和原理有个比较好的把握;
用python实现简易服务器,前提需要知道以下几个点:
2.1 B/S架构原理
B代表的是浏览器,S代表的是服务器,B/S交互流程:
[if !supportLists]1. [endif]用户在客户端向服务器发送请求,等待服务器响应;
[if !supportLists]2. [endif]服务器端接收到请求后,对请求的数据进行处理,并产生响应资源数据;
[if !supportLists]3. [endif]服务器将产生的资源数据返回给客户端浏览器
[if !supportLists]4. [endif]浏览器接收并进行解析相关资源文件,然后呈现在客户端界面。
简单原理图如下:
图1 B/S架构原理
2.2 http协议工作原理
http是客户端和服务器端请求和应答的标准,是基于TCP/IP协议之上的应用层协议.
工作原理:当客户端发起一个http请求时,客户端创建一个到服务器指定端口(http默认端口号80)的TCP连接,服务器在指定端口号监听客户端的请求,一旦接收到请求,服务器回向客户端返回一个状态包含状态码以及返回的内容.
http状态码:
[if !supportLists]· [endif]1xx消息——请求已被服务器接收,继续处理
[if !supportLists]· [endif]2xx成功——请求已成功被服务器接收、理解、并接受
[if !supportLists]· [endif]3xx重定向——需要后续操作才能完成这一请求
[if !supportLists]· [endif]4xx请求错误——请求含有词法错误或者无法被执行
[if !supportLists]· [endif]5xx服务器错误——服务器在处理某个正确请求时发生错误
[if !vml]
[endif]
图2 http请求及响应数据
2.3 URL
URL,全称是UniformResourceLocator, 中文叫统一资源定位符,是互联网上用来标识某一处资源的地址,就是在浏览器中键入的网址.
2.4 HTTPServer、BaseHTTPRequestHandler
HTTPServer
继承SocketServer.TCPServer,用于获取请求,并将请求分配给应答程序处理
HttpServer的处理过程如下:
[if !supportLists]1. [endif]HTTPServer绑定对应的应答类(BaseHTTPRequestHandler ),http_server = HTTPServer(('', int(port)), ServerHTTP);
[if !supportLists]2. [endif]监听端口:serve_forever()方法使用select.select()循环监听请求,当接收到请求后调用当监听到请求时,取出请求对象;
[if !supportLists]3. [endif] 应答:创建新线程以连接对象为参数实例化应答类:ServerHTTP()应答类根据请求方式调用ServerHTTP.do_XXX处理方法
BaseHTTPRequestHandler
继承SocketServer.StreamRequestHandler,对http连接的请求作出应答(response)是一个以TCPServer为基础开发的模块,可以在请求外层添加http协议报文,发送http协议。
3.实验代码
3.1首先导入相关模块
import sys,os, subprocess
fromhttp.server import BaseHTTPRequestHandler,HTTPServer
[if !supportLists]3.2[endif]条件处理基类
classbase_case(object):
def handle_file(self, handler, full_path):
try:
with open(full_path, 'rb') as reader:
content = reader.read()
handler.send_content(content)
except IOError as msg:
msg = "'{0}' cannot be read: {1}".format(full_path, msg)
handler.handle_error(msg)
def index_path(self, handler):
return os.path.join(handler.full_path, 'index.html')
def test(self, handler):
assert False, 'Not implemented.'
def act(self, handler):
assert False, 'Not implemented.'
[if !supportLists]3.3[endif]CGI协议处理实现类
classcase_cgi_file(base_case):
def run_cgi(self,handler):
data =subprocess.check_output(["python3", handler.full_path],shell=False)
handler.send_content(data)
def test(self, handler):
return os.path.isfile(handler.full_path) and \
handler.full_path.endswith('.py')
def act(self, handler):
self.run_cgi(handler)
[if !supportLists]3.4[endif]文件或目录不存在的情况下服务器处理实现
classcase_no_file(base_case):
def test(self,handler):
return not os.path.exists(handler.full_path)
def act(self, handler):
raise ServerException("'{0}' not found".format(handler.path))
3.5 当服务器存在文件时,服务器的处理实现
classcase_existing_file(base_case):
def test(self, handler):
return os.path.isfile(handler.full_path)
def act(self, handler):
self.handle_file(handler, handler.full_path)
3.6 客户端直接输入URL,返回index主页面
class case_directory_index_file(base_case):
def test(self, handler): #判断目标路径下是否有index.html主页面
returnos.path.isdir(handler.full_path) and \
os.path.isfile(self.index_path(handler))
def act(self, handler): #对index.html的内容进行响应
self.handle_file(handler,self.index_path(handler))
3.7 默认处理类
classcase_always_fail(base_case):
def test(self, handler):
return True
def act(self, handler):
raiseServerException("Unknown object '{0}'".format(handler.path))
3.8 当客户端请求路径合法返回响应的处理,如果不合法,返回错误页面实现RequestHandler类
class RequestHandler(BaseHTTPRequestHandler):
Cases = [case_no_file(),
case_cgi_file(),
case_existing_file(),
case_directory_index_file(),
case_always_fail()]
#当请求路径不合法时返回的错误页面模板
Error_Page = """\
Error accessing {path}
{msg}
"""
#重写do_GET函数
def do_GET(self):
try:
#得到完整的请求路径
self.full_path = os.getcwd() + self.path
#遍历所有的情况并处理
for case in self.Cases:
if case.test(self):
case.act(self)
break
#进行异常处理
except Exception as msg:
self.handle_error(msg)
def handle_error(self, msg):
content = self.Error_Page.format(path=self.path, msg=msg)
self.send_content(content.encode("utf-8"), 404)
# 将数据发送到客户端
def send_content(self, content, status=200):
self.send_response(status)
self.send_header("Content-type", "text/html")
self.send_header("Content-Length", str(len(content)))
self.end_headers()
self.wfile.write(content)
3.9服务器程序异常类
class ServerException(Exception):
pass
3.10主函数实现
if __name__ == '__main__':
serverAddress = ('', 8000) #设置对应端口
server = HTTPServer(serverAddress,
RequestHandler) #HTTPServer绑定对应的应答类
server.serve_forever() #serve_forever()方法使用select.select()循环监听请求,收到时取出请求对象,创建新线程进行应答
3.11最后在项目中建立几个用于测试的HTML页面:
Index.html
Register.html
运行server.py程序,再开启浏览器程序进行访问测试
4.实验结果
文件目录结构:
[if !vml]
[endif]
客户端浏览器直接访问请求index主页:
[if !vml]
[endif]
客户端浏览器请求同一目录下存在的register.html页面:
[if !vml]
[endif]
客户端浏览器请求不存在的页面:
[if !vml]
[endif]
5.总结和展望
通过实现基于python的简易web服务器的实现对于B/S架构的理解也不断加深,而且两者之间的通信是基于http、TCP/IP等传输协议的,python中的BaseHTTPRequestHandler, HTTPServer等等相应模块类都能很好的实现这些功能。
基于python的简易web服务器基本能实现正确处理客户端浏览器的访问请求,并对其进行响应,但是其不足之处是仅限于web服务器中的静态网页,对于动态网页尚未实现相应功能。
本文核心代码和实现思路主要参考自《500 lines or less》项目,作者是 Mozilla 的 Greg Wilson
附:https://github.com/aosabook/500lines/blob/master/web-server,有兴趣的小伙伴可以参考参考哦!