Response.Redirect引起的“无法在发送HTTP标头之后进行重定向”
博客后台切换至i.cnblogs.com之后,在日志中发现大量的“无法在发送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("/") + )); //后续也有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("/") + ));
return null;
//...
}
为什么之前没有加return null呢?因为以前一直以为Response.Redirect会结束当前请求,不会执行Response.Redirect之后的代码。
现在残酷的现实说明了不完全是这样的,那问题背后的真相是什么?让我们来一探究竟。
由于微软公开了.NET Framework的源代码,现在无需再看Reflactor出来的代码,可以直接下载源代码用Visual Studio进行查看。
.NET Framework源代码下载链接:http://referencesource.microsoft.com/download.html (相关新闻:微软开放了.NET 4.5.1的源代码)
用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 ? : ; //进行重定向操作
//...
_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=canceled
internal bool IsInCancellablePeriod {
get { return (Volatile.Read(ref _timeoutState) == ); }
}
根据上面的代码,触发这个问题的条件是_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) == -) {
SetStartTime();
}
Volatile.Write(ref _timeoutState, );
}
然后再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的实现中调用的呢?
在园子里木宛城主的一篇写得非常棒的博文(ASP.NET那点不为人知的事)中找到了答案——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)。
Response.Redirect引起的“无法在发送HTTP标头之后进行重定向”的更多相关文章
- C# 无法在发送 HTTP 标头之后进行重定向
在调试中发现错误如下: Response.Redirect引起的“无法在发送HTTP标头之后进行重定向” 跳转失败 解决方案如下: 使用js方法来跳转地址 const string url=" ...
- Page.Response.Buffer与Response.Redirect一起用报错“无法在发送 HTTP 标头之后进行重定向”
Page.Response.Buffer与Response.Redirect一起用报错“无法在发送 HTTP 标头之后进行重定向” 原因还未知..
- 解决:无法在发送 HTTP 标头之后进行重定向。 跟踪信息: 在 System.Web.HttpResponse.Redirect(String url, Boolean endResponse, Boolean permanent) 在 System.Web.Mvc.Async.AsyncControllerActionInvoker.<>……
问题:在MVC的过滤器中验证用户状态时报如下错误: 无法在发送 HTTP 标头之后进行重定向. 跟踪信息: 在 System.Web.HttpResponse.Redirect(String ...
- 处理 ASP.NET 中的异常:无法在发送 HTTP 标头之后进行重定向。
因为在 Global.asax 中的 Application_Error 事件中添加了统一的错误处理,其中会有 Redirect 重定向到错误页面. 但是有可能有些情况下已经进行过其它重定向操作,所以 ...
- 出现“无法在发送 HTTP 标头之后进行重定向”问题
如题,在Response.Redirect之后会偶尔出现“无法在发送HTTP标头之后进行重定向”问题. 是因为,已经在出现错误的代码之前进行过一次重定向了.仔细检查代码即可. 解决方法:按照逻辑移除多 ...
- Response.Redirect:无法在发送 HTTP 标头之后进行重定向
URL:http://blog.163.com/asp_neter/blog/static/17510918820107258107558/ 错误出现语句:“Response.Redirect(&qu ...
- 无法在发送 HTTP 标头之后进行重定向
public ActionResult Index2() { Response.Buffer = true; Response.Clear(); return Redirect("/Wech ...
- MVC开发中的常见错误-06-"无法在发送 HTTP 标头之后进行重定向。"
通过监视可以看到: 原来是跳转到登录页面后,登录页面中又发送了一个GeMneuItems的请求,用于加载页面图片
- response.redirect和server.Transfer的差别详解
Response.Redirect和Server.Transfer都能实现页面的跳转,但两者又有很大区别. 一 地址栏里显示的地址不同 Response.Redirect会显示跳转的网页的地址,而Se ...
随机推荐
- 网站后台调用winform MessageLoopApartment
using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.T ...
- url编码 中文在url参数中传递,在请求头,响应头中传递,是如何编码的呢?
一定要编码成url的吗?还是url自动把接受的汉字编码,请求头响应头到达之后再自动编码成汉字?这样似乎比较合理哦 先把iso8859-1 转换成 utf-8,在mvc中处理,然后响应的时候在转成iso ...
- JAVA ,Map接口 ,迭代器Iterator
1. Map 接口概述 java.util.Map 接口描述了映射结构, Map 接口允许以键集.值集合或键 - 值映射关系集的形式查看某个映射的内容. Java 自带了各种 Map 类. 这些 ...
- cassandra CQL 常用操作
1. CQL客户端链接 bin/cqlsh ip username password 2. (1)建立keyspace语句,keyspace类似于 mysql 中的数据库,一个数据库中可以有很多表: ...
- python模块之logging
在现实生活中,记录日志非常重要.银行转账时会有转账记录:飞机飞行过程中,会有黑盒子(飞行数据记录器)记录飞行过程中的一切.如果有出现什么问题,人们可以通过日志数据来搞清楚到底发生了什么.对于系统开发. ...
- Unity Sprite转Prefab
新项目使用Unity5.X,遇到了一些问题,其中就有Sprite的管理更新问题,查了一些资料,Mono推荐的是转为Prefab处理. 看了一些国外同行的处理方法,分析了一个编辑器插件脚本.学到了一些技 ...
- 国产AR SDK介绍
说到VR,大家都知道虚拟现实有多火.可是VR之后呢,还有AR.相较于VR,AR的应用意义更加的强大. 相信在不久的将来AR和VR将会融为一体,把现实世界的数据信息完全联通在我们的眼前.这其中的领头羊莫 ...
- javascript 中的 delete
那么,为什么我们能删除一个对象的属性: var x = { a: 1 }; delete x.a; // true x.a; // undefined 但却不能删除一个变量: var x = 1; d ...
- C# 多线程同步和线程通信
多线程通信 1. 当线程之间有先后的依赖关系时,属于线程之间的通信问题.也就是后一个线程要等待别的一个或多个线程全部完成,才能开始下一步的工作.可以使用: WaitHandle Class WaitH ...
- ${param.origin}
拦截器中设置未登录拦击跳转链接 response.sendRedirect(request.getContextPath() + "/loginAndRegister/loginAndReg ...