You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Python异步请求遇httpx.UnsupportedProtocol错误,仅调试模式可运行

问题描述

我尝试用Python的xts_api_client异步客户端xts_connect_async连接XTS Market Data API,代码如下:

from xts_api_client.xts_connect_async import XTSConnect
from dotenv import load_dotenv
import os
import asyncio

load_dotenv()

async def main():
    market_data_API_key = os.getenv("market_data_API_key")
    market_data_API_secret = os.getenv("market_data_API_secret")
    API_source = os.getenv("API_source")
    API_root = os.getenv("API_root")

    connect = XTSConnect(market_data_API_key, market_data_API_secret, API_source, API_root)
    response = await connect.marketdata_login()
    print(response)

asyncio.run(main())

在UV(Astral)或标准pip环境运行时,持续出现以下错误:

PS U:\xts> uv run .\xts.py
Traceback (most recent call last):
  File "U:\xts\.venv\Lib\site-packages\httpx\_transports\default.py", line 101, in map_httpcore_exceptions
    yield
  File "U:\xts\.venv\Lib\site-packages\httpx\_transports\default.py", line 394, in handle_async_request
    resp = await self._pool.handle_async_request(req)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "U:\xts\.venv\Lib\site-packages\httpcore\_async\connection_pool.py", line 207, in handle_async_request
    raise UnsupportedProtocol(
        "Request URL is missing an 'http://' or 'https://' protocol."
    )
httpcore.UnsupportedProtocol: Request URL is missing an 'http://' or 'https://' protocol.

The above exception was the direct cause of the following exception:

Traceback (most recent call last):
  File "U:\xts\.venv\Lib\site-packages\xts_api_client\xts_connect_async.py", line 633, in marketdata_login
    response = await self._post("market.login", params)
               ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "U:\xts\.venv\Lib\site-packages\xts_api_client\xts_connect_async.py", line 805, in _post
    return await self._request(route, "POST", params)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "U:\xts\.venv\Lib\site-packages\xts_api_client\xts_connect_async.py", line 841, in _request
    raise e
  File "U:\xts\.venv\Lib\site-packages\xts_api_client\xts_connect_async.py", line 836, in _request
    r = await self.reqsession.request(method = method, url = url,
        ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
    ...<2 lines>...
                                headers=headers)
                                ^^^^^^^^^^^^^^^^
  File "U:\xts\.venv\Lib\site-packages\httpx\_client.py", line 1540, in request
    return await self.send(request, auth=auth, follow_redirects=follow_redirects)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "U:\xts\.venv\Lib\site-packages\httpx\_client.py", line 1629, in send
    response = await self._send_handling_auth(
               ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
    ...<4 lines>...
    )
    ^
  File "U:\xts\.venv\Lib\site-packages\httpx\_client.py", line 1657, in _send_handling_auth
    response = await self._send_handling_redirects(
               ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
    ...<3 lines>...
    )
    ^
  File "U:\xts\.venv\Lib\site-packages\httpx\_client.py", line 1694, in _send_handling_redirects
    response = await self._send_single_request(request)
               ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "U:\xts\.venv\Lib\site-packages\httpx\_client.py", line 1730, in _send_single_request
    response = await transport.handle_async_request(request)
               ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "U:\xts\.venv\Lib\site-packages\httpx\_transports\default.py", line 393, in handle_async_request
    with map_httpcore_exceptions():
         ~~~~~~~~~~~~~~~~~~~~~~~^^
  File "C:\Users\parul.kakade\AppData\Local\Programs\Python\Python313\Lib\contextlib.py", line 162, in __exit__
    self.gen.throw(value)
    ~~~~~~~~~~~~~~^^^^^^^
  File "U:\xts\.venv\Lib\site-packages\httpx\_transports\default.py", line 118, in map_httpcore_exceptions
    raise mapped_exc(message) from exc
httpx.UnsupportedProtocol: Request URL is missing an 'http://' or 'https://' protocol.

During handling of the above exception, another exception occurred:

Traceback (most recent call last):
  File "U:\xts\xts.py", line 16, in <module>
    asyncio.run(main())
    ~~~~~~~~~~~^^^^^^^^
  File "C:\Users\parul.kakade\AppData\Local\Programs\Python\Python313\Lib\asyncio\runners.py", line 195, in run
    return runner.run(main)
           ~~~~~~~~~~^^^^^^
  File "C:\Users\parul.kakade\AppData\Local\Programs\Python\Python313\Lib\asyncio\runners.py", line 118, in run
    return self._loop.run_until_complete(task)
           ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~^^^^^^
  File "C:\Users\parul.kakade\AppData\Local\Programs\Python\Python313\Lib\asyncio\base_events.py", line 725, in run_until_complete
    return future.result()
           ~~~~~~~~~~~~~^^
  File "U:\xts\xts.py", line 14, in main
    response = await connect.marketdata_login()
               ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "U:\xts\.venv\Lib\site-packages\xts_api_client\xts_connect_async.py", line 640, in marketdata_login
    return response['description']
           ^^^^^^^^
UnboundLocalError: cannot access local variable 'response' where it is not associated with a value

我预期XTSConnect.marketdata_login()能成功认证并返回包含登录状态的JSON响应,且已确认所有环境变量(API密钥、密钥、来源、根地址)均正确设置,尤其是API_root以https://开头。

已尝试的操作:

  • 创建XTSConnect实例前打印所有环境变量,确认均正确填充;
  • 硬编码变量值,排除.env加载问题;
  • 在pip和uv环境分别运行脚本;
  • 尝试不同版本的xts-api-client、httpcore和httpx;
  • 用Trio替代asyncio;
  • 在库代码中添加调试打印追踪请求URL;
  • VSCode调试器运行能持续正常,但普通运行报错。

提出以下问题:

  1. 为何错误间歇性出现?调试与普通运行环境的行为差异原因是什么?
  2. XTSConnect或httpx中是否存在未注意到的竞态条件或懒加载行为?
  3. 如何确保API_root始终被正确格式化并使用?

解决方案

1. 错误间歇性出现及调试/普通环境差异的原因

调试模式下代码执行速度慢,掩盖了异步环境中的资源初始化问题。xts_api_client内部大概率存在懒加载的HTTP客户端初始化逻辑:普通运行时客户端还没完成初始化就发起请求,导致API_root未被正确拼接;而调试时的断点、慢执行给了初始化足够时间,所以能正常工作。另外,UnboundLocalError是因为marketdata_login方法里,当请求抛出异常时,response变量未被定义就尝试返回,属于库本身的异常处理漏洞。

2. 竞态条件或懒加载行为的确认

是的,xts_api_client的异步客户端存在懒加载:比如reqsession(httpx异步客户端)可能在第一次请求时才初始化,而初始化过程和请求发起之间没有同步机制,导致竞态——请求已经开始拼接URL,但API_root还没被正确赋值到客户端配置里。httpx本身的连接池异步初始化也可能存在类似问题,但核心原因还是客户端库的实现缺陷。

3. 确保API_root正确格式化并使用的方法

临时修复方案(无需修改库代码)

在创建XTSConnect实例后,手动触发客户端初始化,或者强制校验API_root格式:

async def main():
    # 加载环境变量代码
    market_data_API_key = os.getenv("market_data_API_key")
    market_data_API_secret = os.getenv("market_data_API_secret")
    API_source = os.getenv("API_source")
    API_root = os.getenv("API_root")
    
    # 强制校验API_root格式
    if not API_root.startswith(('http://', 'https://')):
        raise ValueError("API_root必须以http://或https://开头")
    
    connect = XTSConnect(market_data_API_key, market_data_API_secret, API_source, API_root)
    
    # 手动触发客户端初始化,发起一个轻量请求预热
    try:
        await connect.reqsession.get(f"{API_root}/health")
    except Exception:
        pass
    
    # 执行登录
    response = await connect.marketdata_login()
    print(response)

修复库代码的异常处理漏洞

找到xts_api_client/xts_connect_async.py的marketdata_login方法,修改异常处理逻辑,确保response变量始终有定义:

async def marketdata_login(self):
    params = {
        "userID": self.userID,
        "password": self.password,
        "source": self.source
    }
    try:
        response = await self._post("market.login", params)
        return response['description']
    except Exception as e:
        # 返回错误信息或重新抛出异常,避免UnboundLocalError
        return f"登录失败: {str(e)}"
        # 或者选择重新抛出异常:raise e

长期方案

向xts_api_client提交issue,反馈两个问题:

  • 异步客户端初始化的竞态条件导致URL拼接错误;
  • marketdata_login方法的异常处理漏洞引发UnboundLocalError。

内容的提问来源于stack exchange,提问作者Parul Kakade

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.12 20:32:02