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

第三方API前2次请求正常,第3次始终返回400错误求助

第三方API对接异常问题排查求助

核心现象

  • 我方基于.NET 6构建的API,调用第三方Apache端点获取金融数据:单次请求第三方正常;单次我方API请求内,最多成功调用对方2次,第3次必失败
  • 本地IIS开发环境、Azure部署环境均复现该问题
  • 多层级集成测试(单独调用对方服务、编排多调用逻辑、业务流程外部端点)全部正常,仅通过我方API调用时出错
  • 所有XML载荷经对方内部工具/Postman验证有效,调整请求顺序后仍是第3次失败;使用对方批量请求接口时,2次请求中的第2次也返回相同400错误

问题演进

  • 初始使用远程DTD验证XML:第一次获取DTD+请求数据正常,第二次获取DTD返回400错误
  • 将DTD本地化为嵌入式资源后:前2次数据请求成功,后续请求全失败(业务流程需要4次请求)

已尝试的无效方案

  • 设置最大连接数超过2(含HttpClient处理器配置、web.config配置)
  • 使用新实例化后立即释放的HttpClient(处理器最大连接数设为100)
  • 修改DI作用域/生命周期
  • 跳过应用层直接从控制器调用对方服务

关键异常线索

  • Azure App Service HTTP追踪显示:即使URL指定HTTPS/443端口,底层HttpClient仍强制使用80端口,Flurl日志无重定向记录
  • 对方服务器始终返回Apache的HTML格式400错误

新发现

  • 流程作为Azure Durable Function直接运行、或通过NUnit集成测试运行时,对接对方服务完全正常
  • 通过Function端点调用时,仅首次请求对方服务成功,后续请求均失败

核心调用方法

public static async Task<byte[]> PostToABCServiceAsync(this IRestHelper restHelper, RestRequest request, string xml, CancellationToken cancellationToken = default)
{
    var content = new CapturedXmlContent(xml, "text/xml");

    return await ((ABCRestHelper)restHelper).ExecuteAsync(request,
        (flrl) => flrl
            .WithHeader("Content-Type", "text/xml")
            .PostAsync(content, cancellationToken)
            .ReceiveBytes());
}

REST库入口方法

public async Task<HttpResponseMessage> PostAsync(RestRequest request, CancellationToken cancellationToken = default)
{
    var content = request.Body as HttpContent;

    if (content == null && request.Body != null)
    {
        throw new ArgumentException("The request Body must inherit from System.Net.Http.HttpContent.");
    }

    var response = await ExecuteAsync(request, 
        (flrl) => flrl.PostAsync(content, cancellationToken));

    return response.ResponseMessage;
}

REST库执行方法

public async Task<TResponse> ExecuteAsync<TResponse>(RestRequest request,
        Func<IFlurlRequest, Task<TResponse>> flurlRequest)
{
    //build the URL first so we can render it how we want as far as encoding goes
    var requestUrl = request.BaseUri.AbsoluteUri
        .AppendPathSegments(request.Segments);

    if (request.QueryParameters?.Any() ?? false)
    {
        requestUrl = requestUrl.SetQueryParams(request.QueryParameters);
    }

    if (request.Port.HasValue)
    {
        requestUrl.Port = request.Port;
    }
    
    using (var client = new FlurlClient(requestUrl.ToString(request.EncodeSpaceAsPlus)))
    {
        if (request.Settings != null)
        {
            client.Configure(request.Settings);
        }

        if (request.BeforeCallAsync != null)
        {
            client.Settings.BeforeCallAsync = request.BeforeCallAsync;
        }

        if (request.AfterCallAsync != null)
        {
            client.Settings.AfterCallAsync = request.AfterCallAsync;
        }

        if (request.OnRedirectAsync != null)
        {
            client.Settings.OnRedirectAsync = request.OnRedirectAsync;
        }

        if (request.OnErrorAsync != null)
        {
            client.Settings.OnErrorAsync = request.OnErrorAsync;
        }

        client.AllowHttpStatus(string.Join(",", request.AllowHttpStatuses));

        client.HttpClient.DefaultRequestHeaders.Clear();
        client.Headers.Clear();

        if (request.Headers?.Any() ?? false)
        {
            client.WithHeaders(request.Headers);
        }

        if (request.Timeout > 0)
        {
            client.WithTimeout(request.Timeout);
        }

        try
        {
            if (_throttle != null)
            {
                return await _throttle.Enqueue(() => flurlRequest(client.Request()));
            }
            else
            {
                return await flurlRequest(client.Request());
            }
        }
        catch (FlurlHttpTimeoutException e)
        {
            throw new CustomRestTimeoutException(e.Call?.Request?.Url?.ToString(), e);
        }
        catch (FlurlParsingException e)
        {
            string responseContent = null;

            if (e.Call?.Response != null)
            {
                try
                {
                    responseContent = await e.Call?.Response?.GetStringAsync();
                }
                catch (Exception)
                {
                    //eat all errors for pulling the response content
                }
            }

            throw new CustomRestParsingException(e.Call?.Request?.Url?.ToString(), responseContent, e);
        }
        catch (FlurlHttpException e)
        {
            int? statusCode = null;
            string responseErrorMessage = (e.Call?.Exception?.Message) ?? e.Message;
            string responseContent = null;

            if (e.Call?.HttpResponseMessage != null)
            {
                //if the call fails, the response might be null
                statusCode = (int?)e.Call.HttpResponseMessage.StatusCode;

                try
                {
                    responseContent = await e.Call.HttpResponseMessage.Content.ReadAsStringAsync();
                }
                catch (Exception)
                {
                    //eat all errors for pulling the response content
                }
            }

            throw new CustomRestException(statusCode.HasValue ? (int)statusCode.Value : 0,
                e.Call?.Request?.Url?.ToString(), responseErrorMessage, responseContent, e);
        }
    }
}

请求构建示例

CapturedXmlContent content = null;

try
{
    content = new CapturedXmlContent(_serializer.Serialize(query, false), "text/xml");
}
catch (Exception e)
{
    //log writing omitted

    throw;
}

var request = _requestFactory.GetRequest()
    .WithHeader("Content-Type", "text/xml")
    .WithBody(content)
    .OnBeforeCallAsync(LogBeforeCallAsync)
    .OnAfterCallAsync(LogAfterCallAsync)
    .OnRedirectAsync(LogRedirectAsync)
    .OnErrorAsync(LogErrorAsync)
    .Build();

try
{
    var start = DateTime.UtcNow;

    using var response = await _restHelper.PostAsync(request, cancellationToken);

    var end = DateTime.UtcNow;

    return await ProcessZipResponseAsync<DataRequestDto, DataResponseDto>(query, response, attempt, start, end);
}
catch (Exception e)
{
    //log writing omitted

    throw;
}

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.13 22:25:08