cURL: rozwiązanie problemu z CURLOPT_FOLLOWLOCATION

curllogocURL to biblioteka, tak zwany klient HTTP, z ogromnymi możliwościami. Jedną z nich jest możliwość podążania za przekierowaniami zawartymi w nagłówkach odpowiedzi serwera.
Służy do tego opcja CURLOPT_FOLLOWLOCATION, włącza się ja w następujący sposób:

curl_setopt ($ch, CURLOPT_FOLLOWLOCATION, true);

Niestety, jeśli na serwerze włączone są elementy zabezpieczeń serwera PHP: safe_mode lub open_basedir opcja ta jest niedostępna.
Próba jej użycia kończy się komunikatem:

PHP Warning: curl_setopt() [function.curl-setopt]: CURLOPT_FOLLOWLOCATION cannot be activated when in safe_mode or an open_basedir is set

PHP pozwala manipulować opcjami w trakcie działania skryptu poprzez funkcję ini_set, jednak ani ustawienia safe_mode ani open_basedir nie mogą być w ten sposób zmieniane.

W sieci znalazłem kilka przykładów rozwiązania problemu, ale żaden nie spełniał moich oczekiwań. Napisałem więc własną funkcję, tzw. wrapper funkcji curl_exec, która realizuje zadanie. Funkcja zachowuje się dokładnie tak, jak curl_exec, tzn. zwraca to samo przy tych samych parametrach.

/**
 * @param cURL  $ch                     - uchwyt do cURL
 * @param int   $redirects              - przekierowania
 * @param bool  $curlopt_returntransfer - CURLOPT_RETURNTRANSFER
 * @param int   $curlopt_maxredirs      - CURLOPT_MAXREDIRS
 * @param bool  $curlopt_header         - CURLOPT_HEADER
 * @return mixed
 */
function curl_redirect_exec($ch, &$redirects, $curlopt_returntransfer = false, $curlopt_maxredirs = 10, $curlopt_header = false) {
    curl_setopt($ch, CURLOPT_HEADER, true);
    curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
    $data = curl_exec($ch);
    $http_code = curl_getinfo($ch, CURLINFO_HTTP_CODE);
    $exceeded_max_redirects = $curlopt_maxredirs > $redirects;
    $exist_more_redirects = false;
    if ($http_code == 301 || $http_code == 302) {
        if ($exceeded_max_redirects) {
            list($header) = explode("\r\n\r\n", $data, 2);
            $matches = array();
            preg_match('/(Location:|URI:)(.*?)\n/', $header, $matches);
            $url = trim(array_pop($matches));
            $url_parsed = parse_url($url);
            if (isset($url_parsed)) {
                curl_setopt($ch, CURLOPT_URL, $url);
                $redirects++;
                return curl_redirect_exec($ch, $redirects, $curlopt_returntransfer, $curlopt_maxredirs, $curlopt_header);
            }
        }
        else {
            $exist_more_redirects = true;
        }
    }
    if ($data !== false) {
        if (!$curlopt_header)
            list(,$data) = explode("\r\n\r\n", $data, 2);
        if ($exist_more_redirects) return false;
        if ($curlopt_returntransfer) {
            return $data;
        }
        else {
            echo $data;
            if (curl_errno($ch) === 0) return true;
            else return false;
        }
    }
    else {
        return false;
    }
}

Przykłady użycia

Przedstawię dwa przykłady użycia funkcji. Pierwszy prosty, drugi pokazujący wszystkie możliwości.
Standardowe użycie cURL z opcją CURLOPT_FOLLOWLOCATION:

$ch = curl_init("http://08r.utnij.net");
curl_setopt ($ch, CURLOPT_FOLLOWLOCATION, true);
curl_exec($ch);
$info = curl_getinfo($ch);
echo "Aktualny adres: ".$info['url'].", wykonanych przekierowań: {$info['redirect_count']}\n";
curl_close($ch);

to samo zrealizowane z użyciem funkcji curl_redirect_exec:

$ch = curl_init("http://08r.utnij.net");
// deklarujemy i inicjujemy zmienną którą funkcja
// zwiększy o ilość wykonanych przekierowań
$redirects = 0;
// wykonujemy funckję curl_redirect_exec
// zamiast oryginalnej curl_exec
curl_redirect_exec($ch, $redirects);
$info = curl_getinfo($ch);
echo "Aktualny adres: ".$info['url'].", wykonanych przekierowań: $redirects\n";
curl_close($ch);

Użycie cURL z opcjami CURLOPT_FOLLOWLOCATION, CURLOPT_MAXREDIRS i CURLOPT_HEADER:

$ch = curl_init("http://08r.utnij.net");
curl_setopt ($ch, CURLOPT_FOLLOWLOCATION, true);
curl_setopt ($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt ($ch, CURLOPT_MAXREDIRS, 5);
curl_setopt ($ch, CURLOPT_HEADER, true);
curl_exec($ch);
$info = curl_getinfo($ch);
echo "Aktualny adres: {$info['url']}, wykonanych przekierowań: {$info['redirect_count']}\n";
if(curl_errno($ch)) echo "Error: ".curl_error($ch);
curl_close($ch);

to samo zrealizowane z użyciem funkcji curl_redirect_exec:

$ch = curl_init("http://08r.utnij.net");
$redirects = 0;
curl_redirect_exec($ch, $redirects, true, 5, true);
$info = curl_getinfo($ch);
echo "Aktualny adres: {$info['url']}, wykonanych przekierowań: $redirects\n";
if(curl_errno($ch)) echo "Error: ".curl_error($ch);
curl_close($ch);

Jeśli chcemy użyć któreś z opcji: CURLOPT_FOLLOWLOCATION, CURLOPT_MAXREDIRS lub CURLOPT_HEADER, podajemy je jako parametry funkcji curl_redirect_exec.

Znane ograniczenia

Ponieważ cURL nie jest natywną częścią języka PHP, nie na wszystko programista ma wpływ.

Ograniczenia:

  • Przy włączonej opcji CURLOPT_HEADER curl_exec zwróci nagłówki wszystkich przekierowujących stron, curl_redirect_exec zwróci tylko nagłówek ostatniej.
  • curl_redirect_exec nie wywoła błędu CURLE_TOO_MANY_REDIRECTS (47), ale tak jak curl_exec zwróci false.

Pliki oraz przykłady do pobrania: http://antczak.org/source/curl/curl_redirect_exec.zip

Fork me on GitHub

10 comments

  1. „cURL to biblioteka, tak zwany klient HTTP”
    sory ale skoro to jest PHP to ta nazwa bardziej się kojarzy z deczko innym „klijentem” (którego też nie testowałem ale sam link za siebie mówi – choć w sumie klasą 😉 ):
    http://pl.php.net/manual/en/book.http.php
    „…z ogromnymi możliwościami. …”
    to zależy, jak się klasę/funkcję napisze – curl jest kompromisem między oop i procedurą bym rzekł – aczkolwiek dla początkujących jest łatwiejszy niż inne rozwiązania 😉
    „…Jedną z nich jest możliwość podążania za przekierowaniami zawartymi w nagłówkach odpowiedzi serwera…”
    jestem bardziej hobbistą niż programistą 😉 – aczkolwiek z forum może mnie kojarzysz 😉 to jest i plus i moim zdaniem baardzo duży minus, gdyż wielu nie prubuje pomyśleć, a wystarczy zrobić podobne narzędzie do Twojego… inaczej, pasuje wiedzieć, jakie są odpowiedzi serwera chcący parsować bardziej skomplikowane strony/połaczenia… problem sprowadziłeś w tym temacie do kwestii choćby surowego i samego fsockpen – a wystarczy go obudować odpowiednią klasą 😉 – i cóż, jeśli w odpowiedzi przeparsujesz inną funkcj/klasą odpowiedź (same nagłówki w pierw) to wiesz co zrobić dalej, czy zdekompresować zawartość czy falow location… czy inne „pierdoły”… a w zasadzie Tutaj curl’a obudowałeś funkcją – więc równie dobrze mogłeś skorzystać z prostszych funkcji w sensie może uboższych aczkolwiek w „surowym transferze danych”
    „Przy włączonej opcji CURLOPT_HEADER curl_exec zwróci nagłówki wszystkich przekierowujących stron, curl_redirect_exec zwróci tylko nagłówek ostatniej”
    już nie pamiętam jak to dokładnie wygląda – zapewne to się „skumuluje” to wyświetlanie nagłówków – jak już wspomniałem nie jestem programistą a hobbistą i czasami grywam w różne gierki, jak utrudnić wykrycie korzystania z własnych pseudo programów – trzeba wysyłać odpowiednie nagłówki w zależności od sytuacji 😉 – tak więc liczenie n automatyczne FOLLOWLOCATION jest zgubne

    przepraszam, za komentarz który chyba nic nie wnosi do tematu gdyż jestem pod wpływem %-ów 😉 – są święta w końcu – miałem coś jeszcze napisać aczkolwiek mi z głowy wyleciało… przypomnę tylko jeszcze raz, że sam curl jest tylko kompromisem miedzy oop a procedurą i jest tylko więcej artykułów na jego temat ale w sumie on nie daje nic więcej niż fsockpen – a sama ta funkcja daje tylko tyle, że na strumieniu danych operuje się tak jak na pliku + troszku wiedzy, bo do samego strumienia sa też inne odpowiednie funkcje w php 😉

  2. ps. skoro masz utf-8 na stronie i zamieniane cytowanie to może zamień cytaty na polski otwierający i zamykający ;)?? – raczej mało pracy…

  3. Witam,
    Nie wiem co źle robię ale niestety usunięcie

    curl_setopt ($ch, CURLOPT_RETURNTRANSFER, true);

    powoduje że mój skrypt nie loguje się do pewnego serwisu.

    	$a = curl_init('site');
    	curl_setopt($a, CURLOPT_COOKIEFILE, 'cookie.txt');
    	curl_setopt($a, CURLOPT_COOKIEJAR, 'cookie.txt');
    	curl_setopt($a, CURLOPT_USERAGENT, 'User-Agent: Mozilla/5.0 (Windows; U; Windows NT 5.1; pl; rv:1.9.1.3) Gecko/20090824 Firefox/3.5.3');
    	curl_setopt($a, CURLOPT_POST, 1);
    	curl_setopt($a, CURLOPT_POSTFIELDS, '__submit__=1&email='.$login.'&pass='.$pass.'&remember=1');
    	$redirects = 0;
    	curl_redirect_exec($a, $redirects);
  4. Witam.
    Zamiast ręcznie ustawiać

    curl_setopt ($a, CURLOPT_RETURNTRANSFER, true);

    zmień wywołanie z

    curl_redirect_exec($a, $redirects);

    na

    $tresc = curl_redirect_exec($a, $redirects, true);

    W zaawansowanym przykładzie jest to użyte.

  5. Odnośnie komentarza zegarka84:

    1. Na czym polega kompromis pomiędzy OOP, a *INNYM* paradygmatem programowania w CURL-u? Wybacz, ale jakoś nie rzuca się to w oczy.
    2. Nie znam powodu wywodu, ale CURL ogólnie nie jest praktyczny – gdy przychodzi zastosować go w bardziej wymagających projektach ( + zawiera bugi ), wystarczyło krótko podsumować i nie marnować nikomu czasu na czytanie tego wszystkiego.
    3. Follow location to jedna z niewielu rzeczy która działa tam poprawnie. Za to korzystanie z hostingu który ustawia safe mode, już jest pograniczu zdrowego rozsądku.

    Pozdrawiam autora 😉

  6. Dzięki, za zajebista funkcje, dizeki niej zrozumialem dzialanie tego shitu.

    Thanks again sir

  7. niby działa, w sensie że po zmianie skrypt działa dalej, jednak przy użyciu fallow location w moim skrypcie (parser) dostaje ponad 1000 wyników, przy użyciu tej funkcji dostaje ich niecałe 200, i akurat niecałe 200 nie wymaga przekierowania, co za tym idzie wnioskuję że przekierowania w funkcji nie działają..

Dodaj komentarz

Twój adres email nie zostanie opublikowany. Pola, których wypełnienie jest wymagane, są oznaczone symbolem *