понедельник, 6 августа 2012 г.

PHP Sockets - отправка файла

Это продолжение записки "PHP модуль Sockets для HTTP запросов", здесь будет дополнение к ранее написанному и я не буду приводить весь код класса.

Уже при написании HttpSocket мне было интересно узнать, как составить HTTP заголовок для отправки файла. Поэтому я решил разобраться что и как, и дописать эту функциональность.

Как указано в RFC 1867, при отправке файла методом POST, заголовок Content-Type должен принять значение «multipart/form-data». Также Content-Type должен содержать определение boundary:

Content-Type: multipart/form-data; boundary=ABCuniq_idABC

Более того теперь тело запроса разделено на несколько частей начинающихся, разделённых и заканчивающихся строкой указанной в boundary. Каждая часть содержит свой «подзаголовок» и выглядит следующим образом:

--ABCuniq_idABC
Content-Disposition: form-data; name="var_name1"

value1
--ABCuniq_idABC
Content-Disposition: form-data; name="var_name2"

value2
--ABCuniq_idABC
Content-Disposition: form-data; name="img_file"; filename="IMG_20120305_102159.jpg"
Content-Type: image/jpeg

<содержимое файла>
--ABCuniq_idABC--

Обратите внимание, как к строке boundary добавляются символы «--».
Указанная информация о файле, в случае удачной отправки, на принимающей стороне может выглядеть примерно так:

$_FILES = Array(
[img_file] => Array(
[name] => IMG_20120305_102159.jpg
[type] => image/jpeg
[tmp_name] => C:\WINDOWS\Temp\php5DF.tmp
[error] => 0
[size] => 450055
)
)

В дополнение к сделанному, я думал что лучше будет отправлять файл закодированным в base64, чтобы данные не исказились, да и при выводе заголовка он выглядит лучше. Я исследовал такие поля как Content-Encoding, Transfer-Encoding и даже Content-Transfer-Encoding.

Как выяснилось, протокол HTTP принимает двоичные данные (содержимое файла) без проблем и кодирование не нужно. Поля Content-Encoding, Transfer-Encoding нужны соотвественно для сжатия и передачи содержимого по частям, а поле Content-Transfer-Encoding не поддерживается HTTP и используется в почтовых протоколах.

Если с заголовком всё понятно, то написание метода не создаст проблем. Но перед этим, одно «но»: на данный момент в PHP нет надёжного встроенного способа определить MIME-тип произвольного файла. Зато есть простой способ определить MIME-тип изображений. Поэтому я буду отправлять только изображения.

Получившийся у меня код метода класса HttpSocket:

public function reqPostFile($addr,
                            $var_arr = array(),
                            $file_path = null)
{
    $this->parseAddr($addr);
 
    $boundary = '>>============' . uniqid() . '<<';
    $req_body = '';
 
    foreach ($var_arr as $var_name => $var_val)
    {
        $req_body .= '--' . $boundary . NL;
        $req_body .= 'Content-Disposition: form-data;' .
            ' name="' . $var_name . '"' . DNL;
 
        $req_body .= $var_val . NL;
    }
 
    if (!is_null($file_path))
    {
        if (($mime = getimagesize($file_path)) !== false)
        {
            $mime = $mime['mime'];
 
            $req_body .= '--' . $boundary . NL;
            $req_body .= 'Content-Disposition: form-data;' .
                ' name="img_file";' .
                ' filename="' . basename($file_path) . '"' . NL;
 
            $req_body .= 'Content-Type: ' . $mime . DNL;
            $req_body .= file_get_contents($file_path) . NL;
 
        } else {
 
            trigger_error('Mime type of image 
                "' . $file_path . '" can`t be detected',
                E_USER_ERROR);
        }
    }
 
    $req_body .= '--' . $boundary . '--' . NL;
 
 
    $header = 'POST ' . $this->query_str . ' HTTP/1.1' . NL;
    $header .= 'Host: ' . $this->host_str . NL;
    $header .= 'Content-Type: multipart/form-data;' .
        ' boundary=' . $boundary . NL;
 
    $header .= 'Content-Length: ' . strlen($req_body) . NL;
    $header .= 'Connection: Close' . DNL;
 
    $header .= $req_body;
 
    return $this->doQuery($header);
}

Комментариев нет:

Отправить комментарий