Bootstrap

python模块之aioHttp 异步请求

一、简介

aiohttp 是一个基于异步的 Python HTTP 客户端/服务器框架,它允许你编写高性能的异步网络应用程序。它建立在 Python 的协程和异步 I/O 模型上,并利用了 Python 3.5+ 中引入的 asyncio 库。官网:https://docs.aiohttp.org

  1. 异步支持:aiohttp 基于 asyncio 库,充分利用了 Python 的协程和异步 I/O 模型,可以处理大量并发请求而不阻塞其他任务。这使得它非常适合编写高性能的网络应用程序。
    客户端功能:aiohttp 提供了一个强大的异步 HTTP 客户端,可以发送各种类型的 HTTP 请求(GET、POST、PUT、DELETE 等)。它支持自定义请求头、查询参数、请求体,并可以处理响应的文本、字节、JSON 等格式。
  2. 服务器功能:aiohttp 还提供了一个异步 HTTP 服务器框架,可以用于构建高性能的 Web 应用程序。你可以定义路由、处理请求和响应、处理表单数据、设置静态文件等。
  3. WebSocket 支持:aiohttp 提供了对 WebSocket 协议的支持,可以轻松地建立双向通信的实时应用程序。
    中间件支持:aiohttp 具有中间件机制,你可以编写自定义中间件来处理请求和响应,例如身份验证、日志记录、错误处理等。
  4. Cookie 和会话管理:aiohttp 提供了对 Cookie 的支持,可以方便地设置和处理 Cookie。此外,它还支持会话管理,可以跟踪用户的会话状态。
  5. SSL/TLS 支持:aiohttp 支持使用 SSL/TLS 加密进行安全的 HTTPS 通信。
  6. 扩展性:aiohttp 具有模块化的设计,允许你使用插件和扩展来增加功能,例如模板引擎、数据库集成等
二、安装
pip install aiohttp
三、ClientSession 类的方法和属性
  1. aiohttp.ClientSession():异步 HTTP 客户端会话,用于管理与服务器的连接池,返回ClientSession对象
  • connector:连接器对象,用于管理连接池和处理底层的网络连接。
  • loop:事件循环对象。
  • cookies:CookieJar 对象,用于管理会话的 Cookie。
  • headers:默认的请求头部信息,可以是字典。
  • skip_auto_headers:不自动添加的默认头部信息,可以是列表。
  • auth:默认的身份验证信息,可以是 aiohttp.BasicAuth对象。
  • json_serialize:JSON 序列化函数,默认为 json.dumps。
  • version:HTTP 版本,默认为 aiohttp.HttpVersion11。
  • cookie_jar:CookieJar 对象,用于管理会话的 Cookie。
  • read_timeout:读取超时时间(秒)。
  • conn_timeout:连接超时时间(秒)。
  • raise_for_status:是否在收到非 2xx 响应时引发异常。
  • trust_env:是否信任环境变量中的代理设置。
  • ws_response_class:WebSocket 响应类,默认为 aiohttp.ClientResponse。
  1. ClientSession.request(method, url, **kwargs):发送自定义的 HTTP 请求,并返回一个 ClientResponse 对象。
  • method:请求的方法,如 ‘GET’、‘POST’ 等。
  • url:请求的 URL。
  • params:URL 查询参数,可以是字典。
  • data:请求的数据,可以是字符串或字节。
  • json:JSON 数据,会自动设置请求的 ‘Content-Type’ 为 ‘application/json’。
  • headers:请求的头部信息,可以是字典。
  • skip_auto_headers:不自动添加的头部信息,可以是列表
  • cookies:请求的 Cookie,可以是字典或 http.cookies.SimpleCookie 对象。
  • auth:身份验证信息,可以是 aiohttp.BasicAuth对象。
  • allow_redirects:是否允许重定向。
  • max_redirects:最大重定向次数。
  • encoding:请求的编码方式。
  • compress:是否启用压缩。
  • chunked:是否启用分块传输编码。
  • expect100:是否启用 “Expect: 100-continue”。
  • read_until_eof:是否读取直到响应结束。
  • proxy:代理 URL。
  • proxy_auth:代理身份验证信息,可以是 aiohttp.BasicAuth 对象。
  • timeout:请求超时时间(秒)。
  • ssl:SSL/TLS 配置。
  • proxy_headers:代理请求的头部信息,可以是字典。
  • trace_request_ctx:请求上下文。
  1. ClientSession.get(url, **kwargs):发送 GET 请求,参数与 request() 方法相同,返回一个 ClientResponse 对象
  2. ClientSession.post(url, **kwargs):发送 POST 请求,参数与 request() 方法相同,返回一个 ClientResponse 对象
  3. ClientSession.options(url, **kwargs):发送 OPTIONS 请求。参数与 request() 方法相同,返回一个 ClientResponse 对象
  4. ClientSession.head(url, **kwargs、s):发送 HEAD 请求。参数与 request() 方法相同,返回一个 ClientResponse 对象
  5. ClientSession.put(url, **kwargs):发送 PUT 请求。参数与 request() 方法相同,返回一个 ClientResponse 对象
  6. ClientSession.patch(url, **kwargs):发送 PATCH 请求。参数与 request() 方法相同,返回一个 ClientResponse 对象
  7. ClientSession.delete(url, **kwargs):发送 DELETE 请求。参数与 request() 方法相同,返回一个 ClientResponse 对象
  8. ClientSession.ws_connect(url, **kwargs):创建一个 WebSocket 连接。
  • url:WebSocket 的 URL。
  • protocols:协议列表。
  • timeout:连接超时时间(秒)。
  • max_msg_size:最大消息大小。
  • autoclose:是否自动关闭连接。
  • autoping:是否自动发送心跳。
  • heartbeat:心跳间隔时间(秒)。
  • headers:请求的头部信息,可以是字典。
  • proxy:代理 URL。
  • proxy_auth:代理身份验证信息,可以是 aiohttp.BasicAuth对象。
  • ssl:SSL/TLS 配置。
  • origin:请求的来源。
  • compress:是否启用压缩。
  • method:请求的方法,默认为 ‘GET’。
  • auth:身份验证信息,可以是 aiohttp.BasicAuth对象。
  • loop:事件循环对象
  1. ClientSession.close():关闭 ClientSession 对象,释放与服务器的连接。
  2. ClientSession.cookie_jar:一个 CookieJar 对象,用于管理 Cookie。
  3. ClientSession.headers:一个 CIMultiDictProxy 对象,表示请求的头部信息。
  4. ClientSession.cookies:默认的 Cookie。
  5. ClientSession.connector:一个 TCPConnector 对象,用于管理与服务器的连接。
  6. ClientSession.timeout:一个 Timeout 对象,用于设置请求的超时时间。
  7. ClientSession.get_connector(self):获取连接器对象。
  8. ClientSession.loop:事件循环对象。
  9. ClientSession.auth:默认的身份验证信息。
  10. ClientSession.raise_for_status:是否在收到非 2xx 响应时引发异常。
  11. ClientSession.trust_env:是否信任环境变量中的代理设置。
四、ClientResponse 类的方法和属性
  1. ClientResponse.status:响应的状态码。
  2. ClientResponse.reason:响应的原因短语。
  3. ClientResponse.headers:响应的头部信息,一个 CIMultiDictProxy 对象。
  4. ClientResponse.cookies:响应的 Cookie,一个 CookieJar 对象。
  5. ClientResponse.content:响应的内容,以字节形式返回
  • content.read():读取响应内容的原始字节。await response.content.read()
  • content.readany():读取响应内容的原始字节,返回一个 bytes 对象。await response.content.readany()
  • content.readline():读取响应内容的原始字节,返回一个 bytes 对象。await response.content.readline()
  1. ClientResponse.read():读取响应的内容,以字节形式返回。
  2. ClientResponse.text():以文本形式返回响应的内容。
  3. ClientResponse.json():将响应的内容解析为 JSON 格式,并返回解析后的对象。
  4. ClientResponse.raise_for_status():如果响应的状态码表明请求失败(4xx 或 5xx),则引发一个异常。
  5. ClientResponse.raw_headers:响应的原始头部信息,以元组列表的形式返回。
  6. ClientResponse.url:响应的 URL。
  7. ClientResponse.release():释放响应对象的资源。
  8. ClientResponse.close():关闭响应对象。
  9. ClientResponse.get_encoding():获取响应的编码方式。
  10. ClientResponse.get_content_type():获取响应的内容类型。
  11. ClientResponse.get_links():获取响应中的链接信息。
  12. ClientResponse.get_history():获取重定向历史记录。
  13. ClientResponse.get_request_info():获取请求信息对象。
  14. ClientResponse.get_writer():获取写入器对象。
  15. ClientResponse.get_task():获取任务对象。
  16. ClientResponse.get_loop():获取事件循环对象。
  17. ClientResponse.get_traces():获取跟踪信息。
  18. ClientResponse.get_timer():获取计时器对象。
  19. ClientResponse.get_continue():检查是否收到 “100 Continue” 响应。
  20. ClientResponse.ok:布尔值,表示响应是否成功(状态码为 2xx)。
  21. ClientResponse.reason_phrase:响应的状态原因短语。
  22. ClientResponse.content_type:响应的内容类型。
  23. ClientResponse.content_length:响应的内容长度。
  24. ClientResponse.charset:响应的字符编码。
  25. ClientResponse.history:重定向历史记录。
  26. ClientResponse.request_info:请求信息对象。
  27. ClientResponse.writer:写入器对象。
  28. ClientResponse.task:任务对象。
  29. ClientResponse.loop:事件循环对象。
  30. ClientResponse.traces:跟踪信息。
  31. ClientResponse.timer:计时器对象。
  32. ClientResponse.continue100:是否收到 “100 Continue” 响应。
  33. ClientResponse.connection:响应的连接对象。
  34. ClientResponse.is_eof():检查响应是否已经结束。
五、ClientRequest 类的方法和属性
  1. aiohttp.ClientRequest():异步 HTTP 客户端的请求对象,用于构建和发送 HTTP 请求,返回ClientRequest对象
  • method:请求的方法,如 ‘GET’、‘POST’ 等。
  • url:请求的 URL。
  • params:URL 查询参数,可以是字典。
  • headers:请求的头部信息,可以是字典。
  • skip_auto_headers:不自动添加的头部信息,可以是列表。
  • data:请求的数据,可以是字符串或字节。
  • json:JSON 数据,会自动设置请求的 ‘Content-Type’ 为 ‘application/json’。
  • cookies:请求的 Cookie,可以是字典或 http.cookies.SimpleCookie 对象。
  • auth:身份验证信息,可以是 aiohttp.BasicAuth对象。
  • allow_redirects:是否允许重定向。
  • max_redirects:最大重定向次数。
  • encoding:请求的编码方式。
  • compress:是否启用压缩。
  • chunked:是否启用分块传输编码。
  • expect100:是否启用 “Expect: 100-continue”。
  • read_until_eof:是否读取直到响应结束。
  • proxy:代理 URL。
  • proxy_auth:代理身份验证信息,可以是 aiohttp.BasicAuth对象。
  • timeout:请求超时时间(秒)。
  • ssl:SSL/TLS 配置。
  • proxy_headers:代理请求的头部信息,可以是字典。
  • trace_request_ctx:请求上下文。
六、web之aiohttp.web.Application()
  1. aiohttp.web.Application(**kwargs) 创建 Web 应用程序,app = aiohttp.web.Application()
  • router:指定应用程序的路由表对象。默认为 None,表示将创建一个新的路由表对象。
  • middlewares:指定应用程序的中间件列表。中间件是在请求处理过程中执行的额外操作,例如身份验证、日志记录等。默认为一个空列表。
  • default_host:指定应用程序的默认主机名。默认为 None,表示没有默认主机。
  • default_port:指定应用程序的默认端口号。默认为 None,表示没有默认端口。
  • client_max_size:指定应用程序接受的请求体的最大大小(以字节为单位)。默认为 1024^2(1MB)。
  • loop:指定应用程序使用的事件循环。默认为 None,表示使用 * asyncio.get_event_loop() 获取默认事件循环。
  • handler_args:指定传递给请求处理函数的额外参数。默认为一个空字典。
  • debug:指定是否启用调试模式。调试模式下,应用程序会提供更详细的错误信息。默认为 False
  1. app.router:获取应用程序的路由表对象,用于定义和管理应用程序的路由。
  2. app.add_routes(routes):向应用程序添加一组路由。routes 参数可以是 aiohttp.web.RouteTableDef 对象或包含路由的可迭代对象。
  3. app.on_startup(callback, *args, **kwargs):注册应用程序启动时要调用的回调函数。callback 是要注册的回调函数,*args 和 **kwargs 是传递给回调函数的额外参数。
  4. app.on_shutdown(callback, *args, **kwargs):注册应用程序关闭时要调用的回调函数。callback 是要注册的回调函数,args 和 **kwargs 是传递给回调函数的额外参数。
  5. app.cleanup():清理应用程序的资源。这将依次调用已注册的关闭回调函数。
  6. app.make_handler(,access_log_class=AccessLogger, access_log_format=DEFAULT_ACCESS_LOG_FORMAT, access_log=DEFAULT_ACCESS_LOG_FORMAT, **kwargs):创建一个 aiohttp.web.RequestHandler 对象,用于处理 HTTP 请求。
  • access_log_class:指定访问日志的记录器类。默认为 AccessLogger,用于记录请求的访问日志。
  • access_log_format:指定访问日志的格式。默认为 DEFAULT_ACCESS_LOG_FORMAT,包含常见的访问日志字段。
  • access_log:指定访问日志的格式。默认为 DEFAULT_ACCESS_LOG_FORMAT,与 access_log_format 参数相同。
  1. app.middlewares:应用程序的中间件列表。中间件可以在请求处理过程中执行额外的操作,例如身份验证、日志记录等。
  • app.middlewares.append(middleware_handler):添加中间件
  • app.middlewares.remove(middleware_handler):移除中间件
  • app.middlewares.clear():清空中间件列表
  1. app.on_startup:应用程序启动时要调用的回调函数列表。
  2. app.on_shutdown:应用程序关闭时要调用的回调函数列表。
  3. app.route:获取应用程序的路由器对象
  • app.route.add_route(method, path, handler, *, name=None):添加一个路由
  • method:HTTP 方法,指定该路由匹配的请求方法。可以是字符串或字符串列表。
    • path:URL 路径模式,指定该路由匹配的 URL 路径。
    • handler:处理该路由的处理程序。可以是一个函数、一个类的方法,或者是一个 web.View 的子类。
    • name:可选,路由的名称,用于在应用程序中引用该路由。
  • app.route.add_get(path, handler, *, name=None):添加一个 GET 请求方法的路由。
  • app.route.add_post(path, handler, *, name=None):添加一个 POST 请求方法的路由。
  • app.route.add_put(path, handler, *, name=None):添加一个 PUT 请求方法的路由。
  • app.route.add_delete(path, handler, *, name=None):添加一个 DELETE 请求方法的路由。
  • app.route.add_static(prefix, path, *, name=None):添加一个静态文件路由
    • prefix:URL 前缀,用于匹配静态文件请求的 URL。
    • path:静态文件目录的路径。
    • name:可选,路由的名称,用于在应用程序中引用该路由
  • app.route.add_view(path, handler, *, name=None):添加一个基于类的视图路由。
    • path:URL 路径模式,指定该路由匹配的 URL 路径。
    • handler:处理该路由的视图类。
    • name:可选,路由的名称,用于在应用程序中引用该路由。
七、web之 web.RouteTableDef()路由表
  1. web.RouteTableDef() 创建路由表,routes = web.RouteTableDef()
  2. @routes.route(method,path, **kwargs)
  3. routes.get(path, **kwargs)
  4. @routes.post(path, **kwargs)
  5. @routes.put(path, **kwargs)
  6. @routes.patch(path, **kwargs)
  7. @routes.delete(path, **kwargs)
  8. @routes.options(path, **kwargs)
  9. @routes.head(path, **kwargs)
  10. @routes.trace(path, **kwargs)
  11. routes.static() 装饰器添加静态文件路由
八、web之 web.Response 创建并返回给客户端的 HTTP 响应对象
  1. web.Response() 创建并返回给客户端的 HTTP 响应对象,reponse = web.Response()
  • body:响应体的内容。可以是字符串、字节串或文件对象。默认为 None。
  • status:响应的状态码。默认为 200。
  • reason:响应状态码的原因短语。默认为 None,将根据状态码自动确定原因短语。
  • text:响应体的内容,以字符串形式。与 body 参数互斥,只能选择其中一个。
  • content_type:响应的内容类型。默认为 None,表示不指定内容类型。
  • charset:响应的字符集。默认为 None。
  • headers:响应的自定义头部。可以是一个字典或 aiohttp 的 CIMultiDict 对象。
  • content_encoding:响应体的内容编码。默认为 None。
  • zlib_executor_size:指定用于压缩响应体的线程池的大小。它是一个整数值,表示线程池中的线程数量。默认为 None,表示不使用线程池压缩响应体。
  • zlib_executor:指定用于压缩响应体的线程池。它是一个 * concurrent.futures.Executor 对象,用于执行压缩操作。默认为 None。
  1. body:响应体的内容。可以是字符串、字节串或文件对象。
  2. status:响应的状态码。
  3. reason:响应状态码的原因短语。
  4. text:响应体的内容,以字符串形式。
  5. content_type:响应的内容类型。
  6. charset:响应的字符集。
  7. headers:响应的头部,一个 aiohttp 的 CIMultiDict 对象。
  8. content_encoding:响应体的内容编码。
  9. cookies:响应的 cookie,一个 aiohttp 的 CIMultiDict 对象。
  10. chunked:表示响应是否使用分块传输编码。
九、web之 web.Request 获取请求相关的信息和属性,路由中参数都会有一个request
  1. request.method:请求的 HTTP 方法,如 GET、POST 等。
  2. request.path:请求的路径部分。
  3. request.query_string:请求的查询字符串部分。
  4. request.headers:请求的头部,一个 aiohttp 的 CIMultiDict 对象。
  5. request.cookies:请求的 cookie,一个 aiohttp 的 CIMultiDict 对象。
  6. request.content_type:请求的内容类型。
  7. request.charset:请求的字符集。
  8. request.query:解析后的查询参数,一个 MultiDictProxy 对象。
  9. request.match_info:URL 匹配的信息,一个 MatchInfo 对象。
  10. request.version:HTTP 版本。
  11. request.host:请求的主机名。
  12. request.scheme:请求的协议方案,如 “http” 或 “https”。
  13. request.remote:请求的远程地址信息。
  14. request.app:请求所属的 Application 对象。
十、web之 web.WebSocketResponse,处理 WebSocket 连接,提供了与客户端进行双向通信的接口
  1. web.WebSocketResponse() 创建socket对象,ws = web.WebSocketResponse()
  2. ws.prepare(request): 准备 WebSocket 连接。在处理 WebSocket 连接之前,需要先调用这个方法进行准备。
  3. ws.send_str(data): 发送文本消息给客户端。
  4. ws.send_bytes(data): 发送二进制消息给客户端。
  5. ws.receive(): 接收客户端发送的消息。返回一个 WSMessage 对象,其中包含消息的类型和数据。
  6. ws.close(): 关闭 WebSocket 连接。
  7. ws.closed: WebSocket 连接是否已关闭。
  8. ws.exception(): 如果 WebSocket 连接因异常而关闭,返回关闭时的异常对象。
十一、web之 aiohttp.CookieJar 管理 Cookie
  1. aiohttp.CookieJar(unsafe=True):创建cookiejar,cookie_jar = aiohttp.CookieJar(unsafe=True)
  2. cookie_jar.update_cookies(cookies, response_url=None):从字典或 http.cookies.SimpleCookie 对象中更新 Cookie。可以通过指定 response_url 参数来限制 Cookie 的作用域。
  3. cookie_jar.filter_cookies(url):根据给定的 URL 过滤并返回适用于该 URL 的 Cookie。返回一个 http.cookies.SimpleCookie 对象。
  4. cookie_jar.clear():清除所有的 Cookie。
  5. cookie_jar.load(filename):从文件中加载 Cookie。
  6. cookie_jar.save(filename):将 Cookie 保存到文件中。
十二、案例
  1. 爬取英雄联盟官方皮肤
    '''网址:https://101.qq.com/#/hero'''import aiohttp
    import asyncio
    import json
    import os
    
    class LOL:
    
        request_url = {
            'hero_info': 'https://game.gtimg.cn/images/lol/act/img/js/heroList/hero_list.js?ts=2822604',
            'skin': 'https://game.gtimg.cn/images/lol/act/img/skin/big{}00{}.jpg'    }
    
        headers = {
            'User-Agent':'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/116.0.0.0 Safari/537.36'    }
    
        def __init__(self):
            pass    async def get_hero(self):
            async with aiohttp.ClientSession() as request:
                response = await request.get(self.request_url['hero_info'],headers=self.headers)
                hero_json_str = await response.text()
                task_list = []
                for item in json.loads(hero_json_str)['hero']:
                    task = asyncio.create_task(self.get_skin(request,item))
                    task_list.append(task)
                await asyncio.wait(task_list)
    
        async def get_skin(self,request,hero_info):
            if not os.path.exists(hero_info["name"]):
                os.mkdir(hero_info["name"])
            for nu in range(30):
                response = await request.get(self.request_url['skin'].format(hero_info['heroId'], nu))
                if response.status == 200:
                    skin_info = await response.read()
                    with open(f'{hero_info["name"]}{os.sep}{nu}.jpg', 'wb') as file:
                        file.write(skin_info)
                        print('英雄皮肤已下载')
                else:
                    breakif __name__ == '__main__':
        lol = LOL()
        asyncio.run(lol.get_hero())
    
  2. 创建web应用程序之web.RouteTableDef()
    from aiohttp import web
    import os
    
    routes = web.RouteTableDef()
    
    @routes.get('/') #访问http://localhost:8009/
    def index(request):
        response = web.Response(text="Hello, world!")
        return response
    
    @routes.route('GET','/test') #访问http://localhost:8009/test
    def test(request):
        response = web.Response(text="测试")
        return response
    
    routes.static('/skin',path=os.path.join(os.getcwd(),'img')) #访问http://localhost:8009/skin/万花通灵/0.jpg
    web.static('/skin',path=os.path.join(os.getcwd(),'img')) #访问http://localhost:8009/skin/万花通灵/0.jpg
    
    app = web.Application()
    app.add_routes(routes)
    web.run_app(app,host='localhost',port=8009)
    
  3. 创建web应用程序之app.router
    from aiohttp import web
    import os
    
    
    def index(request):
        response = web.Response(text="Hello, world!")
        return response
    
    def test(request):
        response = web.Response(text="测试")
        return response
    
    app = web.Application()
    
    app.router.add_get('/',index)#访问http://localhost:8009/
    app.router.add_route('GET','/test',test) #访问http://localhost:8009/test
    app.router.add_static('/skin',path=os.path.join(os.getcwd(),'img')) #访问http://localhost:8009/skin/万花通灵/0.jpg
    web.static('/skin',path=os.path.join(os.getcwd(),'img')) #访问http://localhost:8009/skin/万花通灵/0.jpg
    
    
    web.run_app(app,host='localhost',port=8009)
    
    
  4. 创建web应用程序之中间件
    from aiohttp import web
    
    # 定义全局中间件函数
    @web.middleware
    async def middleware_handler(request, handler):
        # 在请求处理之前执行的代码
        print('全局请求处理之前执行的代码')
    
        # 调用下一个中间件或请求处理函数
        response = await handler(request)
    
        # 在请求处理之后执行的代码
        print('全局请求处理之后执行的代码')
    
        return response
    
    # 定义test路由中间件函数
    @web.middleware
    async def test_handler(request, handler):
        # 在请求处理之前执行的代码
        print('test路由在请求处理之前执行的代码')
    
        # 调用下一个中间件或请求处理函数
        response = await handler(request)
    
        # 在请求处理之后执行的代码
        print('test路由在请求处理之后执行的代码')
    
        return response
    
    
    
    # 创建应用程序
    app = web.Application()
    routes = web.RouteTableDef()
    
    @routes.get('/')
    async def index(request):
        return web.Response(text='首页')
    
    
    # 定义处理函数
    async def test_response(request):
        return web.Response(text='测试')
    
    @routes.get('/test')
    async def test(request):
        response = await test_handler(request,test_response)
        return  response
    
    
    # 将中间件添加到应用程序
    app.middlewares.append(middleware_handler)
    app.add_routes(routes)
    
    web.run_app(app,host='localhost',port=8009)
    
  5. websocket案例
    from aiohttp import web
    
    # WebSocket 处理函数
    async def websocket_handler(request):
        ws = web.WebSocketResponse()  # 创建 WebSocketResponse 对象
        await ws.prepare(request)  # 准备 WebSocket 连接
    
        async for msg in ws:  # 处理 WebSocket 的消息
            if msg.type == web.WSMsgType.TEXT:
                if msg.data == 'close':
                    await ws.close()
                else:
                    await ws.send_str('You said: ' + msg.data)
            elif msg.type == web.WSMsgType.ERROR:
                print('WebSocket connection closed with exception %s' % ws.exception())
    
        return ws
    
    # 创建应用程序
    app = web.Application()
    
    # 将 WebSocket 处理函数添加到路由中
    app.router.add_get('/ws', websocket_handler)
    
    # 运行应用程序
    web.run_app(app)
    
    
;