Показаны сообщения с ярлыком PHP. Показать все сообщения
Показаны сообщения с ярлыком PHP. Показать все сообщения

вторник, 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

четверг, 30 августа 2012 г.

PHP. Работа с CSV файлами.

Работая надо одним проектом, часто приходилось работать с данными полученными из разных источников. Все эти данные были сформированы в виде CSV файлов. Причем все имели разные форматы. Нужен был инструмент для быстрой и удобной работы. Решил написать. Т.к. из того что я нашел не было ничего подходящего.

Итак что умеет этот класс. Главное это то что он осуществляет удобную загрузку данных CSV и, путем несложного дополнения, можно экспортировать данные в другой формат, а также формирование и сохранение данных в CSV формат. Все делается очень просто и без особых усилий. Из способностей этого класса есть еще такие "недопиленные" фишки как поиск по колонкам и по строкам, пока в некоторых случаях срабатывает некорректно. Но от класса этого и не требовалось, так сказать это побочный положительный эффект.
Итак по порядку. Класс можно забрать на Gitgub'e.
Использовать его очень просто. Ниже пример инициализации и загрузки CSV данных.

$options = array(
    'splitter' => ';', // Это разделитель "колонок" в CSV файле
    'wrapper' => '"', // А это то во что "завернуты" значения "колонок"
);
$csv = new Csv($options);
А вот так просто мы можем добавить строку в CSV файл, но данные сохранятся только после вызова метода Csv::save([file_name])
// Можно передать и в конструктор в виде параметра file => 'filename.csv'
$csv->file('./users.csv');
$csv->add(
    array ('id', 'user', 'name', 'email')
);
// Если как в этом примере мы указали имя файла то просто вызываем Csv::save если нет, то передаем в качестве параметра имя файла
$csv->save()

Если мы заранее указали имя файла с которым работаем, то и этот метод вызываем без параметров. Это загрузка CSV файла и его парсинг.

$csv->load();
или
$csv->load("./users.csv');

Этот класс так может выполнять операции по получению данных из опреденных ячеек, а так же можно задать названия столбцов. Несколько примеров по работе с данными

$csv->rows(); // Получаем все распарсенные строки из файла (в виде массива)
$csv->row(1); // Получи первую строку, причем если вы укажите в параметрах конструктора параметр columnNames, то это строка выведет данные а не названия столбцов
$csv->cell(10, 3); // Выведет данные из 10 строки 3 колонки
$csv->remove(3); // Удалит 3ю строку из файла
$csv->readOnly(true); // Пометить объект как "только чтение" и методы add и remove будут генерировать исключения
$data = $csv->columns() // Получить все колонки
$userNames = $csv->column('name'); // Получить колонку с именами (если конечно был задан map колонок)

// Весь поиск прекращает работу и возвращает строку как только будет найдена первая строка удовлетворяющая поиску
$csv->find('Vasya'); // Будет работать медленее всего, вернет первую строку в которой есть 'Vasya', регистр важен
$csv->find('Vasya', 2); // Будет работать быстрее чем предыдущий вариант, с таким же результатом, но не по всей таблице а только по 3й колонке
$csv->find('Vasya', 2, Csv::FIND_SENS_CASE); // Быстрый регистронезависимый поиск по 3й колонке
$csv->save(); // Сохраняем все изменения если конечно объект не помечен как "только чтение"
Замечу, что поиск работает очень быстро, разумеется для этого типа данных. Я потратил наверное больше всего времени на этот кусочек реализации. Думаю вы оцените, для сравнения, поиск по списку из 450 тыс. (колонок было 13), данные из середины, находились без указания колонки поиска за ~2с. а с указанием за ~0.5), конечно, эти данные абсолютно ни о чем не говорят, а просто приблизительно показывают, что поиск вполне можно использовать.
Благодаря этому классу, я смог быстро реализовать импорт/экспорт данных из CSV в Ms Excel XML и из CSV в MySQL, с минимальным набором логики, как вы заметили есть методы, которые позволяют манипулировать данными, думаю это очень важно.
И мне показалось, что не поделиться им с вами, будет не честно, зачем ему валяться без дела, пускай людям приносит радость и экономит время.

Скачать класс с Github
Можно забрать с composer

вторник, 3 июля 2012 г.

Разработка. Как передать HTTP_REFERER из flash.

Предыстория такова. Есть на некоем ресурсе флеш-виджет которые выполняет какие-то операции с пользовательским контентом. В моем конкретном случае, это фотографии. Задача стояла следующим образом. Необходимо собирать статистику показов с внешних ресурсов, т.е. сколько раз и где флешка засветилась на экранах пользователей. задача так же усложнялась и тем, что по сути показы должны были учитываться не самой флешки, а меняющихся в ней фотографий. Если же вам так же понадобится учитывать показ контента внутри флеш, то, увы, прийдется обратиться к флешеру, сваявшего этот виджет. Зачем я объясню позже.
Итак приступим. Все мы прекрасно знаем, что флешка передать реферера не может, вернее может, но этот реферер будет физическим адресом размещения самой флешки. Что не есть хорошо и нам абсолютно не надо. Так как же передать реферера? Очень просто, достаточно обвернуть выдачу флешки в php-скрипт. В котором мы будем выхватывать реферера учитывать показ. Если же у вас задача, так же как и у меня, "со звездочкой", то делаем нехитрый трюк с редиректом и добавлением параметра в запрос, таким вот образом. Получив реферер, в PHP скрипте, мы генерируем новый запрос к флешке и делаем по нему редирект. Флешка в своб очередь, получив этот параметр, добавляет его к каждому запросу (в моем случае фотографии) к нашему серверу, где этот запрос, обрабатывается скриптом, который учитывает всю необходимую нам информацию, включая реферера. Теперь чтоб совсем все стало ясно, приведу небольшой пример утопического кода (без проверок и пр. только логика, так что, пожалуйста не возмущайтесь понапрасну):

// Файл flashcatch.php
$file = './flash.swf';
$referer = $_SERVER['HTTP_REFERER'];
// Если не надо делать никаких более действий, то прямо здесь счетаем показ
// ...
// Устанавливаем тип содержимого
header('Content-Type: application/x-shockwave-flash');
// Читаем и отдаем флешку.
echo file_get_content($file);
exit();

Если понадобиться добавить параметр для последующего учета показов контента внутри самой флешки то код немного усложниться.

// Файл flashcatch-trick.php
$file = './flash.swf';
$referer = $_SERVER['HTTP_REFERER'];
// Добавляем параметр к запросу на флешку например referer.
if (strpos($_SERVER['REQUEST_URI'], '?')) {
    $connector = '&';
} else {
    $connector = '?';
}
$newRequest = 'http://' . $_SERVER['SERVER_NAME'] . $_SERVER['REQUEST_URI'] . $connector . 'referer=' . $referer;
// Если запрос является внешним и запрос не содержит в себе параметра referer то делаем редирект
if (isExternalRequest() && !$_REQUEST['referer']) {
    header ('Location: ' . $newRequest);
    exit();
}
// Устанавливаем тип содержимого
header('Content-Type: application/x-shockwave-flash');
// Читаем и отдаем флешку.
echo file_get_content($file);
exit();

И вот тут вспоминаем про флешера, во флешку передаем новый параметр в котором прописан реферер, в моем случае это параметр referer :). А флешер формируя запросы к к серверу для получения фотографий (аудио, видео), подставляет его и сервер при выдаче содержимого флешке учитывает реферера и показ, соответсвенно.

Функция isExternalRequest не приведена, ее не составит труда написать самому. Суть ее думаю ясна, вернет труЪ если это внешний запрос и фальс если свой же сервер запросил флешку.