• Strona główna
  • Curriculum Vitae
  • O mnie
  • Mapa strony
  • Kontakt
Niebieski Pomarańczowy Zielony Różowy Fioletowy

Jak pobierać zewnętrzne zasoby?

Opublikowane 9 sierpnia 2010. Autor: Kamil Brenk. Wizyt: 1 784.

Kategorie: PHP
Tematyka: Apache, benchmark php, optymalizacja serwisów, PHP, PHP PEAR, programowane ciekawostki, Protokół HTTP, Web Services w PHP, wydajność serwisów internetowych

sie 09

Często dochodzi do konieczności pobrania zewnętrznych zasobów, zwłaszcza w przypadku korzystania z różnych usług sieciowych (tj. REST, XML-RPC). Czasem nawet musimy pobrać kod całej strony, np. Google, by wyszukać na której pozycji znajduje się nasza strona.

Do operacji tej lepiej jest użyć cURL, file_get_contents, fopen czy może fsockopen? Jeśli też interesuje Cię problem wydajności każdego rozwiązania to zapraszam do niniejszego artykułu, w którym prezentuję wyniki swoich testów.

Sposób przeprowadzania testu

Do testu wykorzystałem Benchmark z pakietu PEAR. Jest to sprawdzony i przyjemny w obsłudze benchmark, więc oszczędziłem sobie przy okazji trochę czasu.

Edit: test został wykonany z użyciem prostego benchmarku:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
function Benchmark($function, $iterations=1000) {
    set_time_limit(0);

    if (is_callable($function) === true) {
        $result = microtime(true);

        for ($i = 1; $i <= $iterations; $i++) {
            call_user_func_array($function, null);
        }

        return round(microtime(true) - $result, 8);
    }

    return false;
}

Testy zostały wykonane na: Windows XP, Apache 2.2.6, PHP 5.2.5.

file_get_contents

Najczęściej i najchętniej chyba wykorzystywana metoda. Popularność jak zwykle wynika z lenistwa programistów, czyli prostoty wykorzystania. Bowiem wystarczy pojedyncze wywołanie funkcji, by uzyskać kod pobieranego zasobu.

Kod pojedynczego wywołania wygląda następująco:

1
$page = file_get_contents('http://localhost/blog.kamilbrenk.pl/');

Prostota wykorzystania funkcji file_get_contents jest zarówno zaletą, jak i wadą – otóż nie mamy zbyt wielu możliwości formatowania żądania. Mimo iż można pobawić się trochę w nagłówkach, niewiele to raczej nam daje:

1
2
3
4
5
6
7
8
9
10
$http = array(
           'method' => 'POST',
           'header' => 'Content-Type: application/x-www-form-urlencoded'
        );

$page = file_get_contents(
            'http://localhost/blog.kamilbrenk.pl/',
            false,
            stream_context_create(array('http' => $http))
        );
fopen

Sposób podobny do file_get_contents, lecz wymaga włożenia trochę większego trudu do pobrania zawartości danej strony. Z tego powodu przez wielu nielubiana, a przynajmniej przeze mnie :-)

Oto wykorzystany kod testowy:

1
2
3
4
5
6
7
8
$handle = fopen("http://localhost/blog.kamilbrenk.pl/", "r");

$data = '';
while (!feof($handle)) {
    $data .= fread($handle, 4096);
}

fclose($handle);

Aby wykorzystywać funkcję fopen do otwierania zewnętrznych zasobów to w ustawieniach konfiguracyjnych PHP musimy ustawić allow_url_fopen na On oraz wyłączyć tryb bezpieczny (safe mode).

Również i tutaj możemy konfigurować własne nagłówki i kilka innych rzeczy, tj.:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
$http   = array(
           'method' => 'GET',
           'header' => 'Accept-language: pl\r\n' .
                       'Cookie: foo=bar\r\n'
        );
       
$fp     = fopen(
            'http://www.example.com',
            'r',
            false,
            stream_context_create(array('http' => $http))
        );
       
fpassthru($fp);
fclose($fp);
fsockopen

Dająca multum możliwości funkcja, lecz niestety również dość skomplikowana i niechętnie wykorzystywana – przynajmniej przez początkujących.

Niemniej jednak pozwala nawiązywać połączenia chyba każdego rodzaju (SSL, TLS dla TCP/IP).

Zasada jest prosta: łączymy się z podanym adresem (na wybranym porcie). Następnie przy pomocy funkcji fwrite dołączamy do otwartego połączenia wybrane przez nas nagłówki (odpowiednio sformatowane), po czym przy użyciu pętli odbieramy przesyłkę.

W praktyce może wyglądać to następująco:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
$fp = fsockopen("localhost", 80, $errno, $errstr, 30);
if(!$fp) {
    echo $errstr;
} else {
    fwrite($fp, "GET /blog.kamilbrenk.pl/ HTTP/1.0\r\n" .
                "Host: www.php.net\r\n" .
                "Connection: Close\r\n\r\n");
    $data = '';
    while (!feof($fp)) {
        $data .= fread($fp, 4096);
        if (substr($data, -9)=="\r\n\r\n0\r\n\r\n") {
            exit;
        }
    }
}

Jak widać, nie jest to zbyt piękne i przejrzyste rozwiązanie. Mimo wszystko będziesz miał często do czynienia z powyższym kodem, zwłaszcza przy korzystaniu z usług sieciowych.

Na potrzeby testów zrobiłem mały eksperyment – pobieram zawartość zarówno z wykorzystaniem protokołu HTTP w wersji 1.0, jak i HTTP 1.1.

Oto zmieniony kod dla wersji HTTP 1.1:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
$fp = fsockopen("localhost", 80, $errno, $errstr, 30);
if(!$fp) {
    echo $errstr;
} else {
    fwrite($fp, "GET /blog.kamilbrenk.pl/ HTTP/1.1\r\n" .
                "Host: www.php.net\r\n" .
                "Connection: Close\r\n\r\n");
    $data = '';
    while (!feof($fp)) {
        $data .= fread($fp, 4096);
        if (substr($data, -9)=="\r\n\r\n0\r\n\r\n") {
            exit;
        }
    }
}
cURL

I przejdźmy do ostatniego rozwiązania – biblioteki cURL. Jest to najbardziej uniwersalne i przydatne narzędzie, którego znajomość przyda się nam niejednokrotnie.

Z wykorzystaniem biblioteki możemy się logować na stronie, przechodzić między podstronami, otwierać wiele adresów na raz, modyfikować nagłówki – możemy zrobić niemal wszystko!

Oczywiście kosztem tylu możliwości jest trudniejsza konfiguracja, lecz tylko na początku, ponieważ obsługi biblioteki dość łatwo się nauczyć.

Na potrzeby naszego przykładu wykorzystamy taki oto prosty kod:

1
2
3
4
5
6
7
8
9
10
$ch = curl_init();
curl_setopt($ch, CURLOPT_URL, "http://localhost/blog.kamilbrenk.pl/");
curl_setopt($ch, CURLOPT_FAILONERROR, true);
curl_setopt($ch, CURLOPT_FOLLOWLOCATION, true);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_SSL_VERIFYHOST, false);
curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, false);
curl_setopt($ch, CURLOPT_USERAGENT, 'Mozilla/4.0 (compatible; MSIE 7.0; Windows NT 5.1)');
$output = curl_exec($ch);
curl_close($ch);

Analiza powyższego kodu nie powinna nikomu sprawić trudności (przynajmniej jeśli potrafi język angielski w stopniu podstawowym).

Jedyną wadą cURL, którą teraz dostrzegam, to konieczność instalacji dodatkowej biblioteki (niestety nie wszystkie jeszcze serwery mają ją wbudowaną).

Które rozwiązanie będzie najwydajniejsze?

Aby wyniki były bardziej wiarygodne, przeprowadziłem kilka prób tego testu (czas podawany w sekundach).

Metoda / requesty110100100010000
file_get_contents0.2235
(0.2235)
0.2078
(2.0785)
0.2029
(20.287)
0.1980
(198.052)
0.2555
(2554.93)
fopen0.2018
(0.2018)
0.1930
(1.9296)
0.1965
(19.6463)
0.1903
(190.343)
0.2866
(2865.57)
fsockopen – HTTP 1.00.2267
(0.2267)
0.1883
(1.8832)
0.1961
(19.6064)
0.2133
(213.30)
0.2460
(2460.17)
fsockopen – HTTP 1.10.1962
(0.1962)
0.1933
(1.9327)
0.2046
(20.4571)
0.2263
(226.324)
0.2294
(2293.68)
cURL0.1895
(0.1895)
0.1814
(1.8137)
0.1842
(18.4159)
0.2273
(227.325)
0.2314
(2314.05)
Konkluzje

Jak widać, najlepiej uplasował się tutaj cURL. Choć przy większej ilości wywołań jest już na gorszej pozycji, to posiada możliwość nawiązywania kilku połączeń (interfejs curl_multi), co jeszcze bardziej skraca czas połączeń (dzięki stosowaniu gniazd nieblokujących).

Pozostałe rozwiązania są czasem wolniejsze, czasem szybsze – nie bardzo rozumiem takiego wyniku i nie wiem z czego to wynika, trudno. Benchmark udostępniam w załączniku na dole strony, także każdy może indywidualnie przetestować poszczególne rozwiązania u siebie.

Najgorzej natomiast uplasowały się file_get_contents oraz fopen, który ma dodatkowo kilka ograniczeń.

Dla mnie najlepszym wyborem jest cURL i od teraz zamierzam posługiwać się głównie tą biblioteką. Choć nie widać wielkiej różnicy między tymi funkcjami w powyższym porównaniu, w praktyce stanowi to dużą różnicę (zwłaszcza przy konieczności częstego odwoływania się do zewnętrznych zasobów).

Dodatkowe materiały
  • Benchmark: cURL vs file_get_contents vs fopen vs fsockopen
  • Wyniki testów (xls; 16 KB)

Podobne wpisy

  • Pobieranie adresów URL z innej strony
  • PHP5. Zaawansowane programowanie
  • Kompresja JavaScript
  • Kompresja CSS
  • Kompresja w Apache (deflate)

Komentarze (7)

  1. eRIZ 10 sierpnia 2010

    IMHO takie porównywanie nie jest nie do końca miarodajne. Rozumiem, gdybyś odwoływaj się wszędzie do localhosta, a tak, to pozostaje kwestia obciążeń Sieci, którą ciężko tu zmierzyć… ;)

  2. Michal Wachowski 10 sierpnia 2010

    Porównanie na wyrost – trzeba by tak z 10000 razy zapytać localhosta…
    —
    Sam osobiście preferuję cURL’a. Jeszcze nie trafiłem na serwer gdzie nie był zainstalowany. Za to często trafiałem na serwery gdzie allow_url_fopen był false.

  3. Kamil Brenk 11 sierpnia 2010

    Macie racje Panowie. Tak myślałem, że odwoływanie się do stron w sieci nie jest zbyt miarodajne, jednak widziałem podobne benchmarki działające na podobnych zasadach i też tak zrobiłem – teraz już wiem, że to był błąd :-)

    Testy wykonałem ponownie, zwiększająca liczbę prób do 10.000 i odwołując się do localhosta. Mimo to jakieś dziwne wyniki powychodziły :D

  4. Michal Wachowski 12 sierpnia 2010

    Podziel uzyskane czasy, przez ilość requestów – łatwiej będzie porównywać.

  5. Kamil Brenk 12 sierpnia 2010

    Dodałem też czasy pojedynczych requestów :-) I tak mam trochę zmieszane uczucia do wyżej uzyskanych wyników, coś mi tam nie gra :D

  6. Piotrek 4 sierpnia 2011

    Przy tak niskich rzędach czasów nie jesteś w stanie przeprowadzić miarodajnych testów na własnej maszynie. Już zwykły hosting ze stałym gwarantowanym obciążeniem procesora będzie wystarczający. W tym przypadku odpadnie Ci również kwestia optymalizacji Linux’a i Apache’a.

    Poza tym nie sprawdzasz pamięci, a może być tak, że któraś metoda jest bardziej pamięciożerna niż procożerna i nie może rozwinąć skrzydeł przy zadanym limicie.

    Dobrym pomysłem przy pobieraniu wielu stron z jednego serwera jest pipelining. Trudno mi powiedzieć, czy cURL go wspiera.

  7. Kamil Brenk 5 sierpnia 2011

    @Piotrek: dziękuję za wartościowe uwagi :) wiem, że niezbyt przemyślałem swoje pomiary, to fakt.



Dodaj komentarz

XHTML: Możesz użyć następujących tagów
<a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <strike> <strong>

Kamil Brenk Blog

PHP, JavaScript, SQL, HTML

  • Informacje o blogu

    Kamil Brenk

    Blog o tworzeniu aplikacji na potrzeby sieci Web.

    Praktyczne przykłady, porady i sztuczki. PHP, SQL, AJAX, JavaScript, HTML i pochodne.

    Kanał RSS

    • Najnowsze
    • Komentarze
    • Popularne
    • Gramatyka w HTML i CSS
    • PHP kontra Microsoft Office, part I
    • Cross-Domain JavaScript: CORS
    • Wysyłanie wiadomości SMS w PHP
    • Boilerplate 2.0
    • Własne selektory w jQuery
    • Kamil Brenk: @Michał:1) jak już otrzymam dyplom to zrobię serię o...
    • Michal Wachowski: Po pierwsze - tyle czekania i tylko to? A bu! :) Po drugie -...
    • Kamil Brenk: @CapaciousCore: języki kompilowane są szybsze niż...
    • CapaciousCore: @Kamil Brenk wiem, że komentarze i post nie są uber świeże....
    • Kamil Brenk: @CapaciousCore: post i komentarze napisane ponad rok temu;...
    • CapaciousCore: Przebrnąłem przez te wszystkie komentarze i mam trochę...
    • Kamil Brenk: @arhiman: dzięki za komentarz :)A to dziwne co piszesz, bo...
    • Przyszłość PHP
    • Niestandardowe czcionki na stronie
    • Gramatyka w PHP, część 1
    • Umowa i zaliczka dla freelancera
    • Projekt aplikacji po stronie klienta
    • Własny mechanizm Feed
    • jQuery.extends dla PHP
  • Szukajka
    Wpisz co chcesz wyszukać na stronie…
  • Kategorie
    • Apache
    • Freelancer
    • Front-end Development
    • HTML5 & CSS3
    • Inne
    • JavaScript
    • Książki
    • PHP
    • Po godzinach
    • Pozycjonowanie
    • Protokół HTTP
    • SQL
    • Wyrażenia regularne
  • Moje serwisy
    • Testy zawodowe
    • Miłość, uczucia i seks
  • Czytane blogi
    • Wojciech Sznapka
    • Wojciech Soczyński
    • Michał Wachowski
    • Tomasz Kowalczyk
    • JavaScript po polsku | Code42
  • Archiwum
    • Luty 2012
    • Listopad 2011
    • Październik 2011
    • Wrzesień 2011
    • Sierpień 2011
    • Lipiec 2011
    • Maj 2011
    • Kwiecień 2011
    • Marzec 2011
    • Luty 2011
    • Styczeń 2011
    • Grudzień 2010
    • Listopad 2010
    • Październik 2010
    • Wrzesień 2010
    • Sierpień 2010
    • Lipiec 2010
    • Czerwiec 2010
    • Maj 2010
    • Kwiecień 2010
    • Marzec 2010
    • Luty 2010
    • Styczeń 2010
  • Strona główna
  • Curriculum Vitae
  • O mnie
  • Mapa strony
  • Kontakt

Kamil Brenk © 2010. All rights reserved.

Designed by FTL Wordpress Themes brought to you by Smashing Magazine.

Do góry ∧