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!!!!!!!