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

ASP.NET Web API中OwinMiddleware无法修改SignalR协商/启动请求缓存头

SignalR negotiate/start请求缓存头被覆盖问题排查与解决

我编写了如下Owin中间件代码,旨在将HTTP缓存控制头统一设置为no-cache, no-store, must-revalidate:

public class CacheMiddleware : OwinMiddleware
{
    public CacheMiddleware(OwinMiddleware next) : base(next)
    {
    }

    public override async Task Invoke(IOwinContext context)
    {
        context.Response.Headers["Cache-Control"] = "no-cache, no-store, must-revalidate";
        context.Response.Headers["Pragma"] = "no-cache";
        context.Response.Headers["Expires"] = "0";
        context.Response.Headers.Set("Strict-Transport-Security", "max-age=31536000; includeSubDomains;");
        await Next.Invoke(context);
    }
}

但实际运行后发现:Chrome开发者工具中,SignalR的signalr/negotiate和signalr/start请求响应头仅返回Cache-Control:no-cache,而signalr/connect及其他普通请求则能正常应用目标缓存头。

两类请求的响应头对比:

  • signalr/negotiate & signalr/start响应头:

    Cache-Control:no-cache
    Connection:keep-alive
    Host:10.0.211.202
    Pragma:no-cache
    Expires:-1
    Strict-Transport-Security:max-age=31536000; includeSubDomains;
    
  • signalr/connect响应头:

    Cache-Control:no-cache, no-store, must-revalidate
    Connection:Upgrade
    Strict-Transport-Security:max-age=31536000; includeSubDomains;
    Upgrade:websocket
    

原因分析

SignalR的negotiate和start请求属于握手关键请求,其内部处理逻辑(如NegotiateHandler)会在你的中间件执行之后运行,并且会主动覆盖Cache-Control、Expires等缓存头,强制设置为no-cache和Expires:-1——这是SignalR的内置机制,目的是避免浏览器缓存握手结果,防止后续连接出现异常。

而signalr/connect是WebSocket或长连接的建立请求,SignalR内部没有对这类请求的缓存头做强制覆盖,所以中间件设置的头能正常保留。

解决方案

要让negotiate和start请求也应用自定义缓存头,核心思路是在SignalR内部处理完成后,再重写响应头,具体有两种实现方式:

1. 全局后置修改缓存头

调整中间件逻辑,先让后续中间件(包括SignalR)处理请求,再设置/覆盖缓存头:

public class CacheMiddleware : OwinMiddleware
{
    public CacheMiddleware(OwinMiddleware next) : base(next)
    {
    }

    public override async Task Invoke(IOwinContext context)
    {
        // 先执行后续中间件,包括SignalR的处理逻辑
        await Next.Invoke(context);
        
        // 后置修改缓存头,覆盖SignalR的默认设置
        context.Response.Headers["Cache-Control"] = "no-cache, no-store, must-revalidate";
        context.Response.Headers["Pragma"] = "no-cache";
        context.Response.Headers["Expires"] = "0";
        context.Response.Headers.Set("Strict-Transport-Security", "max-age=31536000; includeSubDomains;");
    }
}

2. 针对SignalR路径单独处理

如果不想全局修改所有请求的处理顺序,可以在中间件中判断请求路径,仅对negotiate/start请求做后置覆盖:

public class CacheMiddleware : OwinMiddleware
{
    public CacheMiddleware(OwinMiddleware next) : base(next)
    {
    }

    public override async Task Invoke(IOwinContext context)
    {
        var requestPath = context.Request.Path.Value;
        // 先执行后续中间件
        await Next.Invoke(context);
        
        // 对SignalR握手请求重写缓存头
        if (requestPath.Contains("/signalr/negotiate") || requestPath.Contains("/signalr/start"))
        {
            context.Response.Headers["Cache-Control"] = "no-cache, no-store, must-revalidate";
            context.Response.Headers["Pragma"] = "no-cache";
            context.Response.Headers["Expires"] = "0";
        }
        // 其他请求保持自定义缓存设置
        else
        {
            context.Response.Headers["Cache-Control"] = "no-cache, no-store, must-revalidate";
            context.Response.Headers["Pragma"] = "no-cache";
            context.Response.Headers["Expires"] = "0";
        }
        // HSTS头全局生效
        context.Response.Headers.Set("Strict-Transport-Security", "max-age=31536000; includeSubDomains;");
    }
}

验证效果

修改代码后重新部署服务,在Chrome开发者工具中查看signalr/negotiate和signalr/start的响应头,即可看到自定义的Cache-Control:no-cache, no-store, must-revalidate已生效。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.02 06:04:56