PHP многочастной формы данных PUT-запрос? - PullRequest
11 голосов
/ 27 февраля 2012

Я пишу RESTful API.У меня проблемы с загрузкой изображений с использованием разных глаголов.

Учтите:

У меня есть объект, который можно создать / изменить / удалить / просмотреть с помощью сообщения / положить / удалить / получитьзапрос к URL.Запрос состоит из нескольких частей, когда есть файл для загрузки, или application / xml, когда есть только текст для обработки.

Для обработки загрузки изображений, связанных с объектом, который я делаю, что-то вроде:

    if(isset($_FILES['userfile'])) {
        $data = $this->image_model->upload_image();
        if($data['error']){
            $this->response(array('error' => $error['error']));
        }
        $xml_data = (array)simplexml_load_string( urldecode($_POST['xml']) );           
        $object = (array)$xml_data['object'];
    } else {
        $object = $this->body('object');
    }

Основная проблема здесь заключается в том, что при попытке обработать запрос на размещение, очевидно, $ _POST не содержит данных о размещении (насколько я могу судить!).

Для справки, вот как я строю запросы:

curl -F userfile=@./image.png -F xml="<xml><object>stuff to edit</object></xml>" 
  http://example.com/object -X PUT

У кого-нибудь есть идеи, как мне получить доступ к переменной xml в моем запросе PUT?

Ответы [ 3 ]

27 голосов
/ 27 февраля 2012

Прежде всего, $_FILES не заполняется при обработке запросов PUT. Он заполняется только PHP при обработке запросов POST.

Вам нужно разобрать его вручную. Это относится и к «обычным» полям:

// Fetch content and determine boundary
$raw_data = file_get_contents('php://input');
$boundary = substr($raw_data, 0, strpos($raw_data, "\r\n"));

// Fetch each part
$parts = array_slice(explode($boundary, $raw_data), 1);
$data = array();

foreach ($parts as $part) {
    // If this is the last part, break
    if ($part == "--\r\n") break; 

    // Separate content from headers
    $part = ltrim($part, "\r\n");
    list($raw_headers, $body) = explode("\r\n\r\n", $part, 2);

    // Parse the headers list
    $raw_headers = explode("\r\n", $raw_headers);
    $headers = array();
    foreach ($raw_headers as $header) {
        list($name, $value) = explode(':', $header);
        $headers[strtolower($name)] = ltrim($value, ' '); 
    } 

    // Parse the Content-Disposition to get the field name, etc.
    if (isset($headers['content-disposition'])) {
        $filename = null;
        preg_match(
            '/^(.+); *name="([^"]+)"(; *filename="([^"]+)")?/', 
            $headers['content-disposition'], 
            $matches
        );
        list(, $type, $name) = $matches;
        isset($matches[4]) and $filename = $matches[4]; 

        // handle your fields here
        switch ($name) {
            // this is a file upload
            case 'userfile':
                 file_put_contents($filename, $body);
                 break;

            // default for all other files is to populate $data
            default: 
                 $data[$name] = substr($body, 0, strlen($body) - 2);
                 break;
        } 
    }

}

На каждой итерации массив $data будет заполняться вашими параметрами, а массив $headers будет заполняться заголовками для каждой части (например, Content-Type и т. Д.), А $filename будет содержит исходное имя файла, если оно указано в запросе и применимо к полю.

Обратите внимание, что вышеперечисленное будет работать только для типов контента multipart. Обязательно проверьте заголовок запроса Content-Type, прежде чем использовать приведенное выше для анализа тела.

10 голосов
/ 08 сентября 2013

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

Это берет то, что было сказано выше, и дополнительно обрабатывает несколько загрузок файлов и помещает их в $ _FILES каккто-то мог бы ожидать.Чтобы заставить это работать, вы должны добавить 'Script PUT /put.php' к вашему виртуальному хосту для проекта в Документация .Я также подозреваю, что мне придется настроить cron для очистки любых файлов .tmp.

private function _parsePut(  )
{
    global $_PUT;

    /* PUT data comes in on the stdin stream */
    $putdata = fopen("php://input", "r");

    /* Open a file for writing */
    // $fp = fopen("myputfile.ext", "w");

    $raw_data = '';

    /* Read the data 1 KB at a time
       and write to the file */
    while ($chunk = fread($putdata, 1024))
        $raw_data .= $chunk;

    /* Close the streams */
    fclose($putdata);

    // Fetch content and determine boundary
    $boundary = substr($raw_data, 0, strpos($raw_data, "\r\n"));

    if(empty($boundary)){
        parse_str($raw_data,$data);
        $GLOBALS[ '_PUT' ] = $data;
        return;
    }

    // Fetch each part
    $parts = array_slice(explode($boundary, $raw_data), 1);
    $data = array();

    foreach ($parts as $part) {
        // If this is the last part, break
        if ($part == "--\r\n") break;

        // Separate content from headers
        $part = ltrim($part, "\r\n");
        list($raw_headers, $body) = explode("\r\n\r\n", $part, 2);

        // Parse the headers list
        $raw_headers = explode("\r\n", $raw_headers);
        $headers = array();
        foreach ($raw_headers as $header) {
            list($name, $value) = explode(':', $header);
            $headers[strtolower($name)] = ltrim($value, ' ');
        }

        // Parse the Content-Disposition to get the field name, etc.
        if (isset($headers['content-disposition'])) {
            $filename = null;
            $tmp_name = null;
            preg_match(
                '/^(.+); *name="([^"]+)"(; *filename="([^"]+)")?/',
                $headers['content-disposition'],
                $matches
            );
            list(, $type, $name) = $matches;

            //Parse File
            if( isset($matches[4]) )
            {
                //if labeled the same as previous, skip
                if( isset( $_FILES[ $matches[ 2 ] ] ) )
                {
                    continue;
                }

                //get filename
                $filename = $matches[4];

                //get tmp name
                $filename_parts = pathinfo( $filename );
                $tmp_name = tempnam( ini_get('upload_tmp_dir'), $filename_parts['filename']);

                //populate $_FILES with information, size may be off in multibyte situation
                $_FILES[ $matches[ 2 ] ] = array(
                    'error'=>0,
                    'name'=>$filename,
                    'tmp_name'=>$tmp_name,
                    'size'=>strlen( $body ),
                    'type'=>$value
                );

                //place in temporary directory
                file_put_contents($tmp_name, $body);
            }
            //Parse Field
            else
            {
                $data[$name] = substr($body, 0, strlen($body) - 2);
            }
        }

    }
    $GLOBALS[ '_PUT' ] = $data;
    return;
}
0 голосов
/ 10 августа 2012

Цитируя ответ netcoder: «Обратите внимание, что вышеперечисленное будет работать только для составных типов контента»

Для работы с любым типом контента я добавил следующие строки в решение Mr. netcoder:

   // Fetch content and determine boundary
   $raw_data = file_get_contents('php://input');
   $boundary = substr($raw_data, 0, strpos($raw_data, "\r\n"));

   /*...... My edit --------- */
    if(empty($boundary)){
        parse_str($raw_data,$data);
        return $data;
    }
   /* ........... My edit ends ......... */
    // Fetch each part
    $parts = array_slice(explode($boundary, $raw_data), 1);
    $data = array();
    ............
    ...............  
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...