PHP

индекс
206,76

Отправить POST через file_get_contents()

Чтобы получить содержимое веб-страницы все с удовольствием используют file_get_contents(), например file_get_contents('http://www.habrahabr.ru/'). Но я уже давно наблюдаю, что, как дело доходит до того, чтобы отправить POST, разработчики используют либо CURL, либо открывают сокеты. Я не считаю, что это плохо или что не надо так делать, просто для решения простых задач можно использовать простые решения.

Я и сам так раньше делал, пока на наткнулся на понятие контекстов потоковых операций в PHP. Контекст позволяет передать дополнительные параметры потоковому обработчику. Для http например, можно сконфигурировать POST-запрос или передать дополнительные заголовки.

file_get_contents() принимает 3 параметром «контекст», который собственно и конфигурирует сам запрос.
Ниже пример такого запроса или RTFM



<?php
error_reporting(E_ALL);
require_once 'simpletest/unit_tester.php';
require_once 'simpletest/default_reporter.php';

define('PARAM_NAME',  'var');
define('PARAM_VALUE''testData');
define('QUERY',       'var=testData');

/**
 * Набор тестов
 */
class FileGetContentsTest extends UnitTestCase {

    /**
     * Проверить, что пришел POST
     */
    public function testIsPost() {
        $this->assertEqual('POST', $_SERVER['REQUEST_METHOD'],
            'Expected POST request');
        $this->assertTrue(isset($_POST[PARAM_NAME]) && $_POST[PARAM_NAME] == PARAM_VALUE,
            'Expected POST contains ' . QUERY);
    }
}

/**
 * Отправить POST
 */
if (!$_SERVER['QUERY_STRING']) {

    // Создать контекст и инициализировать POST запрос
    $context = stream_context_create(array(
        'http' => array(
            'method' => 'POST',
            'header' => 'Content-Type: application/x-www-form-urlencoded' . PHP_EOL,
            'content' => QUERY,
        ),
    ));

    // Отправить запрос на себя, чтобы запустить тесты
    // и показать результат выполнения тестов
    echo file_get_contents(
        $file = "http://{$_SERVER['HTTP_HOST']}{$_SERVER['PHP_SELF']}?runTests",
        $use_include_path = false,
        $context);

/**
 * Запустить тесты
 */
} else {
    $suite = new FileGetContentsTest;
    $suite->run(new DefaultReporter());
}

* This source code was highlighted with Source Code Highlighter.


Следуюшие файловые функции принимают контексты:
  • file
  • fopen
  • readfile
  • file_get_contents
  • file_put_contents


_________
Текст подготовлен в ХабраРедакторе
+81
11 января 2009, 10:32
152

комментарии (70)

–2
zerobrain #
Статья хороша, правда возникает такое ощущение, что код выхвачен как-то случайно. Раз уж выкладывать пример, дак такой, где и модули есть оставшиеся, и тег ?> закрыт. А в целом все позитивно :)
–1
zerobrain #
Да, и еще неплохо запрос формировать через http_build_query, но это уже мелочи. Спасибо за статью
НЛО прилетело и опубликовало эту надпись здесь
+1
Partizan #
Simpletest, см. require
–2
Kastrulya #
отсутствие тега ?> добавляет некую загадку,… чувство незавершенности статьи, значит следует ждать продолжения… романтика млин=)
+9
G0rDi #
Если закрывать ?> потом будешь искать кто в хедер данные шлет до тебя )) очень часты запар. Так что лучше не закрывать…
+5
kovshenin #
Даже разработчики Zend Framework не рекомендуют закрывать тег ?>
0
dals #
И в стандартах кодирования, принятых в CodeIgniter, черным по белому таки написано: не ставить закрывающий "?>".
+1
wayly #
Интересно, сколько еще народу скажет, что "?>" ставить не рекомендуется и подкрепит свои слова разными примерами из жизни? :-D
+7
Unixspv #
Неоднократно сталкивался с подобными задачами, однако, помимо банального POST'а часто еще нужно отправить и Cookie, или вообще файл. А вообще, имхо, CURL, а уж тем более сокеты гораздо лучше в плане того, что позволяют разобраться лучше в принципах работы HTTP-протокола, ну и естественно имеют значительно больше возможностей.
+1
Partizan #
Это просто еще один удобный инструмент.
–1
Unixspv #
Сомнительное удобство…
0
bolk #
Cookie отправить элементарно — просто указывается ещё один заголовок. С файлом проблемы, я хорошего способа не знаю, кроме как сделать всё руками.
0
homm #
Приемлимый способ заключается в использовании настоящего курла:
curl -c cookies.jar -b cookies.jar -F subno=@file.txt
0
bolk #
Я рассуждаю в рамках статьи.
0
homm #
Простите,
exec('curl -c cookies.jar -b cookies.jar -F subno=@file.txt')
Вот теперь в рамках статьи :)
0
bolk #
:))
В PHP всё-таки есть модуль CURL, один из самых старых.
+3
maxic #
Совершенно верно.

Плюс еще модифицировать user-agent или referer, поэтому имхо я считаю, правильным то, что универсальное.
«Своим» file_get_contents вы возьмете контент только на 50% сайтов, что недопустимо мало.
Через сокеты 99%, что закончится успехом.

«Это» нельзя даже предлагать как одно из решений.
Работа через file_get_contents с удаленными данными — это Зло.
Да и вообще, почаще заглядывайте на php.net, чтобы не писать такие топики ;)

Имхо я пользуюсь сокетами, это более универсальное решение.
НЛО прилетело и опубликовало эту надпись здесь
0
wayly #
Ну так по такой логике и контексты потока использовать — слишком низкий уровень. Нет? =)
+2
kay #
решение работает только на серверах с fopen = true, и с PHP >= 5. в совокупности таких около 40%
поэтому для универсальных решений используют curl.
+1
crocodile2u #
не просто fopen, а allow_url_fopen
0
pwlnw #
Есть только одно оправдание разработчику не использующему curl — массовый дешевый коммерческий продукт, который реально инсталлируют тысячи на всевозможных кривущих хостингах. Вы именно такой пишите?
–1
kay #
имхо всё совсем наоборот. универсальное массовое решение должно как минимум год-два поддерживать версию PHP 4.x. увы, дешевые hosting провайдеры очень медленно переходят на пятёрку.
–1
ecl #
это вы о чем? дешевые хостинг провайдеры берут не качеством, а количеством, поэтому им наиболее важно написать кучу плюсов(а это и php5), но работать не качественно(саппорт).
0
vittore #
к сожалению даже на 1&1 четверка стоит
0
Lux_In_Tenebris #
да ну? :)
faq.1and1.com/scripting_languages_supported/php/9.html

мне вообще не попадались хостеры среди более-менее популярных, не поддерживающие PHP5 тем или иным способом
0
bolk #
Контексты в пятёрке появились, если не путаю.
+10
ferrari #
>>Чтобы получить содержимое веб-страницы все с удовольствием используют file_get_contents()
наглая ложь :) cURL для того и сделан, чтобы было легче жить, все что нужно уже сделано, cookies например.

ps: я не против концепции ооп, но когда вы даете пример примитивной операции — не нужно его заворачивать в свои лишние методы — это напрягает, приходится бегать глазами и вытаскивать строчки. Донесите до читателя смысл 3-4мя строчками вместо класса и пары инклюдов, а он уже сам решит где и как у себя это применить(ведь маловероятно, что он начнет применять ваш класс у себя).
Тяжело читать статьи, когда код большой и совершенно не адаптирован для быстрого восприятия.
RTFM данный вами в тысячу раз понятнее — угадайте почему :)
+1
Partizan #
Согласен. Просто добавил пару тестов — уже разучился доверять коду без тестирования.
+6
ha2bj #
Ну как на хабре без понта? ))
+4
homm #
Как мне показалось, ничуть не проще курла. Тем более когда послдний знаешь :)
–1
Joka #
быстрее имхо курлом это сделать, а может мне просто курлом привычнее… от этого и быстрее
+2
zerkms #
как-то вы странно тестируете код.
один из столпов модульного тестирование — изоляция. т.е. тесты не должны зависеть ни от чего снаружи. все действия по созданию рабочей среды должны производиться в фикстурах, а по воспроизведению тестовых прецедентов — в тестовых методах.
как-то так
0
Partizan #
В соответствии с задачей: быстро, дешево и в одном файле
0
zerkms #
«правильно» (см. по общепринятым стандартам) написанный тест был бы нисколько не больше по числу строк и занял бы ни капли не больше времени.
+1
TeamOut #
как показала практика file_get_contents жутко тормознутый в сравнении с курлом
+2
Mastyf #
Дабы не создавать отдельную тему… Есть замечание насчет file_get_contents…

По мануалу «It will use memory mapping techniques if supported by your OS to enhance performance. »

НО:

php -r '$data = file_get_contents("/media/STORAGE/Downloads/ubuntu-8.10-desktop-i386.iso");'
PHP Fatal error: Allowed memory size of 134217728 bytes exhausted (tried to allocate 732774400 bytes) in Command line code on line 1

То есть он пытается весь файл загнать в оперативку и ни о каком memory mapping речи и нет…
Система Ubuntu 8.10 i686

Может mmap где-то отдельно включается или я не понимаю его сути? Может кто разъяснить?
0
alexxxst #
Все логично — вы же в переменную присваиваете, а вы, допустим, ехайте его?
0
pwlnw #
чего с ним делать?

Нет, на первый взгляд все работает как обещано.
Файл хоть и помещается в общую виртуальную кучу php, на самом деле содержимое не обязательно в нее считывается. Это можно узнать запустив через strace. Вот я запустил:

open("/usr/lib/vmware/isoimages/linux.iso", O_RDONLY|O_LARGEFILE) = 3
fstat64(3, {st_mode=S_IFREG|0444, st_size=34963456, ...}) = 0
_llseek(3, 0, [0], SEEK_CUR) = 0
fstat64(3, {st_mode=S_IFREG|0444, st_size=34963456, ...}) = 0
mmap2(NULL, 34963456, PROT_READ, MAP_SHARED, 3, 0) = 0xb5197000
mmap2(NULL, 35131392, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = 0xb3016000
munmap(0xb5197000, 34963456) = 0
close(3) = 0
0
Mastyf #
Это тоже было моей первой мыслью, но если пробовать без "$data =" — все совершенно тоже самое.
0
enartemy #
Конечно, file_get_contents запихивает все содержимое файла в оперативку. Для этого оно и было придумало — получить одной строкой все содержимое файла или url. Не нравится — самое первое, что приходит в голову: fopen, fread, fwrite, fclose. Они для подобных целей и были сделаны.
0
pwlnw #
Да не запихивает. Смотрите трассировку системных вызовов чуть выше.

Поделитесь лучше идеями как хранить в разделяемой памяти не только строки, но и произвольные структуры языка.
Это даст возможность появиться очень интересным оптимизациям в вебе до сих пор не имеющим аналогов.
0
garex #
Вы не указали, что это доступно только начиная с 5.1.х

+2
kay #
с версии 5.0
цитирую php.net/manual/en/function.file-get-contents.php:
5.0.0 Added context support.
–1
alexxxst #
И еще, народ используется file_get_contents() из-за минимализма кода (в четверке использовали $var = implode("", file("/path/to/file")) и прочие конструкции), а если для отправки постом нужна такая оберкта, то, ИМХО, гораздо проще курл использовать… (да и возможностей в нем всяко больше)
0
panandy #
А я пользуюсь PEAR:HTTP_Request и очень доволен
Там все очень просто, разобратся можно очень быстро.
Вот пример:

<?php
require 'HTTP/Request.php'
$http = new HTTP_Request('http://habrahabr.ru');
$http->setMethod(HTTP_REQUEST_METHOD_POST);
$http->addPostData('test', 1);

$http->sendRequest();
$http->getResponseBody(); //тело ответа
$http->getResponseCode()
0
panandy #
Хабр заглючил — отправилось раньше
<?php
require 'HTTP/Request.php'
$http = new HTTP_Request('http://habrahabr.ru');
$http->setMethod(HTTP_REQUEST_METHOD_POST);
$http->addPostData('test', 1);

$http->sendRequest();
$http->getResponseBody(); //тело ответа
$http->getResponseCode(); //код ответа
$http->getResponseHeader(); //хидеры
$http->getResponseCookies(); //куки
+1
jandosul #
не хидеры а хэдеры! :)
0
panandy #
пусть будет так )
0
DYPA #
+3
Kallisto #
Зачем столько кода, показывать какой ты крутой умеешь писать под рнр5 паблики приваты… когда основная идея лежит в 3м параметре функции… про него и надо было просто написать.

На хабре люди не дураки, свой класс почти каждый в состоянии написать… а разбираться в чужом дело не сильно классное.

0
LDEV #
Сделал интересное открытие для себя этим контекстом. Но практичнее CURL в модуле, дабы подсунуть Referer, Cookies, etc.
0
nmike #
реферер и куки — это header's
0
LDEV #
куки подсовываются указателем на файл — и парсить не надо, и соблюдать RFC — куда практичнее, чем велосипеды.
+2
alekz #
PHP_EOL в конце заголовка (Content-Type) — дурь какая-то.
+1
crocodile2u #
Просто не все читают спецификации.
0
kolpeex #
Просто можно было вместо
>>> 'Content-Type: application/x-www-form-urlencoded'. PHP_EOL
написать
>>> «Content-Type: application/x-www-form-urlencoded\r\n»
0
Partizan #
Когда удалил второй заголовок, забыл убрать EOL.
Сейчас открыл RFC,- и правда — CRLF. Как снес windows уже отвык от \r\n.
+3
enartemy #
Иногда возникает такое чувство, что разработчики PHP придерживаются некой догмы, согласно которой одно и тоже может быть реализовано как минимум 3-мя различными способами. Это конечно здорово, только не всегда понятно зачем это нужно.

Я думаю так, вот был раньше file_get_contents, внутри которого при обращении к url этот саный контекст создавался. И вот в новой версии разработчики решили — а зачем это мы будем генерить свой контекст, если можно сделать так, что б использовался внешний. Сказано сделано — ввели еще один не обязательный параметр. Т.е. наличие данной особенности не потому что она нужна, а потому что она легко реалтизуется. И может кому-нибудь пригодится. И все.

CURL-то конечно удобнее.
0
patt #
CURL в одну строчку контент не получишь, бывают моменты, когда нужно что то сделать быстро.
0
akral #
А вот это:
$context = stream_context_create(array(
'http' => array(
'method' => 'POST',
'header' => 'Content-Type: application/x-www-form-urlencoded' . PHP_EOL,
'content' => QUERY,
),
));

// Отправить запрос на себя, чтобы запустить тесты
// и показать результат выполнения тестов
echo file_get_contents(
$file = "http://{$_SERVER['HTTP_HOST']}{$_SERVER['PHP_SELF']}?runTests",
$use_include_path = false,
$context);

Одна строчка?
0
Lux_In_Tenebris #
Прям 1000 и 1 способ собрать велосипед… :)

Мне всегда хватало libcurl для всевозможных HTTP запросов, к тому же он знает, что такое таймауты. Единственный только его минус — не умеет хранить куки в памяти, как например тот же libwww в Perl. Или может в данном направлении прогресс уже произошёл?
0
nmike #
> к тому же он знает, что такое таймауты
docs.php.net/manual/ru/context.http.php — timeout там присутствует.

> не умеет хранить куки в памяти
это как? чтобы их каждый раз не указывать при запросе? не понял просто.
0
Lux_In_Tenebris #
В оригинале у libcurl поболее всяких таймаутов поддерживает, но не уверен что их все в PHP включили.
curl.haxx.se/libcurl/c/curl_easy_setopt.html

Да нет, с куки не всё так плохо, указывать вручную ничего не надо. :) Но храниться они могут только в текстовом файле, что зачастую в общем-то нафиг нужно.
0
Lux_In_Tenebris #
Но храниться они могут только в текстовом файле, что зачастую в общем-то нафиг нужно.

я малость поумнел с тех пор и догнал, что куки хранятся в памяти, если в качестве CURLOPT_COOKIEJAR указать пустую строку :)
+1
axshavan #
Как говорит один мой умный знакомый, можно и отвертку вместо молотка использовать, главное — бить сильно и метко.
–1
phpdude #
я бы не стал так слать пост. file_get_contents… и где тут слово пост?… уверен что и через реад файл и через фопен можно делать посты, НО ЗАЧЕМ?… чушь. дурь. хрень. нахуй.
0
nmike #
блин, вот ведь лень до чего доводит! читать надо маны, читать.

спасибо автор.
+1
YaakovTooth #
Ёлки-палки! Два месяца использую PHP в подобном роде. Всё время рисовал свои грабли через сокеты. Вчера увидел про CURL, решил что буду гуглить его, и вот — зарегестрировался на хабре и прозрел, каким был идиотом.

Мануалы читать надо! :)

Автору гигантский респект за наставление на пусть истинный. Только что -1.2кб кода и +80 к читаемости. :)

Только зарегистрированные пользователи могут оставлять комментарии. Войдите, пожалуйста.