Форумы портала PHP.SU » PHP » Уроки php » Урок № 14 - CURL

Страниц (1): [1]
 

1. valenok - 25 Мая, 2009 - 00:37:54 - перейти к сообщению
libcurl для обменна данных

libcurl это библиотека функций, которая позволяет взаимодействовать (обмениваться информацией) с различными серверами по различным протоколам. В настоящее время libcurl поддерживает протоколы http, https, ftp, gopher, telnet, dict, file, и ldap. libcurl также умеет работать с сертификатами HTTPS, посылать запросы к HTTP серверам методами POST и PUT, закачивать файлы по протоколам HTTP и FTP (последнее можно сделать также используя модуль FTP), использовать прокси-серверы, cookies и аутентификацию пользователей.


Нас поймут только по протоколу

Любые сервера, в том числе и web, умеют реагировать на присланные им данные т, но только в том случае, если они понимают, что им прислали. Для этого, данные посылаемые им оформляются согласно определенным правилам. Такой набор правил и называется протоколом. Данные по протоколу оформит libcurl сама, мы же лишь рассмотрим далее как их ей передать.


Раз, два, начали.

Функций в библиотеке совсем не много, но одну из них, приводящую шестеренки в действие, вы встретите в любом скрипте с curl. curl_init — Инициализирует сеанс CURL
Другими словами - эта функция запускает механизм curl и возвращает указатель, дескриптор на созданный механизм.

Напомню что такое ресурс (указатель), тем кто забыл.

Дескриптор ( Resource )
Дескриптор представляет из себя указатель, ссылку, на внешний ресурс.
Представим автосервис с большим количеством машин, которым заливают несколько по литров масла в двигатель. Со свистом тормозов из подворотни вылетает феррари и паркуется на очередной сервисной парковке. Хозяин сервиса уже кричит рабочему, залить 5 литров масла и тычет большим пальцем в красную феррари.
Наш рабочий получает в данном случае два типа данных - число (литров масла) и указатель на машину, то-есть определение - какому именно объекту из всех вокруг нужно подлить чего-нибудь.

Подмечу, что рабочий получает от босса вовсе не саму машину, а лишь дескриптор (указатель) машины с которой предстоит работать. В php этим дескриптором является тип данных resource.

Функция curl_init также может сразу принимать url, адрес того сервера с которым будем общаться. Можно его и не указывать, а указать попозже. Инициализировав механизм, можно сразу отправить запрос, ну и наконец освободить память от этого механизма.
Вот что в итоге получится:
PHP:
скопировать код в буфер обмена
  1. <?PHP
  2. $ch = curl_init('http://php.su');
  3. curl_exec($ch); // выполняем запрос curl - обращаемся к сервера php.su
результатом этого кода будет прямой вывод содержимого главной страницы php.su. Не всегда нужно вывести результат запроса прямо в браузер и для этого достаточно просто покрутить пару настроек. Сейчас выясним как это сделать.


Конфигурируем общение

curl_setopt — Устанавливает параметр для сеанса CURL
параметры бывают разные, и их много, очень. Какой за что отвечает можно посмотреть, само собой, в справочнике функции curl_setopt. Мы же сейчас рассмотрим парочку основных и выясним как ими манипулировать.

Но прежде я хочу вернуться на момент к обсуждению протокола http. Из чего же состоит набор правил этого протокола. Давай посмотрим как браузер общается с нашим сервером, что он ему посылает, и что тот от него получает. Я для этого использую расширение браузера livehttpheaders. И вот, что происходит при общении браузера с сервером:

Общение браузера с сервером получилось не слишком замысловатое. Смотрим:

GET /index.php HTTP/1.1 Дай страницу index.php. данные пришли правильно оформленные
Host: php.su с сайта php.su
User-Agent: Mozilla/5.0 А вот такой вот!
Accept: text/html И понимаю я только текст и html.
Accept-Language: ru,en-us; И говори по русски или я твоя не понимать
Accept-Charset: windows-1251,utf-8; Со специями пожалуйста.
Connection: keep-alive Жду от тебя ответа
Keep-Alive: 300 но терпения у меня мало.
Cookie: lastvisit=1243232518; А еще я заходил вчера и заказывал столик. Помнишь меня ? Нет? ну не важно, ты просил напомнить что я заходил во столько то. Теперь вспомнил? Отлично. Где мой столик ?



HTTP/1.1 200 OK Данные принял, состояние 200.
Date: Mon, 25 May 2009 06:33:05 GMT
Server: Apache Вас обслуживает ООО "Сервер Apache"
X-Powered-By: PHP/5.2.6 Старший шеф повар, php 5.2.6
Transfer-Encoding: chunked Это порция первая, вторая ща будет
Connection: close А теперь получил?, распишись, больше не жди
Content-Type: text/html; charset=cp1251 Ваша пицаа по русски, с грибами

Сразу после заголовков ответа идёт сам ответ, тоесть html страницы. А мы, тем временем, что стали свидетелями общения моего браузера с сервером php.su. А раз браузер может, то и мы с нашей программой можем.


Заказываем данные с нужными опциями

После того, как мы запустили наш код простой код
PHP:
скопировать код в буфер обмена
  1. <?PHP
  2. $ch = curl_init('http://php.su');
  3. curl_exec($ch); // выполняем запрос curl - обращаемся к сервера php.su
мы увидели в браузере только html код полученный от сервера. Возможно нам захочется также посмотреть на заголовки, который прислал сервер, а то вдруг я все выше написанное выдумал ?

Для этого установим опцию "показывать заголовки"
CURLOPT_HEADER : При установке этого параметра в ненулевое значение результат будет включать полученные заголовки.
Разумеется параметры устанавливать нужно до того, как отправим сам запрос серверу. И так, получилось вот такое:
PHP:
скопировать код в буфер обмена
  1. <?PHP
  2. $ch = curl_init('http://php.su');
  3. curl_setopt  ($ch, CURLOPT_HEADER, true);
  4. curl_exec($ch); // выполняем запрос curl


Возможно нам также захочется получит содержимое в переменную, а вовсе не выводить сразу в браузер. Для этого нам придется установить такое значение среди прочих командой: curl_setopt ($ch, CURLOPT_RETURNTRANSFER, 1);
Хотя документация видимо ошибается в плане настроек..


Позволю себе заметить, что в документации написано что названием параметра должна быть строка. Мы же передаем функции setopt вовсе не строку (кавычек то нет). Мы передаем константу. Предопределенную переменную, значение которой изменить нельзя. Эти константы сами определяются библиотекой. интересно другое.. значения этих констант вовсе не строковые, а численные. Поэтому если кто-то узнает почему в документации написано "строка" вместо числа - дайте мне знать.

А мы пока попробуем авторизоваться на форуме. А для того, чтобы имитировать обычный браузер, нам нужно посмотреть что говорит браузер серверу, что тот ему отвечает и что потом.


Строим из себя пользователя

В первую очередь, чтоб все это показалось правдоподобным, посмотрим что происходит когда мой браузер авторизовывается на форуме. Для этого я опять же воспользуюсь расширением для FireFox под названием liveHttpHeaders и вот что я вижу.


CODE (text):
скопировать код в буфер обмена
  1. ----------------------------------------------------------
  2. http://php.su/forum/loginout.php
  3.  
  4. POST /forum/loginout.php HTTP/1.1
  5. Host: php.su
  6. User-Agent: Mozilla/5.0 (X11; U; Linux x86_64; en-US; rv:1.9.0.10) Gecko/2009042523 Ubuntu/8.10 (intrepid) Firefox/3.0.10
  7. Accept: text/html,application/xhtml xml,application/xml;q=0.9,*/*;q=0.8
  8. Accept-Language: ru,en-us;q=0.7,en;q=0.3
  9. Accept-Encoding: gzip,deflate
  10. Accept-Charset: windows-1251,utf-8;q=0.7,*;q=0.7
  11. Keep-Alive: 300
  12. Connection: keep-alive
  13. Referer: http://php.su/forum/loginout.php
  14. Content-Type: application/x-www-form-urlencoded
  15. Content-Length: 71
  16. action=login&imembername=valenok&ipassword=ne_skaju&submit=%C2%F5%EE%E4
  17.  
  18. HTTP/1.x 302 Found
  19. Date: Tue, 26 May 2009 14:09:09 GMT
  20. Server: Apache
  21. X-Powered-By: PHP/5.2.6
  22. Expires: Thu, 19 Nov 1981 08:52:00 GMT
  23. Cache-Control: no-store, no-cache, must-revalidate, post-check=0, pre-check=0
  24. Pragma: no-cache
  25. Set-Cookie: lastvisit=1243346949; expires=Wed, 26-May-2010 14:09:09 GMT; path=/
  26. Set-Cookie: exbbn=19; expires=Wed, 26-May-2010 14:09:09 GMT; path=/
  27. Set-Cookie: exbbp=1234567525d2b72bcb01cd2ffe123456; expires=Wed, 26-May-2010 14:09:09 GMT; path=/
  28. Set-Cookie: PHPSESSID=123456789e4eef401e4539060010cc0f;
  29. Set-Cookie: lastvisit=1243346949; expires=Wed, 26-May-2010 14:09:09 GMT; path=/
  30. Location: index.php
  31. Content-Encoding: gzip
  32. Vary: Accept-Encoding
  33. Content-Length: 26
  34. Connection: close
  35. Content-Type: text/html; charset=cp1251

Запрос отличается от предыдущего лишь немногим. рассмотрим разницу.

POST /forum/loginout.php HTTP/1.1 На этот раз мы не просто просим дать нам содержимое страницы, но отправляем серверу свои данные.
Referer: http://php.su/forum/loginout.php Откуда мы шлем данные (на какой страницы только что был браузер)
Content-Type: application/x-www-form-urlencoded тип данных (данные из формы)
Content-Length: 71 длинна посылаемых данных.
action=login&imembername=valenok&ipassword=ne_skaju&submit=%C2%F5%EE%E4 ну и сами данные.. Заметь что отправляются все данные из формы.

А вот ответ немного отличается от предыдущего. В нем появляются новые заголовки. В первую очередь нас интересуют Set-Cookie и Location. Остальные особой роли не играют и что они означают можно найти в википедии.

Set-Cookie как ты видишь, и их много, возможно тебе уже знаком. Задача данного заголовка это наклеить на тебя наклейку с именем, чтобы потом сервер мог тебя по нему узнать и сказать, ах, да, точно, это ты. Разумеется для этого при каждом следующем обращении к серверу нужно будет приходить с этой наклейкой.

Location: index.php перенаправляет браузер на другую страницу, после авторизации.

Ну что, теперь попробуем сыграть за браузер ?
2. valenok - 26 Мая, 2009 - 19:47:13 - перейти к сообщению
Я тоже браузер

Будем анализировать на практике. Код получился следующий.
PHP:
скопировать код в буфер обмена
  1. <?PHP
  2.  
  3. $ch = curl_init('http://php.su/forum/loginout.php');
  4. # /forum/loginout.php HTTP/1.1
  5.  
  6. curl_setopt($ch, CURLOPT_POST, 1);
  7. # POST /forum/..
  8.  
  9.  
  10. curl_setopt ($ch, CURLOPT_USERAGENT, "Mozilla/5.0 (бла бла бла..) ");
  11. # User-Agent
  12.  
  13.  
  14. $headers = array
  15. (
  16.     'Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*;q=0.8',
  17.         'Accept-Language: ru,en-us;q=0.7,en;q=0.3',
  18.         'Accept-Encoding: deflate',
  19.         'Accept-Charset: windows-1251,utf-8;q=0.7,*;q=0.7'
  20. );
  21.  
  22. curl_setopt($ch, CURLOPT_HTTPHEADER,$headers);
  23. # добавляем заголовков к нашему запросу. Чтоб смахивало на настоящих
  24.  
  25. curl_setopt($ch, CURLOPT_REFERER, "http://php.su/forum/loginout.php");
  26. # Подделываем значение - откуда пришли данные.
  27.  
  28. curl_setopt($ch, CURLOPT_POSTFIELDS, 'action=login&imembername=valenok&ipassword=ne_skaju&submit=%C2%F5%EE%E4');
  29. # post данные.
  30. # умная libcurl сама добавит заголовки
  31. # Content-Type: application/x-www-form-urlencoded и Content-Length: 71
  32.  
  33. curl_setopt($ch, CURLOPT_COOKIEJAR, "my_cookies.txt");  
  34. curl_setopt($ch, CURLOPT_COOKIEFILE, "my_cookies.txt");  
  35. # Функции для обработки установливаемых форумом кук.
  36. # подробнее рассмотрим далее.
  37.  
  38. curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1);
  39. # Убираем вывод данных в браузер. Пусть функция их возвращает а не выводит
  40.  
  41. $result = curl_exec($ch); // выполняем запрос curl
Какой параметр за что отвечает я показал в коде. Если что-то не до конца ясно, всегда можно посмотреть в документации. Тем не менее, двумя словами опишу параметры cookiejar и cookiefile.

Когда сервер выдает нам куки, тоесть наклейку - Ты такой-то, он потом смотрит на эту наклейку и вспоминает тебя. Но нам для этого разумеется нужно обращаться к серверу когда наклейка у нас висит на видном месте. libcurl может за нас сохранять наклейку в файл, если мы его укажем в параметре cookiejar и также посылать куки, тоесть обращаться вместе с наклейкой, если мы укажем файл в котором эту наклейку мы сохранили cookiefile. А так как нужно было для авторизации чтоб сервер запомнил что мы, это мы при следующем обращение, то на самом деле нужно было просто получить при авторизации куки. Поэтому вот как мы это сделаем.
PHP:
скопировать код в буфер обмена
  1. <?PHP
  2. $ch = curl_init('http://php.su/forum/loginout.php');
  3. curl_setopt($ch, CURLOPT_POST, 1);
  4. curl_setopt($ch, CURLOPT_NOBODY, 1);
  5. curl_setopt($ch, CURLOPT_POSTFIELDS, 'action=login&imembername=valenok&ipassword=ne_skaju&submit=%C2%F5%EE%E4');
  6. curl_setopt($ch, CURLOPT_COOKIEJAR, "my_cookies.txt");  
  7. curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1);
  8. curl_exec($ch);


Здесь я сократил несколько этапов. Причиной стало то, что сервер на самом деле не проверяет откуда пришли данные авторизации, поэтомум реферер указывать не имеет смысла. Какой браузер авторизовывается - ему тоже все равно. Уберем.
Заголовки о том, что мы хотим получить в ответ - тоже отправлять не будем. В ответ же мы все равно получим куки и перенаправление. Кроме того почти ни один программист все равно эти заголовки не учитывает. Они как бараны решили что знают как правильно и что нужно пользователю - и все тут.
Также установлен параметр - без тела (nobody) который говорит что весь html нам не нужен, нужны только заголовки. На самом деле его там и нет, но это только в нашем случае. На самом деле скрипт может и проводить авторизацию и ругаться в одном флаконе.
Присвоение результата в переменную я тоже убрал. Зачем нам в памяти результат запроса.. Мы и так знаем что он успешен. Но если не знаем, можно для примера проверять на наличие перенаправления Location:index.php и на основании этого решать, правильно ли авторизовались или нет. Но ничего ли мы не забыли ?


Пара рекомендаций

На самом деле все что бы могло тебе потребоваться - мы уже прошли. Ты умеешь уже авторизоваться где нибудь, отправить форму (пост данные) средствами библиотеки curl при этом выдавать себя за другой браузер. И напоминать серверу кто ты - с прошлого раза при помощи кук.

Я же лишь скажу что иногда тебе вовсе не нужна страница, а лишь её заголовки, как в нашем примере авторизации. Еще не нужно для каждого запроса создавать в памяти экземпляр механизма curl. Один раз его инициализируем, потом просто меняем параметры и адрес url. Думаю этого вполне хватит для успешного обмена данным с другими сайтами по началу, ну а если не хватит, то можно рассмотреть несколько сложных случаев далее.
3. valenok - 26 Мая, 2009 - 23:17:48 - перейти к сообщению
Несколько параллельных запросов и curl_multi_init

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


Для следующего кода примеры данных $data могут быть следующими:
PHP:
скопировать код в буфер обмена
  1. <?PHP
  2. // GET
  3. $data = Array
  4. (
  5.         'http://yandex.ru',
  6.         'http://php.su',
  7.         'http://google.com'
  8. );
  9.  
  10. // POST
  11. $data = Array
  12. (
  13.         Array('url' => 'http://yandex.ru/login.php', 'post' => 'a=b&c=d'),
  14.         Array('url' => 'http://php.su/index.php', 'post' => 'a=b&c=d'),
  15.         Array('url' => 'http://google.com/search.py', 'post' => 'a=b&c=d')
  16. );
Сама функция
PHP:
скопировать код в буфер обмена
  1. <?PHP
  2. function multiCurl($data, $options = array())
  3. {
  4.  
  5.   $curls = array();
  6.   // Массив дескрипторов. Библиотека создат много экземпляров своего
  7.   // механизма, но работать они будут параллельно
  8.  
  9.   $result = array();
  10.   // массив с результатами запрошенных страниц которые наша функция вернет.
  11.  
  12.   $mh = curl_multi_init();
  13.   // Дескриптор мульти потока. Тоесть эта штука отвечает за то, чтобы много
  14.   // запросов шли параллельно.
  15.  
  16.   foreach ($data as $id => $d) {
  17.  
  18.     $curls[$id] = curl_init();
  19.         // Для каждого url создаем отдельный curl механизм чтоб посылал запрос)
  20.  
  21.         $url = (is_array($d) && !empty($d['url'])) ? $d['url'] : $d;
  22.         // Если $d это массив (как в случае с пост), то достаем из массива url
  23.         // если это не массив, а уже ссылка - то берем сразу ссылку
  24.  
  25.         curl_setopt($curls[$id], CURLOPT_URL,            $url);
  26.     curl_setopt($curls[$id], CURLOPT_HEADER,         0);
  27.     curl_setopt($curls[$id], CURLOPT_RETURNTRANSFER, 1);
  28.  
  29.     // Если у нас есть пост данные, тоесть запрос отправляется постом
  30.         // устанавливаем флаги и добавляем сами данные
  31.     if (is_array($d) && !empty($d['post']))
  32.         {
  33.         curl_setopt($curls[$id], CURLOPT_POST,       1);
  34.         curl_setopt($curls[$id], CURLOPT_POSTFIELDS, $d['post']);
  35.     }
  36.  
  37.  
  38.     // Если указали дополнительные параметры $options то устанавливаем их
  39.         // смотри документацию функции curl_setopt_array
  40.     if (count($options)>0) curl_setopt_array($curls[$id], $options);
  41.  
  42.         // добавляем текущий механизм к числу работающих параллельно
  43.     curl_multi_add_handle($mh, $curls[$id]);
  44.   }
  45.  
  46.   // число работающих процессов.
  47.   $running = null;
  48.  
  49.   // curl_mult_exec запишет в переменную running количество еще не завершившихся
  50.   // процессов. Пока они есть - продолжаем выполнять запросы.
  51.   do { curl_multi_exec($mh, $running); } while($running > 0);
  52.  
  53.   // Собираем из всех созданных механизмов результаты, а сами механизмы удаляем
  54.   foreach($curls as $id => $c)
  55.   {
  56.     $result[$id] = curl_multi_getcontent($c);
  57.     curl_multi_remove_handle($mh, $c);
  58.   }
  59.  
  60.   // Освобождаем память от механизма мультипотоков
  61.  
  62.   // возвращаем данные собранные из всех потоков.
  63.   return $result;
  64. }
  65.  


Источники:
http://curl[dot]haxx[dot]se/libcurl/c/cu[dot][dot][dot]lti_perform[dot]html
http://www.phpied[dot]com/simultaneu[dot][dot][dot]n-php-with-curl/
4. Delovoy - 10 Января, 2011 - 12:17:39 - перейти к сообщению
Есть вопрос по уроку!

исходный код
PHP:
скопировать код в буфер обмена
  1. // инициализация сеанса
  2.             $ch = curl_init();
  3.  
  4.             // установка URL и других необходимых параметров
  5.             curl_setopt($ch, CURLOPT_URL, $_POST["texturl"]);
  6.            
  7.             //curl_setopt($ch, CURLOPT_HEADER, 0);
  8.             curl_setopt($ch, CURLOPT_NOPROGRESS, 0);
  9.  
  10.             // загрузка страницы и выдача её браузеру
  11.             curl_exec($ch);
  12.  
  13.             // завершение сеанса и освобождение ресурсов
  14.             curl_close($ch);


вроде все просто, как в примере
но результат ..никакой
браузер задумывается секунд на 20 ... и все - ничего не происходит !
а должен был загрузить страницу и отобразить, насколько я понимаю?
5. Delovoy - 10 Января, 2011 - 15:24:53 - перейти к сообщению
А вопросы установки cURL в этой теме рассматриваются ?
может дело не в самом коде ..а установленно неверно, каких-то библиотек не хватает ?

поменял несколько URL для чистоты эксперемента - получил сообщение
Fatal error: Maximum execution time of 30 seconds exceeded ...
6. Мелкий - 10 Января, 2011 - 18:46:14 - перейти к сообщению
Если бы нехватало библиотек, то был бы вызов неизвестной функции. Или вообще нестарт сервера.

По какому урлу обращаетесь и передаётся ли он вообще с формы?
7. Delovoy - 11 Января, 2011 - 09:50:08 - перейти к сообщению
спасибо за ответ Улыбка
вопрос решил уже - пишу как, может кому-то сгодится Улыбка

PHP:
скопировать код в буфер обмена
  1.  curl_setopt($ch, CURLOPT_PROXY, '10.224.100.2 : 18080');


у нас стоит прокси сервер! и если через браузер (в котором прокси указан в настройках) мы переходим на внешние сайты без проблем, то для cURL нужно было указывать отдельно!
8. Novi4ok - 29 Марта, 2011 - 06:48:14 - перейти к сообщению
Вопрос перенес сюда http://forum.php.su/topic.php?fo...08172#1301408172
9. flisk - 20 Апреля, 2011 - 18:49:14 - перейти к сообщению
Подскажите пожалуйста, а вот как проверить, валидные данные или нет? В плане, куда и что возвращает курл в случае неудачной авторизации? Можно ли как то сделать функцию, вида .if eax==верное значение, то делать то
.else (значение неверное) - делать другое. еах тут условно, это место куда функция возвращает значение.
Php знаю очень плохо, поэтому извиняюсь заранее, если вопрос очень глупый. По форуму искал, но ничего толком не нашел.
з.ы. если в курле это невозможно, то подскажите с помощью чего еще можно проверить, можно ли с помощью определенных данных авторизоваться на сайте, или нет.
10. shpiz - 23 Октября, 2012 - 10:36:52 - перейти к сообщению
Подскажите пожалуйста, как быть если надо подключиться к soap-сервису при помощи curl по протоколу https используя сертификат? (wsdl описание есть)

Заранее спасибо)

 

Powered by ExBB FM 1.0 RC1