Сделать запрос POST с большим файлом в теле для PHP 5.5+

Есть API, с которым мне нужно работать из моего PHP-приложения. Одна конечная точка получает файл для загрузки как тело запроса POST. Загруженный файл может быть довольно большим (до 25 ГБ). Конечная точка возвращает простой контент JSON с 200 OK или другие другие коды состояния.

Пример запроса может выглядеть так:

POST /api/upload HTTP/1.1
Host: <hostname>
Content-Type: application/octet-stream
Content-Length: 26843545600
Connection: close

<raw file data up to 25 GB>

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

Я пытался найти какую-либо разумную реализацию, но, насколько я вижу, как cURL, так и non-cURL (stream_context_create) методы требуют строка тело запроса, которое может исчерпать память сервера.

Есть ли простой способ добиться этого без написания отдельный сокет транспортного уровня?

1

Решение

Поскольку не было найдено лучших вариантов, я выбрал стандартное решение с fsockopen.

Вот полный исходный код функции утилиты, которая будет выполнять HTTP-запрос с низким потреблением памяти. Как data параметр, который он может принять string, array а также SplFileInfo объект.

/**
* Performs memory-safe HTTP request.
*
* @param string $url       Request URL, e.g. "https://example.com:23986/api/upload".
* @param string $method    Request method, e.g. "GET", "POST", "PATCH", etc.
* @param mixed $data       [optional] Data to pass with the request.
* @param array $headers    [optional] Additional headers.
*
* @return string           Response body.
*
* @throws Exception
*/
function request($url, $method, $data = null, array &$headers = []) {
static $schemes = [
'https' => ['ssl://', 443],
'http'  => ['', 80],
];

$u = parse_url($url);
if (!isset($u['host']) || !isset($u['scheme']) || !isset($schemes[$u['scheme']])) {
throw new Exception('URL parameter must be a valid URL.');
}

$scheme = $schemes[$u['scheme']];
if (isset($u['port'])) {
$scheme[1] = $u['port'];
}

$fp = @fsockopen($scheme[0] . $u['host'], $scheme[1], $errno, $errstr);
if ($fp === false) {
throw new Exception($errstr, $errno);
}

$uri = isset($u['path']) ? $u['path'] : '/';
if (isset($u['query'])) {
$uri .= '?' . $u['query'];
}

if (is_array($data)) {
$data = http_build_query($data);
$headers['Content-Type'] = 'application/x-www-form-urlencoded';
$headers['Content-Length'] = strlen($data);
} elseif ($data instanceof SplFileInfo) {
$headers['Content-Length'] = $data->getSize();
}

$headers['Host'] = $this->host;
$headers['Connection'] = 'close';

fwrite($fp, sprintf("%s /api%s HTTP/1.1\r\n", $method, $uri));
foreach ($headers as $header => $value) {
fwrite($fp, $header . ': ' . $value . "\r\n");
}
fwrite($fp, "\r\n");

if ($data instanceof SplFileInfo) {
$fh = fopen($data->getPathname(), 'rb');
while ($chunk = fread($fh, 4096)) {
fwrite($fp, $chunk);
}
fclose($fh);
} else {
fwrite($fp, $data);
}

$response = '';
while (!feof($fp)) {
$response .= fread($fp, 1024);
}
fclose($fp);

if (false === $pos = strpos($response, "\r\n\r\n")) {
throw new Exception('Bad server response body.');
}

$headers = explode("\r\n", substr($response, 0, $pos));
if (!isset($headers[0]) || strpos($headers[0], 'HTTP/1.1 ')) {
throw new Exception('Bad server response headers.');
}

return substr($response, $pos + 4);
}

Пример использования:

$file = new SplFileObject('/path/to/file', 'rb');
$contents = request('https://example.com/api/upload', 'POST', $file, $headers);
if ($headers[0] == 'HTTP/1.1 200 OK') {
print $contents;
}
0

Другие решения

Других решений пока нет …

По вопросам рекламы [email protected]