Python 基于http.server模块实现简单http服务

测试环境

win11专业版

python 3.9

代码实现

# -*- coding:utf-8 -*-

import json
import traceback
import uuid
from http.server import HTTPServer, ThreadingHTTPServer, BaseHTTPRequestHandler
from urllib.parse import urlparse, parse_qs

class MyHTTPRequestHandler(BaseHTTPRequestHandler):
    
    def _send_response(self, status_code, content_type, body):
        '''发送响应信息'''
        self.send_response(status_code)
        self.send_header('Content-type', content_type)
        self.send_header('Cache-Control', 'no-store')  # 防止缓存旧编码响应
        self.end_headers()
        
        self.wfile.write(body.encode('utf-8'))
    
    def _handle_home(self):
        '''访问主页请求处理'''
        
        html = '<meta http-equiv="Content-Type" content="text/html; charset=utf-8"><h1>Home Page</h1>'
        self._send_response(200, 'text/html; charset=utf-8', html)
    
    def _handle_404(self):
        '''请求不存在资源处理'''
        
        # json_respose = {"success": False, "message": "Not Found"}
        # self._send_response(404, 'application/json;charset=utf-8', json.dumps(json_respose))
        
        # Content-Type 指定 charset=utf-8 可避免浏览器GET请求,界面中文显示乱码问题
        self._send_response(404, 'text/html; charset=utf-8', "<h1>404 Not Found</h1>")
    
    def _handle_login(self, login_req_data):
        '''处理登录请求'''
        
        try:
            data = json.loads(login_req_data)
            username = data.get('username')
            password = data.get('password')
            ip = data.get('ip')
            response = {
                'code': 0,
                'token': uuid.uuid4().hex,
                'description': 'success'
            }
            self._send_response(200, 'application/json; charset=utf-8', json.dumps(response))
        except Exception as e:
            error_msg = traceback.format_exc()
            print(error_msg)
            response = {
                'code': 1,
                'token': '',
                'description': error_msg
            }
            self._send_response(500, 'application/json; charset=utf-8', json.dumps(response))
    
    def do_GET(self):
        '''处理GET请求'''
        
        parsed_path = urlparse(self.path)
        path = parsed_path.path
        query_params = parse_qs(parsed_path.query)  # 获取URL携带的查询参数
        # print('收到GET请求参数:', query_params)
        
        if path == '/':
            self._handle_home()
        else:
            self._handle_404()
    
    def do_POST(self):
        '''处理POST请求'''
        
        content_length = int(self.headers['Content-Length'])
        post_data = self.rfile.read(content_length)
        parsed_path = urlparse(self.path)
        path = parsed_path.path
        query_params = parse_qs(parsed_path.query)  # 获取URL 查询参数
        # print("收到POST数据:", post_data.decode())
        
        # 路由匹配逻辑
        if path == '/':
            self._handle_home()
        elif path == '/north/login':
            self._handle_login(post_data.decode())
        else:
            self._handle_404() 
            

if __name__ == '__main__':
    # server = HTTPServer(('0.0.0.0', 8000), MyHandler) # 阻塞式运行
    server = ThreadingHTTPServer(('localhost', 8000), MyHTTPRequestHandler)
    print('正在启动服务,访问地址:http://localhost:8000')
    server.serve_forever()

扩展:如果我们希望服务在处理请求的时,调用其它类实例的方法,或者更新其它类实例的属性,咋处理呢?

答案:将其它类实例初始化为RequestHandler的类属性,然后在相关请求处理函数中进行调用

示例

class Subsystem():
    def __init__(self, http_server_port):
        self.http_server_port = http_server_port
        
        self.server = ThreadingHTTPServer(('0.0.0.0', self.http_server_port), lambda *args: MyHTTPRequestHandler(self, *args))
        self.north_server_info = {}

    def start_http_server(self):
        self.server.serve_forever()
        
    def push_data(self, data):
        '''测试函数'''
        
        logger.info(f'pushing data to server')
        
    def update_north_server_info(self, data):
        '''测试函数'''

        self.north_server_info[data.get('ip')] = data
        
class MyHTTPRequestHandler(BaseHTTPRequestHandler):
    def __init__(self, subsystem, *args, **kwargs):
        self.subsystem = subsystem  # 保存SubSystem实例引用
        super().__init__(*args, **kwargs)
    
    # ....略
    
    def _handle_login(self, login_req_data):
        '''处理登录请求'''
        
        try:
            data = json.loads(login_req_data)
            username = data.get('username')
            password = data.get('password')
            ip = data.get('ip')
            response = {
                'code': 0,
                'token': uuid.uuid4().hex,
                'description': 'success'
            }
            self.subsystem.push_data('')
            self.subsystem.update_north_server_info({'username': username, 'password': password, 'ip': ip})
            self._send_response(200, 'application/json; charset=utf-8', json.dumps(response))
        except Exception as e:
            error_msg = traceback.format_exc()
            logger.error(error_msg)
            response = {
                'code': 1,
                'token':'',
                'description': error_msg
            }
            self._send_response(500, 'application/json; charset=utf-8', json.dumps(response))
            
# 测试
if __name__ == '__main__':
    http_port = 8000
    for i in range(2):
        system = Subsystem(http_port)
        thread = threading.Thread(target=system.start_http_server)
        thread.start()
        http_port += 1

相关介绍

模块简介

http.server 模块定义了用于实现HTTP服务器(Web服务器)的类。

其中一个类,HTTPServersocketserver.TCPServer子类。它创建并监听HTTP套接字,将请求分派给处理程序。创建和运行服务器的代码示例如下:

def run(server_class=HTTPServer, handler_class=BaseHTTPRequestHandler):
    server_address = ('', 8000)
    httpd = server_class(server_address, handler_class)
    httpd.serve_forever()

类及相关函数简介

  • class http.server.HTTPServer(server_address, RequestHandlerClass)

    该类基于基于TCPServer 类构建,通过将将server地址存储为名为server_nameserver_port的实例变量。通过handler访问服务,通常是通过handler的server 实例变量

  • class http.server.ThreadingHTTPServer(server_address, RequestHandlerClass)

    HTTPServer一样,除了通过使用 ThreadingMixIn使用线程处理请求。 3.7版本中新增

HTTPServerThreadingHTTPServer 必须在实例化时给它一个RequestHandlerClass,此模块提供了三种不同的变体:

  • class http.server.BaseHTTPRequestHandler(request, client_address, server)

    此类用于处理到达服务器的HTTP请求。就其本身而言,它无法响应任何实际的HTTP请求;它必须被子类化以处理每个请求方法(例如GETPOST)。BaseHTTPRequestHandler提供了许多类和实例变量以及子类使用的方法。

    BaseHTTPRequestHandler实例变量:

    • client_address

      包含一个 (host, port) 形式,表示客户端地址的元组

    • server

      包含服务器实例

    • close_connection

      handle_one_request() 返回前需要设置的布尔值。指示是否可能需要另一个请求,或者是否应该关闭连接。

    • requestline

      包含HTTP请求行的字符串表示,不含终止CRLF。此属性应通过[handle_one_request()设置。如果没有处理有效的请求行,则应将其设置为空字符串。

    • command

      包含命令(请求类型)。例如,'GET'.

    • path

      包含请求路径。 如果URL的查询参数部分存在,则path包括查询参数。

    • request_version

      包含请求版本字符串,形如’HTTP/1.0’`。

    • headers

      保存由 MessageClass类变量指定的类的实例。此实例解析和管理HTTP请求中的请求头。parse_headers()函数来自 http.client,用于解析标头,它要求http请求提供有效的RFC 2822样式请求头。

    • rfile

      一个io.BufferedIOBase 输入流,准备从可选输入数据的开头读取。

    • wfile

      包含用于将响应写回客户端的输出流。在写入此流时,必须正确遵守HTTP协议,以实现与HTTP客户端的成功交互操作。在3.6版本中更改:这是一个 io.BufferedIOBase 流。

    BaseHTTPRequestHandler属性:

    • server_version

      指定服务器软件版本。你可能希望覆盖此内容。格式是多个空格分隔的字符串,其中每个字符串的格式为name[/version]。例如,'BaseHTTP/0.2'

    • sys_version

      包含Python系统版本,其形式和 version_string方法及 server_version]类变量使用的相同。例如,'Python/1.4'

    • error_message_format

      指定send_error()应使用的格式字符串,用于构建对客户端的错误响应的。默认情况下,基于传递给send_error的状态代码,用responds中的变量填充该字符串。

    • error_content_type

      指定发送到客户端的错误响应的Content-Type HTTP请求头。默认值为 'text/html'

    • protocol_version

      这指定了响应中使用的HTTP协议版本。如果设置为'HTTP/1.1',服务器将允许HTTP持久连接;但是,服务器在对客户端的所有响应中必须包含一个准确的Content-Length请求头(使用send_header())。为了向后兼容,设置默认为“HTTP/1.0”。

    • MessageClass

      指定一个email.message.Message之类的类,用于解析HTTP头。通常,这是不可覆盖的,默认为http.client.HTTPMessage

    • responses

      此属性包含整数错误代码到包含短消息和长消息的两个元素元组的映射。例如,{code: (shortmessage, longmessage)}*shortmessage*通常用作错误响应中的message键,longmessage用作explain键。供send_response_onlysend_error()方法使用。

    BaseHTTPRequestHandler实例方法:

    • handle()

      调用handle_one_request()实现适当的do_*() 方法。

    • handle_one_request()

      此方法将解析请求并将其分发到适当的do_*()方法。你应永远不需要重写它。

    • handle_expect_100()

      当符合HTTP/1.1标准的服务器收到 Expect: 100-continue请求头时,它会以 100 Continue 和“ 200 OK 头作为响应。如果服务器不希望客户端继续,则可以重写此方法以引发错误。例如,服务器可以选择发送417 Expectation Failed 作为响应头,并返回False

      3.2版本中的新功能

    • send_error(code, message=None, explain=None)

      向客户端发送并记录完整的错误回复。数字code指定HTTP错误代码,message作为可选的、简短的、人类可读的错误描述。explain参数可用于提供有关错误的更详细信息;它将使用 error_message_format 属性进行格式化,并在一组完整的标头之后作为响应体发出。response属性包含messageexplain的默认值,如果没有提供值,将使用这些值;对于未知代码,两者的默认值都是字符串???。如果方法是HEAD或响应代码是以下代码之一:

      1xx, 204 No Content, 205 Reset Content, 304 Not Modified,则请求体将为空。

      在版本3.4中变更:错误响应包含Content-Length头。添加了explain参数。

    • send_response(code, message=None)

      添加一个响应头添加到headers缓冲区,并记录接受的请求。HTTP响应行被写入内部缓冲区,后面跟随ServerDate响应头。这两个响应头的值分别从version_string()date_time_string()方法中获取。如果服务器不打算使用send_header()发送任何其他请求头,则send_response()后面应该跟一个end_headers()调用。

      3.3版本中变更:header存储在内部缓冲区,且需要显示调用end_headers()

    • send_header(keyword, value)

      将HTTP头添加到内部缓冲区,当end_headers()或者flush_headers()被调用时,该缓冲区中的内容将被写入到输出流。keyword应指定header,value指定其值。请注意,在send_headers调用完成后,必须调用end_headers()才能完成操作。

      版本3.2中变更:HTTP头存储在内部缓冲区中

    • send_response_only(code, message=None)

      仅发送响应头,服务器向客户端发送100 Continue 响应时使用。响应头未缓冲,直接发送到输出流。如果未指定message,则发送与响应code对应的HTTP消息。

      版本3.2中的新功能

    • end_headers()

      在header缓冲区中添加一个空行(表示响应中HTTP头的结束)并调用flush_headers()

      版本3.2中变更:缓冲的HTTP头被写入输出流

    • flush_headers()

      最后将HTTP头发送到输出流并刷新内部HTTP头缓冲区。3.3版本中的新功能

    • log_request(code=’-‘, size=’-‘)

      记录已接受(成功)的请求。code应指定与响应关联的数字HTTP code。如果可获取响应的大小,则应将其作为size参数传递。

    • log_error()

      当请求无法满足时记录错误。默认情况下,它将消息传递给log_message(),因此它采用相同的参数(format和其它参数)。

    • log_message(format, )

      将任意消息记录到sys.stderr。这通常会被重写以创建自定义错误日志记录机制。format参数是一个标准的类printf风格的格式字符串,其中log_message()的附加参数作为格式化的输入。客户端ip地址和当前日期和时间都会作为记录的每条消息的前缀。

    • version_string()

      返回服务器软件的版本字符串。这是server_version以及sys_version属性的混合。

    • date_time_string(timestamp=None)

      返回timestamp给出的日期和时间(必须为None或采用time.time()返回的格式),格式化为消息头。如果省略timestamp,则使用当前日期和时间。结果看起来像'Sun, 06 Nov 1994 08:49:37 GMT'

    • log_date_time_string()

      返回当前日期和时间,格式化为日志记录格式。

    • address_string()

      返回客户端地址。

      3.3版本中变更:以前执行了名称查找。为了避免名称解析延迟,它现在总是返回IP地址。

  • class http.server.SimpleHTTPRequestHandler(request, client_address, server, directory=None)

    介绍略

  • class http.server.``CGIHTTPRequestHandler(*request*, client_address, server)

    介绍略

参考链接

https://docs.python.org/3.9/library/http.server.html