Cross-Origin Request sharing (CORS): wywoływanie zewnętrznych usług z JavaScript

Kilka postów wcześniej pisałem o JSONP, jako sposobie na wywoływanie serwisów znajdujących się w innych domenach z poziomu JavaScript. Domyślnie przeglądarki blokują takie wywołania ze względu na bezpieczeństwo. Załóżmy, że mamy następujący serwis w jakiejś domenie:

public class ValuesController : ApiController
{
   // GET api/values/5
   public string Get()
   {
      return "Hello World";
   }
}

Następnie w drugiej domenie mamy kod JavaScript próbujący pobrać dane z powyższej usługi:

$.ajax({ url: "http://localhost:24523/api/Values" }).
done(function (data) { $("#testLabel").text(data.Text); });

Próba połączenia się z usługą zakończy się oczywiście następującym błędem:

“XMLHttpRequest cannot load http://localhost:24523/api/Values.
No ‘Access-Control-Allow-Origin’ header is present on the requested resource. Origin ‘http://localhost:24018’ is therefore not allowed access.”

CORS to standard zaimplementowany przez większość współczesnych przeglądarek internetowych.  W skrócie jeśli kod JavaScript chce wykonać zapytanie cross-domain to przeglądarka najpierw wyśle specjalny pakiet do usługi. Jeśli usługa wyrazi zgodę na cross-domain, wtedy połączenie między domenowe zostanie nawiązane. Innymi słowy, to serwer decyduje czy dopuścić dane połączenie z obcej domeny. Z punktu technicznego zatem, zarówno przeglądarka jak i serwer muszą wspierać CORS. W WebAPI bardzo prostą możemy zaimplementować CORS. Wystarczy, że zainstalujemy następujący pakiet:

Install-Package Microsoft.AspNet.WebApi.Cors

Następnie w WebConfig wywołujemy EnableCors:


        public static void Register(HttpConfiguration config)
        {
            // Web API configuration and services
            config.EnableCors();

            // Web API routes
            config.MapHttpAttributeRoutes();

            config.Routes.MapHttpRoute(
                name: "DefaultApi",
                routeTemplate: "api/{controller}/{id}",
                defaults: new { id = RouteParameter.Optional }
            );
        }

 

Należy również oznaczyć dany kontroler atrybutem EnableCors:

    [EnableCors( "http://localhost:24018","*","*")]
    public class ValuesController : ApiController
    {
        // GET api/values/5
        public string Get()
        {
            return "Hello World@";
        }
    }

 

Po uruchomieniu strony, wszystko załaduje się prawidłowo. Jak widać jest to trochę prostsze niż JSONP i bardziej naturalne. JSONP to tak naprawdę wykorzystywanie pewnej luki w przeglądarkach, aczkolwiek jest to bardzo powszechna praktyka i nic nie stoi na przeszkodzie, aby po prostu używać JSONP.

Zajrzyjmy jeszcze do definicji atrybutu EnableCors:


public EnableCorsAttribute(string origins, string headers, string methods)
: this(origins, headers, methods, (string) null)
{
}

Jak widać, najważniejszy parametr to origin czyli adres strony, która będzie wywoływała daną usługę. Innymi słowy, w powyższym rozwiązaniu akceptujemy wyłącznie domenę http://localhost:24523. Możemy również być bardziej wybredni co do przychodzących zapytań i określić konkretne nagłówki czy metody HTTP (GET\POST itp.).

Przyjrzyjmy się również pakietom jakie przeglądarka i usługa wysyłają. W momencie, gdy klient (przeglądarka) próbuje nawiązać połączenie między domenowe, przeglądarka wyśle pakiet z nagłówkiem Origin równym adresowi klienta czyli w tym przypadku “Origin: http://localhost:24018”:


Accept:*/*
Accept-Encoding:gzip, deflate, sdch
Accept-Language:en-GB,en;q=0.8,en-US;q=0.6,pl;q=0.4
Cache-Control:max-age=0
Connection:keep-alive
Host:localhost:24523
Origin:http://localhost:24018
Referer:http://localhost:24018/Home/Index
User-Agent:Mozilla/5.0 (Windows NT 6.3; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/44.0.2403.125 Safari/537.36

Jeśli domena pokryje się z regułami opisanymi za pomocą EnableCors, wtedy zostanie zwrócony pakiet z nagłówkiem Access-Control-Allow-Origin równym danej domenie tzn.:


Access-Control-Allow-Origin:http://localhost:24018
Cache-Control:no-cache
Content-Length:14
Content-Type:application/json; charset=utf-8
Date:Mon, 03 Aug 2015 18:09:57 GMT
Expires:-1
Pragma:no-cache
Server:Microsoft-IIS/10.0
X-AspNet-Version:4.0.30319
X-Powered-By:ASP.NET
X-SourceFiles:=?UTF-8?B?YzpcdXNlcnNccGlvdHJ6XGRvY3VtZW50c1x2aXN1YWwgc3R1ZGlvIDIwMTVcUHJvamVjdHNcV2ViQXBwbGljYXRpb24yXFdlYkFwcGxpY2F0aW9uMlxhcGlcVmFsdWVz?=

 

W przyszłym poście opiszę jeszcze kilka rzeczy związanych z CORS. Na zakończenie zachęcam na zapoznanie się, które przeglądarki posiadają wsparcie dla CORS: http://caniuse.com/#feat=cors

 

Leave a Reply

Your email address will not be published.