ASP.NET - Server cannot append header after HTTP headers have been sent. (HTTP 헤더를 보낸 후에는 서버에서 헤더를 추가할 수 없습니다.)
ASP.NET 응용 프로그램 개발 중 다음과 같은 예외를 만날 수 있습니다.
HTTP 헤더를 보낸 후에는 서버에서 헤더를 추가할 수 없습니다.
[HttpException (0x80004005): Server cannot append header after HTTP headers have been sent.]
System.Web.HttpResponse.AppendHeader(String name, String value) +9691456
System.Web.HttpResponse.AddHeader(String name, String value) +13
...[생략]...
System.Web.SyncEventExecutionStep.System.Web.HttpApplication.IExecutionStep.Execute() +141
System.Web.HttpApplication.ExecuteStep(IExecutionStep step, Boolean& completedSynchronously) +69
원인은 간단합니다. HTTP 프로토콜의 특성 상, HTTP Header 또는 Body까지 이미 모두 내려보냈는데 뒤늦게 HTTP 헤더를 추가하는 등의 작업을 하면 예외가 발생하는 것입니다.
간단하게 예를 들어볼까요? ^^
오류 상황을 재현하기 위해, Default.aspx.cs에 다음과 같은 코드를 추가해 줍니다.
using System;
namespace WebApplication1
{
public partial class Default : System.Web.UI.Page
{
protected void Page_Load(object sender, EventArgs e)
{
this.Response.Flush();
}
}
}
Response.Flush 메서드는 현재까지의 응답 Stream에 담긴 모든 내용을 클라이언트로 전송하는 역할을 합니다. 당연히 이 메서드로 인해 HTTP Header 영역이 내려가게 됩니다. 정말 그런지 ^^ 확인해 볼까요? 이를 위해 다음과 같이 약간의 코드를 더 추가한 후,
using System;
namespace WebApplication1
{
public partial class Default : System.Web.UI.Page
{
protected void Page_Load(object sender, EventArgs e)
{
this.Response.Flush();
Thread.Sleep(1000 * 15); // 앞 단의 Stream이 출력된 후 HTTP 응답이 완전히 완료되기까지 15초 동안 대기
}
}
}
웹 브라우저의 "개발자 도구"와 함께 방문해 "Network" 영역을 보면, Flush 코드가 수행된 시점에 HTTP Header 내용이 보이면서 (Pending) 상태로 15초 동안 머무르는 것을 볼 수 있습니다.
이제 Global.asax 파일에 EndRequest 메서드를 생성하고 다음과 같이 Header를 추가해 봅니다.
using System;
using System.Web;
namespace WebApplication1
{
public class Global : System.Web.HttpApplication
{
protected void Application_Start(object sender, EventArgs e)
{
}
protected void Application_EndRequest(object sender, EventArgs e)
{
HttpContext.Current.Response.AddHeader("test", "value");
}
}
}
당연히, 이후부터는 default.aspx를 방문할 때마다 "HttpException (0x80004005): Server cannot append header after HTTP headers have been sent" 예외가 발생합니다.
따라서, Response.AddHeader(또는, AppendHeader/ClearHeaders)와 같은 메서드를 공통 라이브러리 같은 곳에서 호출하려면 반드시 try/catch를 해주는 것이 좋습니다.
그런데, try/catch만 해주는 것은 왠지 아쉽습니다. 애초부터 header를 쓸 수 없는 상황임을 알 수 있다면 좋을 텐데요. 이를 위해 HeadersWritten 속성이 .NET Framework 4.5.2부터 제공됩니다.
HttpResponse.HeadersWritten Property
; https://docs.microsoft.com/en-us/dotnet/api/system.web.httpresponse.headerswritten
이를 이용하면 AddHeader 메서드를 좀 더 매끄럽게 호출할 수 있습니다.
protected void Application_EndRequest(object sender, EventArgs e)
{
if (HttpContext.Current.Response.HeadersWritten == false)
{
HttpContext.Current.Response.AddHeader("test", "value");
}
}
4.5.2 미만의 버전에서는 HeadersWritten 속성이 "internal"로 되어 있기 때문에 접근할 수 없습니다. 대신, 어차피 공용 속성으로 변경되었으니 향후 호환성을 걱정하지 않아도 되기 때문에 .NET Reflection을 이용해 사용하는 것도 방법일 수 있습니다.
using System;
using System.Reflection;
using System.Web;
namespace WebApplication1
{
public class Global : System.Web.HttpApplication
{
static PropertyInfo _piHeadersWritten = null;
protected void Application_Start(object sender, EventArgs e)
{
if (_piHeadersWritten == null)
{
_piHeadersWritten = typeof(HttpResponse).GetProperty("HeadersWritten",
System.Reflection.BindingFlags.Public |
System.Reflection.BindingFlags.NonPublic | System.Reflection.BindingFlags.Instance);
}
}
protected void Application_EndRequest(object sender, EventArgs e)
{
bool headersWritten = GetHeadersWritten();
if (headersWritten == false)
{
HttpContext.Current.Response.AddHeader("test", "value");
}
}
bool GetHeadersWritten()
{
if (_piHeadersWritten == null)
{
return false;
}
return (bool)_piHeadersWritten.GetValue(HttpContext.Current.Response);
}
}
}
(
첨부 파일은 이 글의 예제 코드를 포함합니다.)
[이 글에 대해서 여러분들과 의견을 공유하고 싶습니다. 틀리거나 미흡한 부분 또는 의문 사항이 있으시면 언제든 댓글 남겨주십시오.]