вторник, 11 февраля 2014 г.

CORS. Как посылать запросы GET, POST, DELETE etc.

В наши дни не знать что такое CORS (wiki), сродни прогулки тёмной ночью по последнему этажу, недостроенной высотки где-нибудь за городом, в неблагополучном районе... Т.е. смертеподнобно, а особенно если вы фронт-енд разработчик.

Более подробно вы можете ознакомиться перейдя по ссылкам в конце текста, я же скажу вкратце, CORS это набор HTTP заголовков, которые позволяют объяснить браузеру и серверу, что они хоть и из разных доменов, но одной крови работать могут вместе. Т.е. кросс-доменные зпросы поддерживаються. Кто не знает, то запрос из браузера посредством старого, доброго JavaSctipt недавно, так на чистоту, без костылей сделать было невозможно. Ура, эти мрачные времена канули в лету. Пришёл на момощь CORS.

Речь пойдет именно о процессе использования и базовой настройки заголовков сервера, и отсыла запросов клиента с использованием jQuery.

Сервер, который мнит себя крутым сервисом, настолько куртым что хочет предоставить поддержку кросс-доменных запросов. Должен клинету сообщить об этом. Отправив следующие заголовки:

  • Access-Control-Allow-Origin
  • Access-Control-Allow-Methods
  • Access-Control-Allow-Headers

Это тот минимум, который необходим для успешной работы наших кросс-доменных запросов. немного рапишем что, к чему:

Access-Control-Allow-Origin — (обязательный) собственно список разделенный пробелами допустимых доменнов (источников), которые будут ломиться делать запросы к нам на сервер. Из особенностей: регистрозависим, поддерживает маски, например, http://api.superservice.com/, http://*.superservice.com/ или, как вы могли догадаться просто "звездочка" *. Этот заголовок будет сравниваться с заголовком Origin клиентского запроса.
Access-Control-Allow-Method — (обязательный) это список доступных HTTP методов, разделенных запятыми. Особое внимание стоит уделить тому, что "звездочка" аля * работать не будет!
Access-Control-Allow-Headers — (обязательный вместе с Access-Control-Request-Headers) список через запятую заголовков разрешенных в запросе.

Пример настройки для Apache:

# CORS заголовки (добавте это, например, в .htaccess)
<ifmodule mod_headers.c>
  Header always set Access-Control-Allow-Origin: "*"
  Header always set Access-Control-Allow-Methods "POST, GET, PUT, DELETE, OPTIONS"
  Header always set Access-Control-Allow-Headers "X-Requested-With, content-type"
</ifModule>

Давайте разберемся, что тут происходит? А происходит следующее, запросы будут обработанны с любого источника, если они будут одного из перечисленных в Header always set Access-Control-Allow-Methods типов, так же будут подерживаться заголовки X-Requested-With и content-type

Тот же пример для PHP

<?php
header('Access-Control-Allow-Origin: *');
header('Access-Control-Allow-Methods: POST, GET, PUT, DELETE, OPTIONS');
header('Access-Control-Allow-Headers: X-Requested-With, content-type');
?>

Теперь, предположим, что сервис у нас крутится по адресу http://api.superservice.com/ , а GET запрос нам надо сделать со странички http://pure.client.net/. Это не сложно сделать, к примеру с jQuery:

jQuery.ajax({
  url: 'http://api.superservice.com/credit-card-ids',
  type: 'GET',
  contentType: 'application/json', // тот тип контента, который вы будете получать от своего сервиса
  headers: { }, // Разные заголовки, нестандартные заголовки, не забудте их указать в Access-Control-Allow-Headers
  success: function (res) { 
  console.log(res);
},
error: function () { ... }
});

Думаю самое время упомянуть о неприятном моменте с AngularJs если вам понадобиться сделать кросс-доменый запрос с помощью $http или $resource, то надо будет удалить один заголовок, делается это так:

myApp.config(['$httpProvider', function($httpProvider) {
 $httpProvider.defaults.useXDomain = true;
 delete $httpProvider.defaults.headers.common['X-Requested-With'];
}
]);

Такое поведение странно, для меня, по крайней мере, несмотря на то что заголовок X-Requested-With вы укажите Access-Control-Allow-Headers не исключены проблемы с запросами. Ходят слухи что это бага AngularJs, кому интересно, если найдете что, отпишитесь в коментах.

Переходим к интересным запросам типа POST/PUT/DELETE, всех их объединяет то что они пытаются что-то изменить на сервере. Но как сделать такой запрос кросс-доменным? Сначала надо получить список заголовков типа Access-Control-*, делается это предварительным OPTIONS запросом. Который может вернуть тот же набор Access-Control-*, что и обычный метод GET.

На сервере если вы больше нигде и не для чего более не будете использовать OPTIONS запросы, то можете смело на все запросы типа OPTIONS отвечать необходимыми заголовками в том числе и такими заветными для нас как CORS заголовки. Если кто не понял то имееться ввиду добавить правило для всех запросов типа OPTIONS в роутах вашего любимого фреймворка. Или же сделать обработку руками.

Этот предварительный запрос (prefligth-запрос), делается автоматически jQuery или тем же Angular, по-тому же URL что и ваш основной запрос POST/PUT/DELETE поэтому будте внимательны если вы не сделаете правильной обработку запросов типа OPTIONS то и запрос POST/PUT/DELETE у вас сделать не получиться, из-за политики безопасности, который следуют браузеры.

Пример запроса POST с использованием jQuery.

jQuery.ajax({
  ulr: 'http://api.superservice.com/credit-card-ids',
  type: 'POST',
  data: '{"content": "' + content + '"}', // внимание! если вы передатите параметр как объект то это будет Query String, в данном случае это Request Body, т.к. тут передана строка.
  contentType: 'application/json', // тот тип контента, который вы будете получать от своего сервиса
  headers: { }, // Разные заголовки, нестандартные заголовки, не забудте их указать в Access-Control-Allow-Headers
  success: function (res) { 
  console.log(res);
},
error: function () { ... }
});

Я старался изложить кратко, в справочном виде, не очень получилось :), но думаю многим кто прочитал это сэкономит время. Ниже список источников, который позволят вам более глумого изучить вопрос CORS