Microsoft MVP성태의 닷넷 이야기
글쓴 사람
정성태 (techsharer at outlook.com)
홈페이지
첨부 파일

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초 동안 머무르는 것을 볼 수 있습니다.

add_header_1.png

이제 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);
        }
    }
}

(첨부 파일은 이 글의 예제 코드를 포함합니다.)




[이 글에 대해서 여러분들과 의견을 공유하고 싶습니다. 틀리거나 미흡한 부분 또는 의문 사항이 있으시면 언제든 댓글 남겨주십시오.]







[최초 등록일: ]
[최종 수정일: 7/17/2021]

Creative Commons License
이 저작물은 크리에이티브 커먼즈 코리아 저작자표시-비영리-변경금지 2.0 대한민국 라이센스에 따라 이용하실 수 있습니다.
by SeongTae Jeong, mailto:techsharer at outlook.com

비밀번호

댓글 작성자
 




... 46  47  48  49  50  [51]  52  53  54  55  56  57  58  59  60  ...
NoWriterDateCnt.TitleFile(s)
12666정성태6/10/202115636.NET Framework: 1067. 별도 DLL에 포함된 타입을 STAThread Main 메서드에서 사용하는 경우 CoInitializeSecurity 자동 호출파일 다운로드1
12665정성태6/9/202117718.NET Framework: 1066. Wslhub.Sdk 사용으로 알아보는 CoInitializeSecurity 사용 제약파일 다운로드1
12664정성태6/9/202115571오류 유형: 723. COM+ PIA 참조 시 "This operation failed because the QueryInterface call on the COM component" 오류
12663정성태6/9/202117964.NET Framework: 1065. Windows Forms - 속성 창의 디자인 설정 지원: 문자열 목록 내에서 항목을 선택하는 TypeConverter 제작파일 다운로드1
12662정성태6/8/202115553.NET Framework: 1064. C# COM 개체를 PIA(Primary Interop Assembly)로써 "Embed Interop Types" 참조하는 방법파일 다운로드1
12661정성태6/4/202127585.NET Framework: 1063. C# - MQTT를 이용한 클라이언트/서버(Broker) 통신 예제 [4]파일 다운로드1
12660정성태6/3/202118303.NET Framework: 1062. Windows Forms - 폼 내에서 발생하는 마우스 이벤트를 자식 컨트롤 영역에 상관없이 수신하는 방법 [1]파일 다운로드1
12659정성태6/2/202119205Linux: 40. 우분투 설치 후 MBR 디스크 드라이브 여유 공간이 인식되지 않은 경우 - Logical Volume Management
12658정성태6/2/202117053Windows: 194. Microsoft Store에 있는 구글의 공식 Youtube App
12657정성태6/2/202117794Windows: 193. 윈도우 패키지 관리자 - winget 설치
12656정성태6/1/202116164.NET Framework: 1061. 서버 유형의 COM+에 적용할 수 없는 Server GC
12655정성태6/1/202114716오류 유형: 722. windbg/sos - savemodule - Fail to read memory
12654정성태5/31/202115486오류 유형: 721. Hyper-V - Saved 상태의 VM을 시작 시 오류 발생
12653정성태5/31/202118715.NET Framework: 1060. 닷넷 GC에 새롭게 구현되는 DPAD(Dynamic Promotion And Demotion for GC)
12652정성태5/31/202116185VS.NET IDE: 164. Visual Studio - Web Deploy로 Publish 시 암호창이 매번 뜨는 문제
12651정성태5/31/202116418오류 유형: 720. PostgreSQL - ERROR: 22P02: malformed array literal: "..."
12650정성태5/17/202115752기타: 82. OpenTabletDriver의 버튼에 더블 클릭을 매핑 및 게임에서의 지원 방법
12649정성태5/16/202117910.NET Framework: 1059. 세대 별 GC(Garbage Collection) 방식에서 Card table의 사용 의미 [1]
12648정성태5/16/202116726사물인터넷: 66. PC -> FTDI -> NodeMCU v1 ESP8266 기기를 UART 핀을 연결해 직렬 통신하는 방법파일 다운로드1
12647정성태5/15/202116891.NET Framework: 1058. C# - C++과의 연동을 위한 구조체의 fixed 배열 필드 사용파일 다운로드1
12646정성태5/15/202115684사물인터넷: 65. C# - Arduino IDE의 Serial Monitor 기능 구현파일 다운로드1
12645정성태5/14/202115748사물인터넷: 64. NodeMCU v1 ESP8266 - LittleFS를 이용한 와이파이 접속 정보 업데이트파일 다운로드1
12644정성태5/14/202117133오류 유형: 719. 윈도우 - 제어판의 "프로그램 및 기능" / "Windows 기능 켜기/끄기" 오류 0x800736B3
12643정성태5/14/202117200오류 유형: 718. 서버 유형의 COM+ 사용 시 0x80080005(Server execution failed) 오류 발생
12642정성태5/14/202118721오류 유형: 717. The 'Microsoft.ACE.OLEDB.12.0' provider is not registered on the local machine.
12641정성태5/13/202117513디버깅 기술: 179. 윈도우용 .NET Core 3 이상에서 Windbg의 sos 사용법
... 46  47  48  49  50  [51]  52  53  54  55  56  57  58  59  60  ...