c语言编程笔录

首页 >   > 笔记大全

笔记大全

使用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()