Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Server cannot set status after HTTP headers have been sent - web api CORS

Alright all, I've been bashing my head bloody over this one...

The Set Up:

  • I have a Web Api 2.0 Project with Basic Authentication set up.
  • I have CORS enabled in the web.config
  • I have ELMAH logging errors
  • I have a DelegatingHandler handling the incoming request - (code below)
  • I have a very specific data structure so I'm not using Membership or Identity by Microsoft.
  • I'm flushing every request that comes in that uses the OPTIONS verb

The Problem:

I am getting the following error on EVERY Authenticated Request:

System.Web.HttpException (0x80004005): Server cannot set status after HTTP headers have been sent.
   at System.Web.HttpResponse.set_StatusCode(Int32 value)
   at System.Web.HttpResponseWrapper.set_StatusCode(Int32 value)
   at System.Web.Http.WebHost.HttpControllerHandler.<CopyResponseStatusAndHeadersAsync>d__31.MoveNext()
--- End of stack trace from previous location where exception was thrown ---
   at System.Runtime.CompilerServices.TaskAwaiter.ThrowForNonSuccess(Task task)
   at System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(Task task)
   at System.Runtime.CompilerServices.TaskAwaiter`1.GetResult()
   at System.Web.Http.WebHost.HttpControllerHandler.<CopyResponseAsync>d__7.MoveNext()
--- End of stack trace from previous location where exception was thrown ---
   at System.Runtime.CompilerServices.TaskAwaiter.ThrowForNonSuccess(Task task)
   at System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(Task task)
   at System.Runtime.CompilerServices.TaskAwaiter.GetResult()
   at System.Web.Http.WebHost.HttpControllerHandler.<ProcessRequestAsyncCore>d__0.MoveNext()
--- End of stack trace from previous location where exception was thrown ---
   at System.Runtime.CompilerServices.TaskAwaiter.ThrowForNonSuccess(Task task)
   at System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(Task task)
   at System.Runtime.CompilerServices.TaskAwaiter.GetResult()
   at System.Web.TaskAsyncHelper.EndTask(IAsyncResult ar)
   at System.Web.HttpTaskAsyncHandler.System.Web.IHttpAsyncHandler.EndProcessRequest(IAsyncResult result)
   at System.Web.HttpApplication.CallHandlerExecutionStep.OnAsyncHandlerCompletion(IAsyncResult ar)

Investigation done so far:

The error listed above is happening only with the OPTIONS verb.

The error says that the headers were already sent before it was asked to set its Status Code.

Only 1 error record appears - in elmah, the "SendAsync" method is hit twice - once for the OPTIONS pre-flight and once for the GET or POST. That tells me that this is only happening on one request - the OPTIONS request.

In order to get past the pre-flight, I'm using this: (global.asax)

protected void Application_BeginRequest()
{
  if (Request.Headers.AllKeys.Contains("Origin") && Request.HttpMethod == "OPTIONS")
    {
        Response.Flush();
    }
}

This passes the responses into my handler, but it ALSO passes the OPTIONS verb into this code, which I think it should:

//Capture incoming request by overriding SendAsync method

protected override Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, CancellationToken cancellationToken)
{
    //validate credentials, set CurrentPrincipal and Current.User if valid
    if (ValidateCredentials(request.Headers.Authorization))
    {
        Thread.CurrentPrincipal = new MyPrincipal(_userName);
        HttpContext.Current.User = new MyPrincipal(_userName);
    }


    // Execute base.SendAsync to execute default actions
    // and once it is completed, capture the response object and add
    // WWW-Authenticate header if the request was marked as (401) unauthorized.

    return base.SendAsync(request, cancellationToken).ContinueWith(task =>
    {
        HttpResponseMessage response = task.Result;
        if (response.StatusCode == HttpStatusCode.Unauthorized && !response.Headers.Contains("WWW-Authenticate"))
        {
            response.Headers.Add("WWW-Authenticate", "Basic");
        }
        return response;
    });

}

Now in the debugger, I'm seeing that the OPTIONS goes through and fires a 405 - method not allowed, but when it comes through to elmah it's a 500 error - Server cannot set status after HTTP headers have been sent.

I'm NOT putting OPTIONS on any of my controllers, so that might explain the 405 - but the request shouldn't get that far.

So - this sounds like a config thing? Confused.

My cors config is:

<!-- BEGIN: Enable CORS -->
<httpProtocol>
  <customHeaders>
    <add name="Access-Control-Allow-Origin" value="*" />
    <add name="Access-Control-Allow-Headers" value="accept, authorization, origin, content-type" />
    <add name="Access-Control-Allow-Methods" value="GET, POST, PUT, DELETE, OPTIONS" />
  </customHeaders>
</httpProtocol>

<handlers>
  <remove name="ExtensionlessUrlHandler-Integrated-4.0" />
  <remove name="OPTIONSVerbHandler" />
  <remove name="TRACEVerbHandler" />
  <remove name="WebDAV"/>
  <add name="ExtensionlessUrlHandler-Integrated-4.0" path="*." verb="*" type="System.Web.Handlers.TransferRequestHandler" preCondition="integratedMode,runtimeVersionv4.0" />
</handlers>
<!-- END: Enable CORS -->

My Thoughts so far

The error is not a "FatalError" and the program still fires because the preflight request doesn't effect anything that I'm aware of - so can the error be ignored? Well, NO! It's an error!

Now - with the research I've done, it seems like this is an IIS thing that is leaking into my app that I may be able to stop through config - but how?

If that's not it then what the hell IS causing this? Where is it coming from? How can I fix it? No idea...

So I'm leaning on the valiant wisdom of my fellow nerds on Stack Overflow...

HELP!!!!!!!

like image 813
user1628627 Avatar asked Apr 17 '15 21:04

user1628627


1 Answers

Put a Response.End(); right after the Response.Flush();

So you should have an empty response for the preflight request.

At least this solved the issue for me.

like image 198
devployment Avatar answered Oct 20 '22 12:10

devployment