Alternativa ke knihovně cURL
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.
3.6.2011 02:11:15
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ě.
3.6.2011 10:30:15
Trochu Tě poškádlím s mým Eclipse:
http://img.thezero.info/curlhint.pngAle 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 ;)
3.6.2011 13:49:56
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.
3.6.2011 13:55:11
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…
3.6.2011 02:18:22
Ajaj, použití $context mi nějak vypadlo. Díky za upozornění, doplnil jsem to.
3.6.2011 10:14:05
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...
3.6.2011 02:54:42
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.
3.6.2011 11:12:46
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ší.
3.6.2011 16:39:41
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);
?>
3.6.2011 04:34:51
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.
3.6.2011 05:38:38
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.
3.6.2011 10:18:51
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.
3.6.2011 10:53:05
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ší.
3.6.2011 10:46:18
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
3.6.2011 11:41:00
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.
3.6.2011 11:56:28
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?
3.6.2011 14:29:00
CURLOPT_FOLLOWLOCATION není možné povolit, pokud je aktivní safe_mode nebo open_basedir.
6.3.2013 18:45:45
Vojta:
Ahoj, není CURL podstatně rychlejší?
3.6.2011 12:18:14
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.
3.6.2011 12:29:28
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
3.6.2011 23:42:43
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%.
6.6.2011 12:08:39
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.
3.6.2011 22:13:26
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 :)
3.6.2011 22:21:28
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.
4.6.2011 00:16:35
Trochu jinak, HttpRequest je přímo objektový wrapper cURL.
4.6.2011 04:34:58
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.
5.6.2011 12:31:50
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.
9.6.2011 12:45:13
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.
9.6.2011 13:12:13
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.
9.6.2011 15:11:52
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
9.6.2011 23:45:54
Reagoval jsem na „to co píšeš je totiž možné jen posledních pár měsíců“.
10.6.2011 12:47:30
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
26.8.2012 01:59:28