Alternativa ke knihovně cURL
Školení, která pořádám
Knihovna cURL je poměrně populární. Řada PHP aplikací ji používá v případě potřeby komunikace pomocí protokolu HTTP. Já jsem nikdy oblíbenost této knihovny nechápal a pokud vím, tak jsem ji snad ani nikdy nepoužil. PHP totiž nabízí mnohem jednodušší způsob, jak tuto komunikaci provádět.
<?php
// cURL
$curl = curl_init("https://php.vrana.cz/");
curl_setopt($curl, CURLOPT_RETURNTRANSFER, true);
$page = curl_exec($curl);
curl_close($curl);
// samotné PHP
$page = file_get_contents("https://php.vrana.cz/");
?>
Druhá možnost je k dispozici, pokud je zapnutá konfigurační direktiva allow_url_fopen
, kterou od vyčlenění allow_url_include
není důvod zakazovat.
Možná si řeknete, že se cURL používá kvůli složitějším obratům. Ale třeba i takové nastavení hlavičky, poslání dat metodou POST a zpracování příchozích hlaviček není v PHP nijak složité. Spíš bych řekl, že je pořád jednodušší – hlavně kvůli strukturovanějšímu API. V cURL se prakticky všechno zajišťuje pomocí voleb – a projít všech 106 voleb a vybrat z nich tu správnou, dá přece jen docela práce.
<?php
// cURL
$curl = curl_init("https://php.vrana.cz/");
curl_setopt_array($curl, array(
CURLOPT_POST => true,
CURLOPT_COOKIE => 'a=1',
CURLOPT_POSTFIELDS => array('test' => 'a'),
CURLOPT_HEADER => true,
CURLOPT_NOBODY => true,
CURLOPT_RETURNTRANSFER => true,
));
$headers = explode("\r\n", rtrim(curl_exec($curl)));
curl_close($curl);
// samotné PHP
$context = stream_context_create(array('http' => array(
'method' => 'POST',
'header' => 'Cookie: a=1',
'content' => http_build_query(array('test' => 'a')),
)));
$fp = fopen("https://php.vrana.cz/", "r", false, $context);
$meta = stream_get_meta_data($fp);
$headers = $meta["wrapper_data"];
fclose($fp);
?>
cURL přinejmenším ve verzi pro Windows také dramaticky zpomaluje spuštění PHP (i v aktuální verzi PHP 5.3.6).
<?php
$start = microtime(true);
dl("php_curl.dll");
echo (microtime(true) - $start) . " s\n"; // 0.868 s
?>
Tam snad musí být nějaký sleep… Při použití ve webovém serveru to nijak zvlášť nevadí (protože v případě modulu nebo FastCGI se PHP moc často nenahrává), při spouštění z příkazové to vadí dost. Nejpatrnější je to v případě, kdy se sada testů spouští kvůli izolaci v samostatných procesech – na nahrání PHP se potom u každého testu čeká znovu. Pokud tedy ve společném php.ini
máte řádek extension = php_curl.dll
, tak ho z něj přemístěte někam, kde ho najde jen webový server. Při spuštění z příkazového řádku lze potom použít funkci dl.
Diskuse
Rychlé hledání prozradí... http://bugs.php.net/bug.php?id=50410 opraveno v 5.3.5, v 5.3.6 se zase objevilo, ach jo :)
Ve snapshotu už je to ok.
A druhý příklad s uploadem souboru mi tedy s cURL přijde asi o 150% přehlednější.
Proč se vůbec cURL používá? Nejspíš proto, že stejně v každém větším projektu/frameworku nakonec bude nějaký wrapper a cURL toho umí nejvíc, je tak logickou volbou pro trpaslíka schovaného v krabičce s názvem \Http\Request.
Takhle napsané to vypadá asi přehledně, ale hledat každou z těch voleb mezi 105 dalšími dá dost práce. Třeba zapamatovat si rozdíl mezi CURLOPT_HEADER a CURLOPT_HTTPHEADER je pro mě téměř nemožné, navíc mi s tím ani nijak nepomůže tooltip v editoru (jako u normálních funkcí).
API PHP se mi proto v praxi používá líp, protože je pestřejší a více strukturované – jednotlivé obraty se mi tedy snáze hledají i pamatují.
Největší rozdíl ale možná nakonec vidím v tom, že nemám rád závislosti. A závislost na defaultně povolené konfigurační direktivě je prostě menší než na defaultně zakázané knihovně.
Trochu Tě poškádlím s mým Eclipse: http://img.thezero.info/curlhint.png
Ale nefunguje to automaticky, (jen curl_setopt má jako tooltip nepoužitelně dlouhý výčet všech možností), nicméně dá se tam dopsat vlastní dokumentace i pro interní funkce/konstanty. Takže teoreticky to jde i v IDE ;)
Ono by to podobně šlo i ve SciTE, ale jak píšeš – není to běžné. Navíc CURLOPT_HTTPHEADER a CURLOPT_HEADER začíná na H jen shodou náhod.
Michal Wiglasz:
Ten druhý příklad v PHP se mi jeví skoro nečitelný. Vytvoří se nějaká proměnná $context, která se nikde nepoužije, a na pohled ani nevím, jestli to nastavení (cookie, hlavičky) platí jenom pro tento fopen nebo pro všechny, co budou následovat…
Ajaj, použití $context mi nějak vypadlo. Díky za upozornění, doplnil jsem to.
starenka:
A cookie jar? Gzip? Apod? Nepíše se pak zbytečně něco, co už jednou (a třeba i líp) bylo napsaný? Já osobně oblibuju curl kvuli tomu, protože ho používám dost masivně i v konzoli . Jen myho pul Eura...
CookieJar může být užitečné, ale ukládání do souboru mi je dost otravné a ve většině případů také zbytečné (a někdy dokonce úplně nepoužitelné). Lépe by mi vyhovovalo ukládání do paměti, pro což by se mi abstrakce snadněji napsala v PHP.
Nevím přesně, co myslíš Gzipem, ale <?php copy("compress.zlib://http://php.vrana.cz/php-triky.tgz", "php-triky.tar"); ?> je podle mě nejjednodušší možný přístup. Funguje i při poslání hlavičky Accept-Encoding: gzip.
starenka:
Na těch koláčkách něco je, ten soubor má ale svoje použití (exportnu si z browseru).
Gzipem myslím, že se pošle header a curl mi to sám rozbalí, ať už to je gzipovaný, nebo neni. Nejsem si jistej, jak ten tvuj kód jednoduše roubovat na request. Musel bych přidat hlavičku a pak ručne odgzipnout...
Já curl nijak neidalizuju, jen jsem chtěl poukázat na to, že sice má zilion switchů, ale ve složitějších případech je IMO výslednej kód o dost srozumitelnější a asi i udržovatelnější.
Pro Nette Framework jsem psal wrapper na cURL, snad nebude vadit odkaz a přepis tvého příkladu: http://addons.nette.org/cs/curl-wrapper
<?php // cURL-wrapper
$request = CurlRequest("http://php.vrana.cz/");
$headers = $request
->setOptions(array('cookie' => 'a=1', 'nobody' => TRUE))
->post(array('test' => 'a'))
->getHeaders();
?>
Uvědomil jsem si, že na podstrkávání sušenek mi chybí hezčí API. S tím ještě něco udělám.
A taky jsi zapomněl předat $context;
<?php
$fp = fopen("http://php.vrana.cz/", "r", FALSE, $context);
?>
Také se mi cURL nelíbí. Pokud již nepracuji s frameworkem, který by mi komunikaci zajistil, pak používám knihovnu Snoopy: http://sourceforge.net/projects/snoopy/
Nevýhodou je, že je zastaralá a projekt mrtvý. Nicméně mohu ovlivnit úplně všechno.
Pravda, asynchronní dotazy mohou být užitečné, ale v praxi jsem je také nikdy nepoužil. Jen jsem si s tím tak hrál, ta knihovna byl jenom takový pokus – asynchronní dotazy v HTTP, MySQL, ještě jsem měl v plánu myslím něco.
Andrew:
Tak já to využil už hodněkrát - standardně i asynchronně. Co si vzpomínám, tak pomocí cURL mám psanou AJAX proxy (ale ta je triviální a zvládla by se i bez). Dále jsem ji využil při integraci dvou systémů jako webovou proxy (a tam už to vyžadovalo o poznání sofistikovanější nastavení) a naposledy při volání externích systémů s dlouhým časem zpracování, kde jsem využil právě asynchronní dotazy.
To vše bych dokázal napsat i bez cURL, ale s mnohem větším úsilím.
Jednodušší? U toho jednoduchého GETu bych souhlasil, ale ten POST požadavek mi v podání čistého PHP přijde podstatně složitější a nečitelnější.
Adam:
Chtel bych se zeptat, jestli je tohle mozne pouzit pro "znovuodeslani formulare" pres POST?? Nastinim situaci: Data je treba nejdrive ulozit do databaze a az pote odeslat na externi URL ke zpracovani.
Zkousel jsem to pres cURL, ale mel jsem s tim problemy, potrebuji totiz aby externi skript na ktery odesilam data, mohl presmerovat uzivatele na svuj vystup a to mi nejak nefungovalo(tusim, ze zatim byl safemode??)
Nakonec jsem to vyresil pres vygenerovani noveho formulare s hidden inputy a odeslanim pres js hned po zobrazeni, ale to se mi opravdu nelibi, uz jenom kvuli tomu javascriptu, ktery je mozne vypnout a jsem v haji:)
Nebude zde take problem s tim presmerovanim? Diky za pomoc
Ano, dalo by se to k tomu použít. Ale přesměrování uživatele neprovede externí skript, ale můj kód. Dá se to udělat tak, že volbou max_redirects (http://www.php.net/manual/en/context.http.php) se vypne přesměrování, zachytí se hlavička Location a ta se přepošle uživateli.
Totéž by šlo ale udělat i s cURL, žádného omezení kvůli safe_mode si nejsem vědom.
Adam:
Tak jsem na to koukal do dokumentace a je tam direktiva follow_location, defaultne nastavena na true, nemelo by to stacit? Pokud totiz odchytin tu hlavicku odpovedi a presmeruji na ni, tak si nejsem jisty, jestli mi to udela co chci..Skript na ktery odesilam data uzivatele presmerovava pouze pri neuspesne validaci udaju, jinak zobrazi formular pro vlozeni osobnich udaju, tzn ze kdyz pote dam header("Location :...") tak externi skript tento pozadavek vyhodnoti jako "spatny" a pak bude presmerovavat na chybovou URL vzdy, nebo se mylim?
CURLOPT_FOLLOWLOCATION není možné povolit, pokud je aktivní safe_mode nebo open_basedir.
Vojta:
Ahoj, není CURL podstatně rychlejší?
Podle rychlého testu je to zcela srovnatelné – jednou vyjde rychlejší jedno, podruhé druhé. Záleží podle mě hlavně na momentální propustnosti sítě.
<?php
$start = microtime(true);
for ($i=0; $i < 1e2; $i++) {
$curl = curl_init("http://www.google.cz/");
curl_setopt($curl, CURLOPT_RETURNTRANSFER, true);
$page = curl_exec($curl);
curl_close($curl);
}
echo (microtime(true) - $start) . " s\n";
?>
Poprvé 7.577 s, podruhé 7.752 s.
<?php
$start = microtime(true);
for ($i=0; $i < 1e2; $i++) {
$page = file_get_contents("http://www.google.cz/");
}
echo (microtime(true) - $start) . " s\n";
?>
Poprvé 7.630 s, podruhé 7.595 s.
luki:
ten test podla mna o nicom nehovori, pretoze najdlhsi cas trva naviazanie spojenia a samotna odpoved od googlu... zaujimavejsie by to bolo keby sa to skusilo voci localhostu, ale aj potom by sa podla mna neprejavili nejake meratelne rozdiely oboch rieseni, ktore budu niekde na urovni micro sekund...
okrem toho si myslim, ze curl je pohodlnejsi na pouzitie, prave vdaka pouzitiu volieb.. a ked neni nejaka konstanta dobre dokumentovana v php tak staci dokumentaciu od libcurl
Cílem testu bylo ukázat, jestli nějaké řešení zásadně nezpomaluje. Pokud je v praxi nebudu pouštět na localhost, tak je nemá smysl proti localhostu ani testovat. Když komunikace s externím serverem trvá minimálně 0,1 s a cURL si k tomu přidá 1 ms a PHP 2 ms (nebo naopak), tak výměnou řešení neprodělám 100%, jak by se mohlo zdát, ale jen 1%.
kukulich:
Vzhledem k tomu, že jsem tohle rozšíření naučil kluky z původního Jyxa já a jako Windowsákovi mi bez problémů léta funguje, tak nevím, kde máš problém.
Mělo by stačit stáhnout si správnou verzi rozšíření z
http://downloads.php.net/pierre/.
Jinak jednu dobu se mluvilo o tom, že se tohle rozšíření stane součástí standardního PHP, ale zatím k tomu bohužel nedošlo.
Ondřej Mirtes:
Snažil jsem se to bez úspěchu sehnat zkompilované i zkompilovat, na tuhle stránku jsem nenarazil. Díky, snad to funguje na libovolné 5.3 :)
lopata:
Jojo, tohle rozšíření je výborné, a nově ve verzi 2.0 už http extenze bude mít pro php 5.3+ namespace "http", HttpRequest bývá dost často v konfliktu. Http extenze myslím umí i spolupracovat s curl.
The Zero:
Trochu jinak, HttpRequest je přímo objektový wrapper cURL.
v6ak:
Kdysi jsem na to použil Zend_Http_Client. I to mi přišlo celkem použitelné, ačkoli bych to navrhl trošku jinak. Ale může to být v některých případech trošku nepříjemné nahrávat celý Zend jen kvůli přístupu na HTTP a tehdy bych možná použil i toto, kdybych to znal.
SEO Konzultant:
Jakube, je to jednoduché - to co píšeš je totiž možné jen posledních pár měsíců (rok?) - spousta možností je tam až od 5.2+. Takže ti co s PHP dělají leta prostě museli CURL používat a ti ostatní se to učí od těch zkušených. Příklad za všechny je následování rewritnutých URL.
Osobně jsem jednu svou CURL funkci před pár měsíci přepisoval. CURL má totiž ohromnou nevýhodu/chybu, že když je nastavena nějaká basedir (nebo safe mode), tak neumí udělat followlocation.
Jakub Vrána :
Jak stream_context_create(), tak stream_get_meta_data() je k dispozici od PHP 4.3, http_build_query() od PHP 5, ale ve starších verzích se dá celkem snadno nahradit pomocí urlencode(). Takže netuším, co jde podle tebe až od PHP 5.2.
Jakub Vrána :
To jsou všechno věci, bez kterých jsem se dokázal bez problémů obejít. Když se vrátím k původní námitce: obraty použité v článků jde bez problémů použít už 7 let, s mírnou úpravou dokonce 8,5.
SEO Konzultant:
ale já netvrdím, že základní věci nebylo takto možné dělat před 7 lety, já tvrdím, že pokročilé věci nebylo možné dělat předloni. A protože mnoho lidí používá raději nějaké univerzální řešení, tak možná proto používali CURL, alespoň tak tomu bylo u mne
Jakub Vrána :
Reagoval jsem na „to co píšeš je totiž možné jen posledních pár měsíců“.
ronda:
dobrý den, mám potíž s curl, ale vůbec nevím co to je a tak ani co s tím
Řeším problém s TMNF a freezone pluginem.
Mám server do kterého když přidám freezone plugin a spustím aseco. načte všechy pluginy a nakonec vypíše chybu "freezone-plugin neds the curl php extension
Poraďte prosím laikovi co s tím. Jsem z toho i z Googlení hotový.
Předem moc děkuji
Diskuse je zrušena z důvodu spamu.