吾羽 發表於 2025-6-5 00:38:00

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

<h2 id="测试环境">测试环境</h2>
<p>win11专业版</p>
<p>python 3.9</p>
<h2 id="代码实现">代码实现</h2>
<pre><code class="language-python"># -*- 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 = '&lt;meta http-equiv="Content-Type" content="text/html; charset=utf-8"&gt;&lt;h1&gt;Home Page&lt;/h1&gt;'
      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', "&lt;h1&gt;404 Not Found&lt;/h1&gt;")
   
    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()
</code></pre>
<p>扩展:如果我们希望服务在处理请求的时,调用其它类实例的方法,或者更新其它类实例的属性,咋处理呢?</p>
<p>答案:将其它类实例初始化为<code>RequestHandler</code>的类属性,然后在相关请求处理函数中进行调用</p>
<p><strong>示例</strong></p>
<pre><code class="language-python">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
      
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
</code></pre>
<h2 id="相关介绍">相关介绍</h2>
<h3 id="模块简介">模块简介</h3>
<p><code>http.server</code> 模块定义了用于实现HTTP服务器(Web服务器)的类。</p>
<p>其中一个类,<code>HTTPServer</code>是<code>socketserver.TCPServer</code>子类。它创建并监听HTTP套接字,将请求分派给处理程序。创建和运行服务器的代码示例如下:</p>
<pre><code class="language-python">def run(server_class=HTTPServer, handler_class=BaseHTTPRequestHandler):
    server_address = ('', 8000)
    httpd = server_class(server_address, handler_class)
    httpd.serve_forever()
</code></pre>
<h3 id="类及相关函数简介">类及相关函数简介</h3>
<ul>
<li>
<p><code>class http.server.HTTPServer(server_address, RequestHandlerClass)</code></p>
<p>该类基于基于<code>TCPServer</code> 类构建,通过将将server地址存储为名为<code>server_name</code>和<code>server_port</code>的实例变量。通过handler访问服务,通常是通过handler的<code>server</code> 实例变量</p>
</li>
<li>
<p><code>class http.server.ThreadingHTTPServer(server_address, RequestHandlerClass)</code></p>
<p>同<code>HTTPServer</code>一样,除了通过使用 <code>ThreadingMixIn</code>使用线程处理请求。 3.7版本中新增</p>
</li>
</ul>
<p><code>HTTPServer</code> 和<code>ThreadingHTTPServer</code> 必须在实例化时给它一个<em>RequestHandlerClass</em>,此模块提供了三种不同的变体:</p>
<ul>
<li>
<p><code>class http.server.BaseHTTPRequestHandler(request, client_address, server)</code></p>
<p>此类用于处理到达服务器的HTTP请求。就其本身而言,它无法响应任何实际的HTTP请求;它必须被子类化以处理每个请求方法(例如<code>GET</code>或<code>POST</code>)。<code>BaseHTTPRequestHandler</code>提供了许多类和实例变量以及子类使用的方法。</p>
<p><strong><code>BaseHTTPRequestHandler</code>实例变量</strong>:</p>
<ul>
<li>
<p><code>client_address</code></p>
<p>包含一个 <code>(host, port)</code> 形式,表示客户端地址的元组</p>
</li>
<li>
<p><code>server</code></p>
<p>包含服务器实例</p>
</li>
<li>
<p><code>close_connection</code></p>
<p><code>handle_one_request()</code> 返回前需要设置的布尔值。指示是否可能需要另一个请求,或者是否应该关闭连接。</p>
</li>
<li>
<p><code>requestline</code></p>
<p>包含HTTP请求行的字符串表示,不含终止<code>CRLF</code>。此属性应通过[<code>handle_one_request()</code>设置。如果没有处理有效的请求行,则应将其设置为空字符串。</p>
</li>
<li>
<p><code>command</code></p>
<p>包含命令(请求类型)。例如,<code>'GET'</code>.</p>
</li>
<li>
<p><code>path</code></p>
<p>包含请求路径。如果URL的查询参数部分存在,则<code>path</code>包括查询参数。</p>
</li>
<li>
<p><code>request_version</code></p>
<p>包含请求版本字符串,形如'HTTP/1.0'`。</p>
</li>
<li>
<p><code>headers</code></p>
<p>保存由 <code>MessageClass</code>类变量指定的类的实例。此实例解析和管理HTTP请求中的请求头。<code>parse_headers()</code>函数来自 <code>http.client</code>,用于解析标头,它要求http请求提供有效的<strong>RFC 2822</strong>样式请求头。</p>
</li>
<li>
<p><code>rfile</code></p>
<p>一个<code>io.BufferedIOBase</code> 输入流,准备从可选输入数据的开头读取。</p>
</li>
<li>
<p><code>wfile</code></p>
<p>包含用于将响应写回客户端的输出流。在写入此流时,必须正确遵守HTTP协议,以实现与HTTP客户端的成功交互操作。<em>在3.6版本中更改:</em>这是一个 <code>io.BufferedIOBase</code> 流。</p>
</li>
</ul>
<p><strong><code>BaseHTTPRequestHandler</code>属性:</strong></p>
<ul>
<li>
<p><code>server_version</code></p>
<p>指定服务器软件版本。你可能希望覆盖此内容。格式是多个空格分隔的字符串,其中每个字符串的格式为<code>name</code>。例如,<code>'BaseHTTP/0.2'</code>。</p>
</li>
<li>
<p><code>sys_version</code></p>
<p>包含Python系统版本,其形式和 <code>version_string</code>方法及 <code>server_version</code>]类变量使用的相同。例如,<code>'Python/1.4'</code>。</p>
</li>
<li>
<p><code>error_message_format</code></p>
<p>指定<code>send_error()</code>应使用的格式字符串,用于构建对客户端的错误响应的。默认情况下,基于传递给<code>send_error</code>的状态代码,用<code>responds</code>中的变量填充该字符串。</p>
</li>
<li>
<p><code>error_content_type</code></p>
<p>指定发送到客户端的错误响应的<code>Content-Type</code> HTTP请求头。默认值为 <code>'text/html'</code>。</p>
</li>
<li>
<p><code>protocol_version</code></p>
<p>这指定了响应中使用的HTTP协议版本。如果设置为<code>'HTTP/1.1'</code>,服务器将允许HTTP持久连接;但是,服务器在对客户端的所有响应中<em>必须</em>包含一个准确的<code>Content-Length</code>请求头(使用<code>send_header()</code>)。为了向后兼容,设置默认为“HTTP/1.0”。</p>
</li>
<li>
<p><code>MessageClass</code></p>
<p>指定一个<code>email.message.Message</code>之类的类,用于解析HTTP头。通常,这是不可覆盖的,默认为<code>http.client.HTTPMessage</code>。</p>
</li>
<li>
<p><code>responses</code></p>
<p>此属性包含整数错误代码到包含短消息和长消息的两个元素元组的映射。例如,<code>{code: (shortmessage, longmessage)}</code>。<code>*shortmessage*</code>通常用作错误响应中的<code>message</code>键,<code>longmessage</code>用作<code>explain</code>键。供<code>send_response_only</code>和<code>send_error()</code>方法使用。</p>
</li>
</ul>
<p><strong><code>BaseHTTPRequestHandler</code>实例方法:</strong></p>
<ul>
<li>
<p><code>handle</code>()</p>
<p>调用<code>handle_one_request()</code>实现适当的<code>do_*()</code> 方法。</p>
</li>
<li>
<p><code>handle_one_request</code>()</p>
<p>此方法将解析请求并将其分发到适当的<code>do_*()</code>方法。你应永远不需要重写它。</p>
</li>
<li>
<p><code>handle_expect_100</code>()</p>
<p>当符合HTTP/1.1标准的服务器收到 <code>Expect: 100-continue</code>请求头时,它会以 <code>100 Continue</code> 和“ <code>200 OK</code> 头作为响应。如果服务器不希望客户端继续,则可以重写此方法以引发错误。例如,服务器可以选择发送<code>417 Expectation Failed</code> 作为响应头,并返回<code>False</code>。</p>
<p>3.2版本中的新功能</p>
</li>
<li>
<p><code>send_error</code>(<em>code</em>, <em>message=None</em>, <em>explain=None</em>)</p>
<p>向客户端发送并记录完整的错误回复。数字<em>code</em>指定HTTP错误代码,<em>message</em>作为可选的、简短的、人类可读的错误描述。<em>explain</em>参数可用于提供有关错误的更详细信息;它将使用 <code>error_message_format</code> 属性进行格式化,并在一组完整的标头之后作为响应体发出。response属性包含<em>message</em>和<em>explain</em>的默认值,如果没有提供值,将使用这些值;对于未知代码,两者的默认值都是字符串<code>???</code>。如果方法是<code>HEAD</code>或响应代码是以下代码之一:</p>
<p><code>1xx</code>, <code>204 No Content</code>, <code>205 Reset Content</code>, <code>304 Not Modified</code>,则请求体将为空。</p>
<p><em>在版本3.4中变更:</em>错误响应包含<code>Content-Length</code>头。添加了<em>explain</em>参数。</p>
</li>
<li>
<p><code>send_response</code>(<em>code</em>, <em>message=None</em>)</p>
<p>添加一个响应头添加到headers缓冲区,并记录接受的请求。HTTP响应行被写入内部缓冲区,后面跟随<em>Server</em>和<em>Date</em>响应头。这两个响应头的值分别从<code>version_string()</code> 和<code>date_time_string()</code>方法中获取。如果服务器不打算使用<code>send_header()</code>发送任何其他请求头,则<code>send_response()</code>后面应该跟一个<code>end_headers()</code>调用。</p>
<p><em>3.3版本中变更:</em>header存储在内部缓冲区,且需要显示调用<code>end_headers()</code>。</p>
</li>
<li>
<p><code>send_header</code>(<em>keyword</em>, <em>value</em>)</p>
<p>将HTTP头添加到内部缓冲区,当<code>end_headers()</code>或者<code>flush_headers()</code>被调用时,该缓冲区中的内容将被写入到输出流。<em>keyword</em>应指定header,<em>value</em>指定其值。请注意,在<code>send_headers</code>调用完成后,必须调用<code>end_headers()</code>才能完成操作。</p>
<p><em>版本3.2中变更:</em>HTTP头存储在内部缓冲区中</p>
</li>
<li>
<p><code>send_response_only</code>(<em>code</em>, <em>message=None</em>)</p>
<p>仅发送响应头,服务器向客户端发送<code>100 Continue</code> 响应时使用。响应头未缓冲,直接发送到输出流。如果未指定<em>message</em>,则发送与响应<em>code</em>对应的HTTP消息。</p>
<p><em>版本3.2中的新功能</em></p>
</li>
<li>
<p><code>end_headers</code>()</p>
<p>在header缓冲区中添加一个空行(表示响应中HTTP头的结束)并调用<code>flush_headers()</code>。</p>
<p><em>版本3.2中变更:</em>缓冲的HTTP头被写入输出流</p>
</li>
<li>
<p><code>flush_headers</code>()</p>
<p>最后将HTTP头发送到输出流并刷新内部HTTP头缓冲区。<em>3.3版本中的新功能</em>。</p>
</li>
<li>
<p><code>log_request</code>(<em>code='-'</em>, <em>size='-'</em>)</p>
<p>记录已接受(成功)的请求。<em>code</em>应指定与响应关联的数字HTTP code。如果可获取响应的大小,则应将其作为<em>size</em>参数传递。</p>
</li>
<li>
<p><code>log_error</code>(<em>...</em>)</p>
<p>当请求无法满足时记录错误。默认情况下,它将消息传递给<code>log_message()</code>,因此它采用相同的参数(<em>format</em>和其它参数)。</p>
</li>
<li>
<p><code>log_message</code>(<em>format</em>, <em>...</em>)</p>
<p>将任意消息记录到<code>sys.stderr</code>。这通常会被重写以创建自定义错误日志记录机制。<em>format</em>参数是一个标准的类printf风格的格式字符串,其中<code>log_message()</code>的附加参数作为格式化的输入。客户端ip地址和当前日期和时间都会作为记录的每条消息的前缀。</p>
</li>
<li>
<p><code>version_string</code>()</p>
<p>返回服务器软件的版本字符串。这是<code>server_version</code>以及<code>sys_version</code>属性的混合。</p>
</li>
<li>
<p><code>date_time_string</code>(<em>timestamp=None</em>)</p>
<p>返回<em>timestamp</em>给出的日期和时间(必须为<code>None</code>或采用<code>time.time()</code>返回的格式),格式化为消息头。如果省略<em>timestamp</em>,则使用当前日期和时间。结果看起来像<code>'Sun, 06 Nov 1994 08:49:37 GMT'</code>。</p>
</li>
<li>
<p><code>log_date_time_string</code>()</p>
<p>返回当前日期和时间,格式化为日志记录格式。</p>
</li>
<li>
<p><code>address_string</code>()</p>
<p>返回客户端地址。</p>
<p><em>3.3版本中变更:</em>以前执行了名称查找。为了避免名称解析延迟,它现在总是返回IP地址。</p>
</li>
</ul>
</li>
<li>
<p><code>class http.server.SimpleHTTPRequestHandler(request, client_address, server, directory=None)</code></p>
<p>介绍略</p>
</li>
<li>
<p><code>class http.server.``CGIHTTPRequestHandler(*request*, client_address, server)</code></p>
<p>介绍略</p>
</li>
</ul>
<h2 id="参考链接">参考链接</h2>
<p>https://docs.python.org/3.9/library/http.server.html</p>


</div>
<div id="MySignature" role="contentinfo">
    <div id="AllanboltSignature">
    <p id="PSignature" style="border: #330066 1px dashed; padding: 5px 10px; font-family: 微软雅黑; font-size: 11px">
      <span style="margin-left: 5px; font-weight: bold">作者:授客</span>
                <br>
      <span style="margin-left: 5px; font-weight: bold">微信/QQ:1033553122
                <br>
      <span style="margin-left: 5px; font-weight: bold">全国软件测试QQ交流群:7156436</span></span>
                <br>
      <span style="margin-left: 5px; font-weight: bold">Git地址:https://gitee.com/ishouke</span>
                <br>
      <span style="margin-left: 5px; font-weight: bold">友情提示:<span>限于时间仓促,文中可能存在错误,欢迎指正、评论!</span>
      <br>
                <span><span style="margin-left: 5px; font-weight: bold; color: red">作者五行缺钱,如果觉得文章对您有帮助,请扫描下边的二维码打赏作者,金额随意</span>,您的支持将是我继续创作的源动力,<span style="margin-left: 10px; font-weight: bold; color: red">打赏后如有任何疑问,请联系我!!!</span></span>
      <br>
                <span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;微信打赏&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;
                支付宝打赏&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;全国软件测试交流QQ群&nbsp;&nbsp;<br>
                <img src="https://www.cnblogs.com/images/cnblogs_com/shouke/1368383/t_%E5%BE%AE%E4%BF%A1%E6%94%B6%E6%AC%BE%E7%A0%81.bmp">
                &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<img src="https://www.cnblogs.com/images/cnblogs_com/shouke/1368383/t_%E6%94%AF%E4%BB%98%E5%AE%9D%E6%94%B6%E6%AC%BE%E7%A0%81.bmp">
                &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<img src="https://www.cnblogs.com/images/cnblogs_com/shouke/1368383/t_qq%E7%BE%A4.bmp">
    </span></span></p>
</div><br><br>
来源:https://www.cnblogs.com/shouke/p/18911392
頁: [1]
查看完整版本: Python 基于http.server模块实现简单http服务