简体   繁体   中英

JQuery stuck at CORS preflight and IIS ghost response

I'm stuck. Seriously... - solved. read on :)

Scenario : I'm trying to do the right thing here. I added CORS functionality to my REST Service (ASP.NET Web-API) relying on the Thinktecture Identitymodel CORS DelegatingHandler. So far so good.

To actually test if it is working I did the following:

  1. I set up a simple HTML page and published it on a different host than the rest service ( xttp://otherhost/simplewebpage ). The page uses JQuery to make the sample request. Code see below.
  2. Next I set up my rest service to not use iis express but rather the fully blown instance of it running on my development machine ( xttp://developmenthost/restservice ).
  3. Last but not least on my development machine I open up the xttp://otherhost/simplewebpage and fire the Ajax request. The error callback is executed telling me there was a "transport error" (IE9) or "" (empty string) in Chrome. I made sure there's no proxy related connectivity issue or anything like that.

So I went forth and looked at the Fiddler traces and IIS logs. Fiddler says there is no GET /rest/hello request but rather a OPTIONS /rest/hello request - which is totally fine and expected! However the response to the OPTIONS request is rather intriguing!

The whole response header looks like this:

HTTP/1.1 200 OK
Allow: OPTIONS, TRACE, GET, HEAD, POST
Server: Microsoft-IIS/7.5
Public: OPTIONS, TRACE, GET, HEAD, POST
Date: Fri, 15 Feb 2013 14:09:27 GMT
Content-Length: 0

This is of course nowhere even close to the expected response. The fun part about this is, that the Request didn't even hit Application_BeginRequest() in my application. So there's no way my application could be responsible for that result. I can see the request in my IIS logs and IIS adds the Powered-by-ASP.NET header.. so it definitely passes through (the right) IIS site.

The JQuery code that triggers the ajax request:

    function Run()
    {
        $.ajax({
            type: 'GET',
            url: url,
            dataType: "json",
            beforeSend: function(jqXhr) {
                jqXhr.setRequestHeader("Authorization", "Basic " + getBasicHttpEncodedString(userName, password));
                jqXhr.setRequestHeader("Api-Key", "123");
            },
            success: successCallback,
            error: errorCallback,
            timeout: 180*1000
        });
    }

The resulting OPTIONS request looks like that:

OPTIONS http://services.dev13/Rest/Hello HTTP/1.1
Host: developmenthost
Connection: keep-alive
Access-Control-Request-Method: GET
Origin: http://otherhost/simplewebpage
User-Agent: Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.17 (KHTML, like Gecko) Chrome/24.0.1312.57 Safari/537.17
Access-Control-Request-Headers: accept, origin, api-key, authorization
Accept: */*
DNT: 1
Referer: http://otherhost/simplewebpage
Accept-Encoding: gzip,deflate,sdch
Accept-Language: en-US,en;q=0.8
Accept-Charset: ISO-8859-1,utf-8;q=0.7,*;q=0.3

... and you have already seen the response to that above.

Any idea who exactly answers my OPTIONS request? Or is my JQuery code flawed? The REST Service works just fine if I use for example Postman (Google Chrome App) or if I forge Requests in Fiddler (That's probably because they don't do CORS negotiation - there's no OPTIONS request).

Update #1: Earlier today I read somewhere that disabling WebDAV is mandatory because it interferes with OPTIONS requests. My IIS Role Services view tells me WebDAV Publishing is Not installed .

* Update #2:* Problem solved?? I dug deeper. There's a module registered in IIS that is responsible for the "undesired(?)" response to the OPTIONS request. Its name is "OPTIONSVerbHandler" (handler: ProtocolSupportModule). If I disable that module the request passes through to my application. There a more meaningful response is created and then followed by the actual GET request! YAY!

HTTP/1.1 200 OK
Cache-Control: no-cache
Pragma: no-cache
Expires: -1
Server: Microsoft-IIS/7.5
Access-Control-Allow-Origin: http://otherhost/simplewebpage
Access-Control-Allow-Credentials: true
Access-Control-Allow-Headers: accept,origin,api-key,authorization
X-AspNet-Version: 4.0.30319
Date: Fri, 15 Feb 2013 15:09:25 GMT
Content-Length: 0

Once you know where the problem is of course you find plenty of resources telling you to make sure your web.config looks like that :-/

<system.webServer>
    <validation validateIntegratedModeConfiguration="false" />
    <modules runAllManagedModulesForAllRequests="false">
      <remove name="WebDAVModule" />
    </modules>
    <handlers>
      <remove name="OPTIONSVerbHandler" />
      <remove name="ExtensionlessUrlHandler-ISAPI-4.0_32bit" />
      <remove name="ExtensionlessUrlHandler-ISAPI-4.0_64bit" />
      <remove name="ExtensionlessUrlHandler-Integrated-4.0" />
      <add name="ExtensionlessUrlHandler-ISAPI-4.0_32bit" path="*." verb="GET,HEAD,POST,DEBUG,PUT,DELETE,PATCH,OPTIONS" modules="IsapiModule" scriptProcessor="%windir%\Microsoft.NET\Framework\v4.0.30319\aspnet_isapi.dll" preCondition="classicMode,runtimeVersionv4.0,bitness32" responseBufferLimit="0" />
      <add name="ExtensionlessUrlHandler-ISAPI-4.0_64bit" path="*." verb="GET,HEAD,POST,DEBUG,PUT,DELETE,PATCH,OPTIONS" modules="IsapiModule" scriptProcessor="%windir%\Microsoft.NET\Framework64\v4.0.30319\aspnet_isapi.dll" preCondition="classicMode,runtimeVersionv4.0,bitness64" responseBufferLimit="0" />
      <add name="ExtensionlessUrlHandler-Integrated-4.0" path="*." verb="GET,HEAD,POST,DEBUG,PUT,DELETE,PATCH,OPTIONS" type="System.Web.Handlers.TransferRequestHandler" preCondition="integratedMode,runtimeVersionv4.0" />
    </handlers>
  </system.webServer>

It doesn't still work in IE9 though ("error: no transport"). In case someone went down the same road as I did -> it's a IE9 thing: https://stackoverflow.com/a/10232313/1407618

Your answer is here: https://gist.github.com/mathieucarbou/1114981

Basically, IE has some pretty specific caveats and pitfalls for cors. I started writing one myself once, but then I found mathieucarbou's solution and decided his was better.

This may be a little heavier a solution than you wanted, but for cross-browser support I have found it to be acceptable to hit IE with the "custom solution" as hard as it takes.

Create PreflightRequestsHandler class where you allow request headers(1) and enable cors before your class(2).

1. public class PreflightRequestsHandler : DelegatingHandler
    {
        protected override Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, CancellationToken cancellationToken)
        {
            if (request.Headers.Contains("Origin") && request.Method.Method.Equals("OPTIONS"))
            {
                var response = new HttpResponseMessage { StatusCode = HttpStatusCode.OK };
                // Define and add values to variables: origins, headers, methods (can be global)               
                response.Headers.Add("Access-Control-Allow-Origin", "*");
                response.Headers.Add("Access-Control-Allow-Headers", "content-type");
                response.Headers.Add("Access-Control-Allow-Methods", "*");
                var tsc = new TaskCompletionSource<HttpResponseMessage>();
                tsc.SetResult(response);
                return tsc.Task;
            }
            return base.SendAsync(request, cancellationToken);
        }

    }

2. [EnableCors(origins: "*", headers: "*", methods: "*", exposedHeaders: "X-Custom-Header")]

检查这个set-access-control-allow-origin-in-web-api 。这可能会对你有所帮助。

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM