PHP “php: // input” проти $ _POST


243

Мене спрямовують використовувати метод php://inputзамість того, $_POSTщоб взаємодіяти з запитами Ajax від JQuery. Що я не розумію, це переваги використання цього проти глобального методу $_POSTабо $_GET.


2
Раніше я використовував "хаки" для прийому дзвінків Ajax на стороні PHP, перш ніж натрапляти на цей пост і читати його приголомшливі відповіді! Для інших людей, які матимуть те саме питання у майбутньому, я сподіваюся, що пошукові системи теж прочитають мій коментар! :)
— aderchox

Відповіді:


484

Причина полягає в тому, що php://inputповертає всі необроблені дані після HTTP-заголовків запиту, незалежно від типу вмісту.

Суперглобальний PHP $_POST, тільки повинен містити дані, які є будь-якими

  • application/x-www-form-urlencoded (стандартний тип вмісту для простих форм-повідомлень) або
  • multipart/form-data (в основному використовується для завантаження файлів)

Це тому, що це єдині типи вмісту, які повинні підтримувати користувальницькі агенти . Тож сервер і PHP традиційно не очікують отримання іншого типу контенту (це не означає, що вони не могли).

Отже, якщо ви просто розмістіть старий хороший HTML-код form, запит виглядає приблизно так:

POST /page.php HTTP/1.1

key1=value1&key2=value2&key3=value3

Але якщо ви багато працюєте з Ajax, ця ймовірність включає також обмін більш складними даними з типами (string, int, bool) та структурами (масиви, об'єкти), тому в більшості випадків JSON - найкращий вибір. Але запит з корисним навантаженням JSON виглядатиме приблизно так:

POST /page.php HTTP/1.1

{"key1":"value1","key2":"value2","key3":"value3"}

Вміст тепер буде application/json(або принаймні жодним із вищезгаданих), тому PHP- $_POSTwrapper не знає, як з цим впоратися.

Дані все ще є, ви просто не можете отримати доступ до них через обгортку. Тож вам потрібно його самостійно отримати в сирому форматі file_get_contents('php://input')(до тих пір, поки це не multipart/form-data-кодовано ).

Це також спосіб доступу до XML-даних або будь-якого іншого нестандартного типу вмісту.


40
+1 для "Це також, як ви отримаєте доступ до XML-даних або будь-якого іншого нестандартного типу вмісту"
— мандза

@Quasdank Я надсилаю JSON з програми Android на сервер php xampp у хмарі ( stackoverflow.com/questions/36558261/… ), але я не зміг змусити його працювати, коли спробував file_get_contents ('php: // input'), який просто повертає рядок (0). Це працювало в моїй локальній машині, але воно не працює, коли я розгортав його в хмару. Ви можете мені допомогти, будь ласка?
— The_Martian

1
Варто зазначити, що використання об'єкта XMLHttpRequest у запиті AJAX до PHP не означає, що потрібно публікувати JSON. Це додаткові накладні витрати, але ваш клієнтський JavaScript може перетворити у формат application / x-www-form-urlencoded. Однак переклад може бути не чистим типом даних .
— Ентоні Ратлідж

Необхідно сказати, що межа двох визнаних типів контенту значною мірою є історичною. Ніщо не заважає PHP розпізнавати, тобто, application/jsonяк дійсне джерело даних для $_POSTмасиву. І навіть є опубліковані запити на саме таку підтримку.
— AnrDaemon

Привіт @quasdunk, будь ласка, допоможіть мені на цьому magento.stackexchange.com/questions/296960/…
— Nagaraju K

53

php://inputможе дати вам необроблені байти даних. Це корисно, якщо дані POSTed - це кодована JSON структура, що часто буває для запиту AJAX POST.

Ось функція робити саме це:

  /**
   * Returns the JSON encoded POST data, if any, as an object.
   * 
   * @return Object|null
   */
  private function retrieveJsonPostData()
  {
    // get the raw POST data
    $rawData = file_get_contents("php://input");

    // this returns null if not valid json
    return json_decode($rawData);
  }

$_POSTМасив є більш корисним , коли ви обробки даних ключ-значення з форми, представленої традиційним POST. Це працює лише в тому випадку, якщо дані POSTed зазвичай є розпізнаним форматом application/x-www-form-urlencoded(детальніше див. Http://www.w3.org/TR/html4/interact/forms.html#h-17.13.4 ).


7
Варто зазначити, що якщо ви перейдете trueяк другий параметр до json_decode, він поверне асоціативний масив.
— Вахід Амірі

28

Якщо дані публікації неправильно сформовані, $ _POST нічого не міститиме. Тим не менш, вхід php: // матиме неправильно сформовану рядок.

Наприклад, є деякі програми ajax, які не формують правильну послідовність розміщення ключа-значення для завантаження файлу, а просто скидають весь файл у вигляді даних публікації, без імен змінних чи нічого. $ _POST буде порожнім, $ _FILES також порожнім, а вхід php: // буде містити точний файл, записаний у вигляді рядка.


22

По-перше, основна правда про PHP.

PHP не розрахований на те, щоб явно надати вам такий REST (GET, POST, PUT, PATCH, DELETE), як інтерфейс для обробки HTTP-запитів .

Однак $_POST, $_GETі $_FILES суперглобального , а функція filter_input_array()дуже корисні для потреб середньої людини / непрофесіонала.

Прихованою перевагою номер 1 $_POST(і $_GET) є те, що PHP автоматично вводить ваші вхідні дані автоматично . Ви навіть не замислюєтеся про те, щоб це зробити, особливо для параметрів рядка запиту в стандартному GET-запиті.

Однак потім ви дізнаєтесь більше ...

Зважаючи на це, коли ви просуваєтесь у своїх знаннях з програмування та хочете використовувати XmlHttpRequestоб’єкт JavaScript (jQuery для деяких), ви побачите обмеження цієї схеми.

$_POSTобмежує використання двох типів носія у Content-Typeзаголовку HTTP :

  1. application/x-www-form-urlencoded, і
  2. multipart/form-data

Таким чином, якщо ви хочете відправити значення даних на PHP на сервері, і вони відображаються в $_POSTсуперглобалі , вам потрібно urlencode їх на стороні клієнта і надіслати ці дані як пари ключів / значень - це незручний крок для новачків (особливо, намагаючись з’ясувати, чи потребують різних частин URL-адреси різні форми кодування urlencoding: звичайні, необроблені тощо).

Для всіх користувачів jQuery $.ajax()метод перетворює ваш JSON в закодовані URL-адреси пари / значення, перш ніж передавати їх серверу. Ви можете змінити цю поведінку, встановивши processData: false. Просто прочитайте документацію $ .ajax () і не забудьте надіслати правильний тип носія у заголовку Content-Type.

Кодування URL-адрес? Якого біса!!!???

Як правило, якщо ви робите звичайні, синхронні (коли вся сторінка перемальовує) запити HTTP із формою HTML, користувальницький агент (веб-браузер) перекодує ваші дані форми для вас. Якщо ви хочете робити асинхронні запити HTTP за допомогою XmlHttpRequestоб'єкта, вам слід створити рядок, що кодується urlencoded, і надіслати його, якщо ви хочете, щоб ці дані відображалися в $_POSTнадглобальному .

Як на зв’язку ви маєте JavaScript? :-)

Перетворення з масиву JavaScript або об'єкта в рядок, що кодується URL, турбує багатьох розробників (навіть з новими API, такими як Form Data ). Вони, скоріше, зможуть просто надіслати JSON, і це було б більш ефективно для клієнтського коду.

Пам'ятайте (підморгнути, підморгнути), середній веб-розробник не вчиться XmlHttpRequestбезпосередньо використовувати об’єкт, глобальні функції, функції рядка, функції масиву та регулярні вирази, як ви і я ;-). Урленкодування для них - кошмар. ;-)

PHP, що дає?

Відсутність PHP у інтуїтивно зрозумілому керуванні XML та JSON багатьох людей відключає. Ви б могли подумати, що зараз це буде частиною PHP (зітхаючи).

Стільки типів мультимедіа (типи MIME в минулому)

XML, JSON і YAML мають всі типи носіїв, які можна помістити в Content-Typeзаголовк HTTP .

  • додаток / xml
  • applicaiton / json
  • application / yaml (хоча IANA не має офіційного позначення у списку)

Подивіться, скільки типів медіа (раніше типів MIME) визначено IANA.

Подивіться, скільки є заголовків HTTP .

php: // вхід чи бюст

Використання php://inputпотоку дозволяє обійти рівень сидіння / тримання за дитину абстракції, який PHP змусив у світі. :-) З великою силою приходить велика відповідальність!

Тепер, перш ніж розібратися зі значеннями даних, що передаються через php://input, ви повинні / повинні зробити кілька речей.

  1. Визначте, чи вказано правильний метод HTTP (GET, POST, PUT, PATCH, DELETE, ...)
  2. Визначте, чи передано заголовок типу HTTP Content Content .
  3. Визначте, чи є значення типу Content-Type потрібним типом медіа.
  4. Визначте, чи добре надіслані дані XML / JSON / YMAL / тощо.
  5. При необхідності перетворіть дані у тип даних PHP: масив чи об’єкт.
  6. Якщо будь-яка з цих основних перевірок чи конверсій не вдається, киньте виняток !

А як щодо кодування символів?

AH, HA! Так, можливо, ви хочете, щоб потік даних, що надсилається у вашу заявку, був закодований UTF-8, але як ви можете дізнатися, є він чи ні?

Дві критичні проблеми.

  1. Ви не знаєте, скільки даних надходить php://input.
  2. Ви точно не знаєте поточного кодування потоку даних.

Чи збираєтесь ви намагатися обробляти дані потоку, не знаючи, скільки спочатку? Це жахлива ідея . Ви не можете покладатися виключно на HTTP- Content-Lengthзаголовок для вказівки щодо розміру потокового входу, оскільки він може бути підробленим.

Вам знадобиться:

  1. Алгоритм виявлення розміру потоку.
  2. Обмеження розміру потоку, визначені додатком (обмеження Apache / Nginx / PHP можуть бути занадто широкими).

Чи збираєтесь ви намагатися перетворити дані потоку в UTF-8, не знаючи поточного кодування потоку? Як? Фільтр потокового потоку iconv ( приклад фільтра потокового потоку iconv ), схоже, бажає кодування, що починається і закінчується, як це.

'convert.iconv.ISO-8859-1/UTF-8'

Таким чином, якщо ви сумлінні, вам знадобляться:

  1. Алгоритм виявлення кодування потоку.
  2. Алгоритм визначення фільтрів потоку динамічного / поточного часу (оскільки апріорі ви не можете знати початкове кодування).

( Оновлення : 'convert.iconv.UTF-8/UTF-8'примусить усе до UTF-8, але вам все одно доведеться враховувати символи, які бібліотека iconv може не знати, як перекладати. Іншими словами, ви повинні дещо визначити, яку дію зробити, коли символ неможливо перекласти : 1) Вставте персонаж манекена, 2) Провал / кидок та виняток).

Ви не можете покладатися виключно на HTTP- Content-Encodingзаголовок, оскільки це може означати щось на зразок стиснення, як у наступному. Це не те, про що ви хочете прийняти рішення щодо ікони.

Content-Encoding: gzip

Тому загальними кроками можуть бути ...

Частина I: Пов’язаний із запитом HTTP

  1. Визначте, чи вказано правильний метод HTTP (GET, POST, PUT, PATCH, DELETE, ...)
  2. Визначте, чи передано заголовок типу HTTP Content Content .
  3. Визначте, чи є значення типу Content-Type потрібним типом медіа.

Частина II: Поточні дані

  1. Визначте розмір вхідного потоку (необов’язково, але рекомендується).
  2. Визначте кодування вхідного потоку.
  3. При необхідності перетворіть вхідний потік у потрібне кодування символів (UTF-8).
  4. При необхідності скасувати чи стиснення чи шифрування будь-якого рівня програми, а потім повторити кроки 4, 5 і 6.

Частина III: Пов'язані з типом даних

  1. Визначте, чи добре надіслані дані XML / JSON / YMAL / тощо.

(Пам'ятайте, що дані все ще можуть бути кодованою URL-адресою, яку необхідно потім розібрати та розшифрувати URL).

  1. При необхідності перетворіть дані у тип даних PHP: масив чи об’єкт.

Частина IV: Пов'язані зі значенням даних

  1. Фільтруйте вхідні дані.

  2. Перевірка вхідних даних.

Тепер ти бачиш?

$_POSTСуперглобальний, поряд з php.ini настройки для обмеження на вхід, простіше для обивателя. Однак робота з кодуванням символів набагато інтуїтивніша та ефективніша при використанні потоків, оскільки немає необхідності прокручувати суперглобали (або масиви, як правило), щоб перевірити вхідні значення на правильне кодування.


1
Ух ти! Ця відповідь повинна мати набагато вищий рейтинг. Дякую вам велике за те, що ви принесли світло темного потоку.
— Локс

Зрештою, PHP добре би оновив базові параметри. Однак логічно помилково поєднувати метод запиту HTTP із однойменною структурою даних ($ _GET, $ _POST). Важливо те, що (1) бажаний метод запиту HTTP та (2) чи є дані запиту з цим запитом (Content-Type). Отже, як і в Perl, ви бачите, що ви можете бути бажаною жертвою думок творців / підтримувачів мови.
— Ентоні Рутлідж

0

Тому я написав функцію, яка б отримувала дані POST з потоку вводу php: // .

Таким чином, завдання тут було переключитись на метод запиту PUT, DELETE OR PATCH і все одно отримати дані про публікацію, надіслані з цим запитом.

Я поділяю це можливо для когось із подібним викликом. Функція нижче - це те, що я придумав, і воно працює. Я сподіваюся, що це допомагає!

    /**
     * @method Post getPostData
     * @return array
     * 
     * Convert Content-Disposition to a post data
     */
    function getPostData() : array
    {
        // @var string $input
        $input = file_get_contents('php://input');

        // continue if $_POST is empty
        if (strlen($input) > 0 && count($_POST) == 0 || count($_POST) > 0) :

            $postsize = "---".sha1(strlen($input))."---";

            preg_match_all('/([-]{2,})([^\s]+)[\n|\s]{0,}/', $input, $match);

            // update input
            if (count($match) > 0) $input = preg_replace('/([-]{2,})([^\s]+)[\n|\s]{0,}/', '', $input);

            // extract the content-disposition
            preg_match_all("/(Content-Disposition: form-data; name=)+(.*)/m", $input, $matches);

            // let's get the keys
            if (count($matches) > 0 && count($matches[0]) > 0)
            {
                $keys = $matches[2];

                foreach ($keys as $index => $key) :
                    $key = trim($key);
                    $key = preg_replace('/^["]/','',$key);
                    $key = preg_replace('/["]$/','',$key);
                    $key = preg_replace('/[\s]/','',$key);
                    $keys[$index] = $key;
                endforeach;

                $input = preg_replace("/(Content-Disposition: form-data; name=)+(.*)/m", $postsize, $input);

                $input = preg_replace("/(Content-Length: )+([^\n]+)/im", '', $input);

                // now let's get key value
                $inputArr = explode($postsize, $input);

                // @var array $values
                $values = [];

                foreach ($inputArr as $index => $val) :
                    $val = preg_replace('/[\n]/','',$val);

                    if (preg_match('/[\S]/', $val)) $values[$index] = trim($val);

                endforeach;

                // now combine the key to the values
                $post = [];

                // @var array $value
                $value = [];

                // update value
                foreach ($values as $i => $val) $value[] = $val;

                // push to post
                foreach ($keys as $x => $key) $post[$key] = isset($value[$x]) ? $value[$x] : '';

                if (is_array($post)) :

                    $newPost = [];

                    foreach ($post as $key => $val) :

                        if (preg_match('/[\[]/', $key)) :

                            $k = substr($key, 0, strpos($key, '['));
                            $child = substr($key, strpos($key, '['));
                            $child = preg_replace('/[\[|\]]/','', $child);
                            $newPost[$k][$child] = $val;

                        else:

                            $newPost[$key] = $val;

                        endif;

                    endforeach;

                    $_POST = count($newPost) > 0 ? $newPost : $post;

                endif;
            }

        endif;

        // return post array
        return $_POST;
    }

-5

Простий приклад того, як ним користуватися

 <?php  
     if(!isset($_POST) || empty($_POST)) { 
     ?> 
        <form name="form1" method="post" action=""> 
          <input type="text" name="textfield"><br /> 
          <input type="submit" name="Submit" value="submit"> 
        </form> 
   <?php  
        } else { 
        $example = file_get_contents("php://input");
        echo $example;  }  
   ?>
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.