Python实现简单的Web服务器
1 项目来源
本课程核心部分来自500 lines or less项目,作者是 Mozilla 的 Greg Wilson。项目代码使用 MIT 协议,项目文档使用 http://creativecommons.org/licenses/by/3.0/legalcode 协议。
2.知识储备:HTTP的了解
暂不叙述。
3.开始项目
3.1 hello,web——我的第一个web服务器
3.1.1 效果图
通过抓包,
3.1.2 代码
from http.server import BaseHTTPRequestHandler, HTTPServer
class RequestHandler(BaseHTTPRequestHandler):
'''处理请求并返回页面'''
# 页面模板
Page = '''\
<html>
<body>
<p>Hello, web!</p>
</body>
</html>
'''
# 处理一个GET请求
def do_GET(self):
self.send_response(200)
self.send_header("Content-Type", "text/html")
self.send_header("Content-Length", str(len(self.Page)))
self.end_headers()
self.wfile.write(self.Page.encode('utf-8'))
if __name__ == '__main__':
serverAddress = ('', 8080)
server = HTTPServer(serverAddress, RequestHandler)
server.serve_forever()
关闭服务器怎么做呢?
将运行的程序关闭,即可以将我们的web服务器关闭。
假设一种情况,我们不确定我们使用的是哪个程序启动的服务器怎么办?
可以使用win10的powershell控制台来关闭。
然后我们发现:
于是我们能够关闭我们的python文件。
模块的 BaseHTTPRequestHandler 类会帮我们处理对请求的解析,并通过确定请求的方法来调用其对应的函数,比如方法是 GET ,该类就会调用名为 do_GET 的方法。RequestHandler 继承了 BaseHTTPRequestHandler 并重写了 do_GET 方法,其效果如代码所示是返回 Page 的内容。 Content-Type 告诉了客户端要以处理html文件的方式处理返回的内容。end_headers 方法会插入一个空白行,如之前的request结构图所示。
重构代码
from http.server import BaseHTTPRequestHandler, HTTPServer
class RequestHandler(BaseHTTPRequestHandler):
# ...页面模板...
Page = '''\
<html>
<body>
<table>
<tr> <td>Header</td> <td>Value</td> </tr>
<tr> <td>Date and time</td> <td>{date_time}</td> </tr>
<tr> <td>Client host</td> <td>{client_host}</td> </tr>
<tr> <td>Client port</td> <td>{client_port}</td> </tr>
<tr> <td>Command</td> <td>{command}</td> </tr>
<tr> <td>Path</td> <td>{path}</td> </tr>
</table>
</body>
</html>
'''
def do_GET(self):
page = self.create_page()
self.send_content(page)
def create_page(self):
values = {
'date_time' : self.date_time_string(),
'client_host' : self.client_address[0],
'client_port' : self.client_address[1],
'command' : self.command,
'path' : self.path
}
page = self.Page.format(**values)
return page
def send_content(self, page):
self.send_response(200)
self.send_header("Content-type", "text/html")
self.send_header("Content-Length", str(len(page)))
self.end_headers()
self.wfile.write(page.encode('utf-8'))
if __name__ == '__main__':
serverAddress = ('', 8080)
server = HTTPServer(serverAddress, RequestHandler)
server.serve_forever()
客户端端口号随机。
实现静态网页访问
import sys, os
from http.server import BaseHTTPRequestHandler,HTTPServer
# 一个服务器异常,当发生异常抛出这个。
class ServerException(Exception):
'''服务器内部错误'''
pass
class RequestHandler(BaseHTTPRequestHandler):
Error_Page = """\
<html>
<body>
<h1>Error accessing {path}</h1>
<p>{msg}</p>
</body>
</html>
"""
def do_GET(self):
try:
# 文件完整路径
# print(os.getcwd() + self.path)
#如C:\Users\Administrator\Desktop\我的学习资料\python\web_server /plain.html
full_path = os.getcwd() + self.path
# 如果该路径不存在...
if not os.path.exists(full_path):
#抛出异常:文件未找到
raise ServerException("'{0}' not found".format(self.path))
# 如果该路径是一个文件
elif os.path.isfile(full_path):
#调用 handle_file 处理该文件
self.handle_file(full_path)
# 如果该路径不是一个文件
else:
#抛出异常:该路径为不知名对象
raise ServerException("Unknown object '{0}'".format(self.path))
# 处理异常
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 create_page(self):
values = {
'date_time' : self.date_time_string(),
'client_host' : self.client_address[0],
'client_port' : self.client_address[1],
'command' : self.command,
'path' : self.path
}
page = self.Page.format(**values)
return page
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)
# 文件操纵:发送内容
def handle_file(self,full_path):
try:
with open(full_path, 'rb') as reader:
content = reader.read()
self.send_content(content)
except IOError as msg:
msg = "'{0}' cannot be read: {1}".format(self.path, msg)
self.handle_error(msg)
if __name__ == '__main__':
serverAddress = ('', 8080)
server = HTTPServer(serverAddress, RequestHandler)
server.serve_forever()
此时,访问了错误的路径:
正确的页面:
访问网址直接链接到首页
import sys, os
from http.server import BaseHTTPRequestHandler,HTTPServer
class ServerException(Exception):
'''服务器内部错误'''
pass
class case_no_file(object):
'''该路径不存在'''
def test(self, handler):
return not os.path.exists(handler.full_path)
def act(self, handler):
raise ServerException("'{0}' not found".format(handler.path))
class case_existing_file(object):
'''该路径是文件'''
def test(self, handler):
return os.path.isfile(handler.full_path)
def act(self, handler):
handler.handle_file(handler.full_path)
class case_always_fail(object):
'''所有情况都不符合时的默认处理类'''
def test(self, handler):
return True
def act(self, handler):
raise ServerException("Unknown object '{0}'".format(handler.path))
class case_directory_index_file(object):
def index_path(self, handler):
return os.path.join(handler.full_path, 'index.html')
#判断目标路径是否是目录&&目录下是否有index.html
def test(self, handler):
return os.path.isdir(handler.full_path) and \
os.path.isfile(self.index_path(handler))
#响应index.html的内容
def act(self, handler):
handler.handle_file(self.index_path(handler))
class RequestHandler(BaseHTTPRequestHandler):
'''
请求路径合法则返回相应处理
否则返回错误页面
'''
Cases = [case_no_file(),
case_existing_file(),
case_directory_index_file(),
case_always_fail()]
# 错误页面模板
Error_Page = """\
<html>
<body>
<h1>Error accessing {path}</h1>
<p>{msg}</p>
</body>
</html>
"""
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)
def handle_file(self, full_path):
try:
with open(full_path, 'rb') as reader:
content = reader.read()
self.send_content(content)
except IOError as msg:
msg = "'{0}' cannot be read: {1}".format(self.path, msg)
self.handle_error(msg)
if __name__ == '__main__':
serverAddress = ('', 8080)
server = HTTPServer(serverAddress, RequestHandler)
server.serve_forever()
使用CGI协议/并且对源代码进行重写。
#-*- coding:utf-8 -*-
import sys, os, subprocess
from http.server import BaseHTTPRequestHandler,HTTPServer
#-------------------------------------------------------------------------------
class ServerException(Exception):
'''服务器内部错误'''
pass
#-------------------------------------------------------------------------------
class base_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.'
#-------------------------------------------------------------------------------
class case_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))
#-------------------------------------------------------------------------------
class case_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)
#-------------------------------------------------------------------------------
class case_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)
#-------------------------------------------------------------------------------
class case_directory_index_file(base_case):
'''在根路径下返回主页文件'''
def test(self, handler):
return os.path.isdir(handler.full_path) and \
os.path.isfile(self.index_path(handler))
def act(self, handler):
self.handle_file(handler, self.index_path(handler))
#-------------------------------------------------------------------------------
class case_always_fail(base_case):
'''默认处理'''
def test(self, handler):
return True
def act(self, handler):
raise ServerException("Unknown object '{0}'".format(handler.path))
#-------------------------------------------------------------------------------
class RequestHandler(BaseHTTPRequestHandler):
'''
请求路径合法则返回相应处理
否则返回错误页面
'''
Cases = [case_no_file(),
case_cgi_file(),
case_existing_file(),
case_directory_index_file(),
case_always_fail()]
# 错误页面模板
Error_Page = """\
<html>
<body>
<h1>Error accessing {path}</h1>
<p>{msg}</p>
</body>
</html>
"""
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)
#-------------------------------------------------------------------------------
if __name__ == '__main__':
serverAddress = ('', 8080)
server = HTTPServer(serverAddress, RequestHandler)
server.serve_forever()