使用Python实现静态服务器
更新时间:2023-12-24第一段:需求分析与技术选型
在开始编写代码之前,我们首先要明确这个静态服务器实现的功能,即我们需要部署一个能够处理HTTP请求的服务器,用以向客户端返回静态资源文件(如HTML、CSS、JS、图片等)。我们可以使用Python内置的HTTP服务器模块 http.server 实现。该模块默认提供简单的文件传输服务,但是因为其安全性较低,因此需要改写并加强完成我们的需求。
import http.server
import socketserver
PORT = 8000
Handler = http.server.SimpleHTTPRequestHandler
httpd = socketserver.TCPServer(("", PORT), Handler)
print("Serving HTTP on port", PORT)
httpd.serve_forever()
这段代码会启动一个简单的Python HTTP服务器,并将当前目录下的文件提供 HTTP 服务,监听端口号为 8000。当然,这样还没达到完美的实现静态服务器的要求,因为该模块并没有提供对响应头的处理,也不允许设置缓存以以提高效率。
第二段:响应头与MIME类型配置
我们需要在 HTTP 响应头中添加额外的信息,如服务器名、缓存控制头信息等,并需要在访问文件的时候,浏览器能够正常识别文件类型,以正确渲染页面。因此,需要我们对 mimetype 进行配置,将它存储在一个映射表 mimeTypeTable 中,之后会利用文件后缀名来读取相应的 MIME 类型并添加到响应头中。
import http.server
import socketserver
import urllib
import os
PORT = 8000
Handler = http.server.SimpleHTTPRequestHandler
class MyHandler(http.server.SimpleHTTPRequestHandler):
def do_GET(self):
path = self.translate_path(self.path)
f = None
if os.path.isdir(path):
parts = urllib.parse.urlsplit(self.path)
if not parts.path.endswith('/'):
# redirect browser - doing basically what apache does
self.send_response(301)
new_parts = (parts[0], parts[1], parts[2] + '/',
parts[3], parts[4])
new_url = urllib.parse.urlunsplit(new_parts)
self.send_header('Location', new_url)
self.end_headers()
return
for index in "index.html", "index.htm":
index = os.path.join(path, index)
if os.path.exists(index):
path = index
break
else:
return self.list_directory(path)
ctype = self.guess_type(path)
# Return 404 error if file not exists
if not os.path.exists(path):
self.send_error(404, "File not found")
return None
# Override Header
self.send_response(200)
self.send_header("Content-type", ctype)
self.send_header("Server", "Test Server")
self.send_header("Cache-Control", "max-age=86400")
self.end_headers()
try:
f = open(path, 'rb')
self.copyfile(f, self.wfile)
finally:
if f:
f.close()
httpd = socketserver.TCPServer(("", PORT), MyHandler)
print("Serving HTTP on port", PORT)
httpd.serve_forever()
第三段:缓存策略
下面是我们添加的缓存控制头信息,这样可以帮助我们实现在客户端对静态资源的缓存。由于资源只有在需要更新内容时才会改变,所以我们可以将普通资源(如js、css、图片等)放到浏览器的本地缓存中。这样对于经常访问的资源,可以大大提高访问效率。max-age 表示客户端缓存的时间,通常为1天,这个时间会在保险期限内不会让客户端再向服务器请求资源,浏览器会从本地缓存中加载资源。
self.send_header("Cache-Control", "max-age=86400")
第四段:访问日志
最后,我们需要加入访问日志,方便我们对服务器的运行状态进行监控,如果需要对访问量进行分析,也可以利用该日志来进行。下面的代码演示了如何记录访问日志,并将日志保存到 server.log 文件中:
import http.server
import socketserver
import urllib
import os
PORT = 8000
Handler = http.server.SimpleHTTPRequestHandler
class MyHandler(http.server.SimpleHTTPRequestHandler):
log_fd = None
def do_GET(self):
path = self.translate_path(self.path)
f = None
if os.path.isdir(path):
parts = urllib.parse.urlsplit(self.path)
if not parts.path.endswith('/'):
# redirect browser - doing basically what apache does
self.send_response(301)
new_parts = (parts[0], parts[1], parts[2] + '/',
parts[3], parts[4])
new_url = urllib.parse.urlunsplit(new_parts)
self.send_header('Location', new_url)
self.end_headers()
return
for index in "index.html", "index.htm":
index = os.path.join(path, index)
if os.path.exists(index):
path = index
break
else:
return self.list_directory(path)
ctype = self.guess_type(path)
# Return 404 error if file not exists
if not os.path.exists(path):
self.send_error(404, "File not found")
return None
# Override Header
self.send_response(200)
self.send_header("Content-type", ctype)
self.send_header("Server", "Test Server")
self.send_header("Cache-Control", "max-age=86400")
self.end_headers()
# Logging Access
self.log_access()
try:
f = open(path, 'rb')
self.copyfile(f, self.wfile)
finally:
if f:
f.close()
def log_access(self):
log_message = '{} - - [{}] "{}" {} {}\n'.format(
self.client_address[0],
self.log_date_time_string(),
self.requestline,
self.status,
self.responses.get(self.status)[0]
)
if self.log_fd is None:
self.log_fd = open("server.log", 'a')
self.log_fd.write(log_message)
self.log_fd.flush()
def finish(self):
if self.log_fd is not None:
self.log_fd.close()
httpd = socketserver.TCPServer(("", PORT), MyHandler)
print("Serving HTTP on port", PORT)
httpd.serve_forever()