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.

Jakub Vrána, Seznámení s oblastí, 3.6.2011, diskuse: 36 (nové: 0)

Diskuse

ikona The Zero:

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.

ikona Jakub Vrána OpenID:

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ě.

ikona The Zero:

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 ;)

ikona Jakub Vrána OpenID:

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…

ikona Jakub Vrána OpenID:

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...

ikona Jakub Vrána OpenID:

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ší.

ikona Filip Procházka:

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);
?>

ikona Jakub Bouček:

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.

Andrew:

"Já jsem nikdy oblíbenost této knihovny nechápal a pokud vím, tak jsem ji snad ani nikdy nepoužil. ..."

:-) Vida a já si tuto knihovnu oblíbil právě díky tobě:
http://php.vrana.cz/paralelni-zpracovani.php
Nicméně je zajímavé, že jsi ji nikdy nepoužil, ale pro jistotu jsi nad ní napsal svoji knihovnu:
http://php.vrana.cz/asynchronni-dotazy-v-curl.php

ikona Jakub Vrána OpenID:

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.

Dundee:

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

ikona Jakub Vrána OpenID:

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?

ikona David Grudl OpenID:

CURLOPT_FOLLOWLOCATION není možné povolit, pokud je aktivní safe_mode nebo open_basedir.

Vojta:

Ahoj, není CURL podstatně rychlejší?

ikona Jakub Vrána OpenID:

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

ikona Jakub Vrána OpenID:

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%.

ikona Ondřej Mirtes:

Ve firmě používáme HTTP rozšíření pro PHP (http://www.php.net/manual/en/book.http.php). Půl království a server k tomu, pokud mi někdo řekne, jak ho zprovoznit na Windows :)

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.

ikona 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.

ikona 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.

ikona Jakub Vrána OpenID:

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.

SEO Konzultant:

http://www.php.net/manual/en/context.socket.php
Version    Description
5.1.0     Added bindto.
5.3.3     Added backlog.
http://php.net/manual/en/function.stream-context-create.php
5.3.0     Added the optional params argument.
http://www.php.net/manual/en/context.http.php
PHP prior to 5.3.0 does not implement chunked transfer decoding. If this value is set to 1.1 it is your responsibility to be 1.1 compliant.
http://www.php.net/manual/en/function.stream-…-callback.php
(PHP 5 >= 5.2.0)

Atd. Prostě jsem tím ještě před nějakou dobu nemohl udělat to co jsem chtěl (a nebo možná mohl, ale ne nějak triviálně jako s CURLem).

Každopádně teď postupně dávám odevšad ten CURL pryč, právě kvůli tomu zmíněnému neřešitelnému problému s open basedir (a náš hosting jej má žel bohu nastaven a nejde to změnit)

ikona Jakub Vrána OpenID:

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

ikona Jakub Vrána OpenID:

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.

avatar © 2005-2024 Jakub Vrána. Publikované texty můžete přetiskovat pouze se svolením autora. Ukázky kódu smíte používat s uvedením autora a URL tohoto webu bez dalších omezení Creative Commons. Můžeme si tykat. Skripty předpokládají nastavení: magic_quotes_gpc=Off, magic_quotes_runtime=Off, error_reporting=E_ALL & ~E_NOTICE a očekávají předchozí zavolání mysql_set_charset. Skripty by měly být funkční v PHP >= 4.3 a PHP >= 5.0.