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

使用WatsonWebserver能否在同一请求处理器中获取客户端新请求?

问题解答

首先明确:无法在同一个DefaultRoute任务实例中等待客户端的新请求,原因在于HTTP协议本身是基于「请求-响应」的无状态模型:

  • 当你向客户端返回400响应后,当前的HTTP连接要么关闭,要么被复用给其他请求,原有的HttpContextBase实例已经完成使命,无法再接收后续请求。
  • 每个客户端请求都会触发一个全新的DefaultRoute任务实例,两者之间没有直接关联。

正确的实现方案:用请求唯一ID追踪状态

你需要生成一个唯一标识来关联两次请求(第一次参数缺失的请求,和第二次补全参数的请求),具体步骤如下:

  1. 当第三方返回400时,生成唯一ID并缓存状态
    • 生成唯一ID(比如Guid.NewGuid().ToString())
    • 将当前需要保留的状态(比如第三方要求的参数清单、第一次请求的基础信息)存入服务器端缓存(如IMemoryCache)
    • 向客户端返回400响应,同时带上这个唯一ID,提示客户端补全参数后携带该ID重新请求
  2. 客户端携带ID发送新请求时,从缓存取出状态继续处理
    • 解析新请求中的唯一ID
    • 从缓存中取出对应的状态信息
    • 用补全后的参数调用第三方服务,完成后续逻辑

修正后的示例代码

using WatsonWebserver;
using WatsonWebserver.Core;
using Newtonsoft.Json.Linq;
using System.Net;
using Microsoft.Extensions.Caching.Memory;

namespace GoogleAuthRequester
{
    internal class Program
    {
        // 全局HttpClient单例,避免频繁创建耗尽连接池
        private static readonly HttpClient _httpClient = new HttpClient();
        // 内存缓存用于存储请求状态
        private static readonly IMemoryCache _cache = new MemoryCache(new MemoryCacheOptions());

        static void Main(string[] args)
        {
            var settings = new WebserverSettings("127.0.0.1", 9000, false);
            var server = new Webserver(settings, DefaultRoute);
            server.Start();
            Console.ReadLine();
        }

        static async Task DefaultRoute(HttpContextBase ctx)
        {
            // 检查请求是否携带重试ID
            if (ctx.Request.Query.TryGetValue("retryId", out string retryId))
            {
                // 从缓存获取之前的状态
                if (_cache.TryGetValue(retryId, out JObject originalRequestData))
                {
                    // 合并新请求的参数到原有数据
                    var newParams = JObject.Parse(ctx.Request.DataAsString);
                    originalRequestData.Merge(newParams);

                    // 重新调用第三方服务
                    var thirdPartyResponse = await _httpClient.GetAsync($"https://3rdpartyservice/json/{originalRequestData}");
                    if (thirdPartyResponse.IsSuccessStatusCode)
                    {
                        ctx.Response.StatusCode = (int)thirdPartyResponse.StatusCode;
                        await ctx.Response.Send(await thirdPartyResponse.Content.ReadAsStringAsync());
                        _cache.Remove(retryId); // 处理完成后清除缓存
                        return;
                    }
                    // 如果还是错误,可继续返回400并更新缓存状态
                }
            }

            // 处理首次请求
            JObject json = JObject.Parse(ctx.Request.DataAsString);
            var thirdPartyRequest = await _httpClient.GetAsync($"https://3rdpartyservice/json/{json}");
            
            if (thirdPartyRequest.StatusCode == HttpStatusCode.BadRequest)
            {
                // 生成唯一重试ID
                string newRetryId = Guid.NewGuid().ToString();
                // 缓存原始请求数据,设置过期时间(比如5分钟)
                _cache.Set(newRetryId, json, TimeSpan.FromMinutes(5));
                
                // 返回400并告知客户端重试ID和需要补全的参数
                ctx.Response.StatusCode = 400;
                var errorResponse = new JObject
                {
                    ["message"] = "参数缺失,请补全后重试",
                    ["retryId"] = newRetryId,
                    ["requiredParams"] = new JArray("param1", "param2") // 从第三方响应中提取需要的参数
                };
                await ctx.Response.Send(errorResponse.ToString());
                return;
            }

            // 处理第三方成功响应
            ctx.Response.StatusCode = (int)thirdPartyRequest.StatusCode;
            await ctx.Response.Send(await thirdPartyRequest.Content.ReadAsStringAsync());
        }
    }
}

额外注意点

  • HttpClient要复用:不要在请求处理器里每次创建新的HttpClient,全局单例或用IHttpClientFactory更高效,避免连接池耗尽。
  • 缓存要设置过期时间:防止无用的状态一直占用内存,根据业务场景设置合理的过期时长。
  • 异步代码规范:避免在异步方法中使用.GetAwaiter().GetResult(),直接用await保证异步流程正确执行。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.26 22:56:03