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

비밀번호

댓글 작성자
 




... 76  77  78  79  80  81  82  83  [84]  85  86  87  88  89  90  ...
NoWriterDateCnt.TitleFile(s)
11885정성태5/7/201918353오류 유형: 533. mstest.exe 실행 시 "File extension specified '.loadtest' is not a valid test extension." 오류 발생
11884정성태5/5/201923408.NET Framework: 828. C# DLL에서 Win32 C/C++처럼 dllexport 함수를 제공하는 방법 - 두 번째 이야기
11883정성태5/3/201928350.NET Framework: 827. C# - 인터넷 시간 서버로부터 받은 시간을 윈도우에 적용하는 방법파일 다운로드1
11882정성태5/2/201924916.NET Framework: 826. (번역글) .NET Internals Cookbook Part 11 - Various C# riddles파일 다운로드1
11881정성태4/28/201924976오류 유형: 532. .NET Core 프로젝트로 마이그레이션 시 "CS0579 Duplicate 'System.Reflection.AssemblyCompanyAttribute' attribute" 오류 발생
11880정성태4/25/201920636오류 유형: 531. 이벤트 로그 오류 - Task Scheduling Error: m->NextScheduledSPRetry 1547, m->NextScheduledEvent 1547
11879정성태4/24/201930013.NET Framework: 825. (번역글) .NET Internals Cookbook Part 10 - Threads, Tasks, asynchronous code and others파일 다운로드2
11878정성태4/22/201924862.NET Framework: 824. (번역글) .NET Internals Cookbook Part 9 - Finalizers, queues, card tables and other GC stuff파일 다운로드1
11877정성태4/22/201925110.NET Framework: 823. (번역글) .NET Internals Cookbook Part 8 - C# gotchas파일 다운로드1
11876정성태4/21/201922927.NET Framework: 822. (번역글) .NET Internals Cookbook Part 7 - Word tearing, locking and others파일 다운로드1
11875정성태4/21/201925195오류 유형: 530. Visual Studo에서 .NET Core 프로젝트를 열 때 "One or more errors occurred." 오류 발생
11874정성태4/20/201925176.NET Framework: 821. (번역글) .NET Internals Cookbook Part 6 - Object internals파일 다운로드1
11873정성태4/19/201923888.NET Framework: 820. (번역글) .NET Internals Cookbook Part 5 - Methods, parameters, modifiers파일 다운로드1
11872정성태4/17/201924716.NET Framework: 819. (번역글) .NET Internals Cookbook Part 4 - Type members파일 다운로드1
11871정성태4/16/201922114.NET Framework: 818. (번역글) .NET Internals Cookbook Part 3 - Initialization tricks [3]파일 다운로드1
11870정성태4/16/201921626.NET Framework: 817. Process.Start로 실행한 콘솔 프로그램의 출력 결과를 얻는 방법파일 다운로드1
11869정성태4/15/201928390.NET Framework: 816. (번역글) .NET Internals Cookbook Part 2 - GC-related things [2]파일 다운로드2
11868정성태4/15/201923453.NET Framework: 815. CER(Constrained Execution Region)이란?파일 다운로드1
11867정성태4/15/201922437.NET Framework: 814. Critical Finalizer와 SafeHandle의 사용 의미파일 다운로드1
11866정성태4/9/201924916Windows: 159. 네트워크 공유 폴더(net use)에 대한 인증 정보는 언제까지 유효할까요?
11865정성태4/9/201921395오류 유형: 529. 제어판 - C:\ProgramData\Microsoft\Windows\Start Menu\Programs\Administrative Tools is not accessible.
11864정성태4/9/201919926오류 유형: 528. '...' could be '0': this does not adhere to the specification for the function '...'
11863정성태4/9/201919826디버깅 기술: 127. windbg - .NET x64 EXE의 EntryPoint
11862정성태4/7/201921877개발 환경 구성: 437. .NET EXE의 ASLR 기능을 끄는 방법
11861정성태4/6/201921101디버깅 기술: 126. windbg - .NET x86 CLR2/CLR4 EXE의 EntryPoint
11860정성태4/5/201925952오류 유형: 527. Visual C++ 컴파일 오류 - error C2220: warning treated as error - no 'object' file generated
... 76  77  78  79  80  81  82  83  [84]  85  86  87  88  89  90  ...