博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
Response.Redirect引起的“无法在发送HTTP标头之后进行重定向”
阅读量:6678 次
发布时间:2019-06-25

本文共 5497 字,大约阅读时间需要 18 分钟。

博客后台之后,在日志中发现大量的“无法在发送HTTP标头之后进行重定向”(Cannot redirect after HTTP headers have been sent)的错误信息。

检查代码发现问题是由下面的代码触发的:

IHttpHandler IHttpHandlerFactory.GetHandler(HttpContext context, string requestType, string url, string pathTranslated){    context.Response.Redirect("http://i.cnblogs.com/" +         context.Request.RawUrl.Substring(context.Request.RawUrl.LastIndexOf("/") + 1));    //后续也有context.Response.Redirect代码    //...    return PageParser.GetCompiledPageInstance(newurl, path, context);}

“无法在发送HTTP标头之后进行重定向”问题来源于Response.Redirect之后,又进行了Response.Redirect。

解决方法很简单:在Response.Redirect之后立即返回。

IHttpHandler IHttpHandlerFactory.GetHandler(HttpContext context, string requestType, string url, string pathTranslated){    context.Response.Redirect("http://i.cnblogs.com/" +         context.Request.RawUrl.Substring(context.Request.RawUrl.LastIndexOf("/") + 1));    return null;    //...    }

为什么之前没有加return null呢?因为以前一直以为Response.Redirect会结束当前请求,不会执行Response.Redirect之后的代码。

现在残酷的现实说明了不完全是这样的,那问题背后的真相是什么?让我们来一探究竟。

由于微软公开了.NET Framework的源代码,现在无需再看Reflactor出来的代码,可以直接下载源代码用Visual Studio进行查看。

.NET Framework源代码下载链接: (相关新闻:)

用Visual Studio打开DotNetReferenceSource\Source\ndp.sln,搜索HttpResponse.cs,找到Response.Redirect的实现代码:

public void Redirect(String url){    Redirect(url, true, false);}

实际调用的是internal void Redirect(String url, bool endResponse, bool permanent) ,传给endResponse的值的确是true啊,为什么后面的代码还会执行?

进一步查看internal void Redirect()的实现代码(省略了无关代码):

internal void Redirect(String url, bool endResponse, bool permanent) {    //...    Page page = _context.Handler as Page;    if ((page != null) && page.IsCallback) {        //抛异常    }    // ... url处理    Clear(); //Clears all headers and content output from the buffer stream.    //...    this.StatusCode = permanent ? 301 : 302; //进行重定向操作    //...    _isRequestBeingRedirected = true;     var redirectingHandler = Redirecting;    if (redirectingHandler != null) {        redirectingHandler(this, EventArgs.Empty);    }    if (endResponse)        End(); //结束当前请求}

从上面的代码可以看出,我们要找的真相在End()方法中,继续看HttpResponse.End()的实现代码:

public void End() {    if (_context.IsInCancellablePeriod) {        AbortCurrentThread();    }    else {        // when cannot abort execution, flush and supress further output        _endRequiresObservation = true;        if (!_flushing) { // ignore Reponse.End while flushing (in OnPreSendHeaders)            Flush();            _ended = true;            if (_context.ApplicationInstance != null) {                _context.ApplicationInstance.CompleteRequest();            }        }    }}

注意啦!真相浮现了!

以前一直以为的Response.Redirect会结束当前请求,就是上面的AbortCurrentThread()情况,如果将Response.Redirect放在try...catch中就会捕捉到ThreadAbortException异常。

通常情况下,我们在WebForms的Page或MVC的Controller中进行Redirect,_context.IsInCancellablePeriod的值为true,执行的是AbortCurrentThread(),所以不会遇到这个问题。

而我们现在的场景恰恰是因为_context.IsInCancellablePeriod的值为false,为什么会是false呢?

进一步看一下_context.IsInCancellablePeriod的实现:

private int _timeoutState; // 0=non-cancelable, 1=cancelable, -1=canceledinternal bool IsInCancellablePeriod {    get { return (Volatile.Read(ref _timeoutState) == 1); }}

根据上面的代码,触发这个问题的条件是_timeoutState的值要么是0,要么是-1,根据我们的实际情况,应该是0=non-cancelable。

再来看看我们的实际应用场景,我们是在实现IHttpHandlerFactory接口的GetHandler方法中进行Response.Redirect操作的,也就是说在这个阶段_timeoutState的值还没被设置(默认值就是0)。为了验证这个想法,继续看一下_timeoutState在哪个阶段设值的。

Shift+F12找到所有引用_timeoutState的地方,在HttpConext中发现了设置_timeoutState的方法BeginCancellablePeriod,实现代码如下:

internal void BeginCancellablePeriod() {    // It could be caused by an exception in OnThreadStart    if (Volatile.Read(ref _timeoutStartTimeUtcTicks) == -1) {        SetStartTime();    }    Volatile.Write(ref _timeoutState, 1);}

然后再Shift+F12找到了在HttpApplication.ExecuteStep()中调用了BeginCancellablePeriod():

internal Exception ExecuteStep(IExecutionStep step, ref bool completedSynchronously) {    //..    if (step.IsCancellable) {        _context.BeginCancellablePeriod(); // request can be cancelled from this point    }    //..}

从上面的代码可以看出,当step.IsCancellable为true时,会调用BeginCancellablePeriod(),就不会出现这个问题。

而我们用到的IHttpHandlerFactory.GetHandler()所在的IExecutionStep的实现可能将IsCancellable设置为了false。

那IHttpHandlerFactory.GetHandler()是在哪个IExecutionStep的实现中调用的呢?

在园子里的一篇写得非常棒的博文()中找到了答案——MapHandlerExecutionStep:

当执行到MapHandlerExecutionStep时会执行如下代码获取最终执行请求:context.Handler = this._application.MapHttpHandler()。HttpApplication对象的MapHttpHandler方法将根据配置文件结合请求类型和URL以调用相应的IHttpHandlerFactory来获取HttpHandler对象。

我们再回到.NET Framework的源代码中看一看MapHandlerExecutionStep的实现:

// execution step -- map HTTP handler (used to be a separate module)internal class MapHandlerExecutionStep : IExecutionStep {    private HttpApplication _application;    internal MapHandlerExecutionStep(HttpApplication app) {        _application = app;    }    void IExecutionStep.Execute() {        //...    }    bool IExecutionStep.CompletedSynchronously {        get { return true;}    }    bool IExecutionStep.IsCancellable {        get { return false; }    }}

看到有没有?IExecutionStep.IsCancellable返回的值是false。

到此,水落石出,真相大白!

请看大屏幕——

由于MapHandlerExecutionStep(调用IHttpHandlerFactory.GetHandler()的地方)返回的IsCancellable的值是false,于是在HttpApplication.ExecuteStep()执行时没有调用_context.BeginCancellablePeriod()——也就是没有把_timeoutState设置为1,_context.IsInCancellablePeriod的值就是false。从而造成在Response.Redirect中进行Response.End()时没有执行AbortCurrentThread()(通常情况下都会执行这个)。于是代码继续执行,后面又来一次Response.Redirect,最终引发了——“无法在发送HTTP标头之后进行重定向”(Cannot redirect after HTTP headers have been sent)。

转载于:https://www.cnblogs.com/dudu/p/cannot-redirect-after-http-headers-have-been-sent.html

你可能感兴趣的文章
浅析:协同软件已成为用户应用软件采购最大热点?
查看>>
阿里云全新一代企业级新品解读—通过MaxCompute Studio实践大数据时代的DevOps
查看>>
中国人工智能学会通讯——个性化推荐和资源分配在金融和经济中的应用 1.4 智能金融·产品增强...
查看>>
云计算在安防监控领域中有哪些作用
查看>>
Python赶超R语言,成为数据科学、机器学习平台中最热门的语言?
查看>>
内部云部署不只虚拟化那么简单
查看>>
数据中心网络的那些二层技术谈
查看>>
Synergy配置与使用
查看>>
微服务的4大设计原则和19个解决方案
查看>>
一个跨平台的 C++ 内存泄漏检测器
查看>>
格力给洛阳砸了150亿,我们瞄到了更深刻的「原因」
查看>>
数据库事务隔离级别
查看>>
如何架设Linux打印服务器
查看>>
嫁接金融业 智能洞察是核心竞争力
查看>>
卸载(Offloading)vs. 加载(Onloading):谁是CPU利用率之王?
查看>>
云适配陈本峰:我为什么发起“中国企业级H5产业联盟”
查看>>
在大数据冲击下的工业质量管理对策
查看>>
《中国人工智能学会通讯》——1.33 基础模型
查看>>
MSSQL 2000 错误823恢复
查看>>
一位美国教授与840个公交扒手奇遇记
查看>>