Занимался разработкой проекта на laravel, технически ничего особенного. Сложность была в том, что сервер располагается на стороне клиента, подключаться к нему можно через промежуточный сервер, подключение к которому идет через vpn. Правки заливать можно через архивирование кода, загрузкой на яндекс.диск, отправкой ссылки, на сервер загружают этот архив и его нужно распаковать. Надо ли говорить, что в таком случае там вылезает масса ошибок с правами и путями к файлам. Стороннее ПО на сервер поставить нельзя, буфер обмена не работает, поэтому скопировать файлы не получается. Исходящие запросы с сервера так же заблокированы. Входящие ограничены размером около 300кб
Все что может делать сервер - принимать запрос и отвечать на него.
Исходя из такой ситуации был написан код, который делит файлы на части и отправляет файлы POST запросами. Это позволяет отправлять файлы на сервер напрямую.
Как работает
- fread читает бинарные данные из файла
- bin2hex переводит в строку
- str_split разбивает строку на части, по допустимому размеру запроса
- отправляется обычная форма на файл-приемник
- там каждая часть дописывается во временный файл
- когда приходит параметр last = 1 содержимое файла читается как строка и сохраняется как бинарные данные с помощью hex2bin
Код отправки
protected $signature = 'app:send-file {filepath}';
public function handle()
{
$filePath = $this->argument('filepath');
$path = base_path($filePath);
$fileName = basename($path);
$file = fopen($path, "rb");
$bin = fread($file, filesize($path));
$hex = bin2hex($bin);
$chunks = str_split($hex, 1024 * 300);
$salt = 'some_salt';
echo 'File: ' . $fileName . PHP_EOL;
echo 'Chunks: ' . sizeof($chunks) . PHP_EOL;
foreach ($chunks as $index => $chunk) {
echo 'Sending: ' . ($index + 1 . ' of ' . sizeof($chunks)) . PHP_EOL;
$isLast = intval($index == count($chunks) - 1);
$hash = md5($filePath . $chunk . $isLast . $salt);
$opts = [
'http' => [
'method' => "POST",
'header' => "Content-Type: application/x-www-form-urlencoded",
'content' => http_build_query([
'hash' => $hash,
'file_path' => $filePath,
'chunk' => $chunk,
'last' => $isLast,
])
]
];
$context = stream_context_create($opts);
$result = file_get_contents('https://host/receiver.php?part=' . $index, false, $context);
if (!$result) {
echo 'Fail';
exit;
}
sleep(1);
}
echo 'Sent' . PHP_EOL;
}
Отправка выглядит так
php artisan app:send-file some_dir/some-file-path.php
Код получения на стороне сервера. В папке received-files создается временный файл, по завершению приема данных файл переносится в путь some_dir/some-file-path.php
<?php
try{
$salt = 'some_salt';
$isLast = $_POST['last'] == '1';
$chunk = $_POST['chunk'];
$fileName = $_POST['file_name'];
$filePath = $_POST['file_path'];
$hash = md5($_POST['file_path'] . $_POST['chunk'] . (int)$isLast . $salt);
if ($hash != $_POST['hash']) {
echo 0; exit;
}
$path = __DIR__ . '/received-files/' . $filePath;
@mkdir(dirname($path), 0755, true);
file_put_contents($path, $chunk, FILE_APPEND);
if ($isLast) {
$string = file_get_contents($path);
// Тут указать где корень проекта
file_put_contents(__DIR__ . '/../../' . $filePath, hex2bin($string));
@unlink($path);
}
echo 1;
}
catch(Throwadble $e){
file_put_contents('error.log', $e->getMessage() . PHP_EOL . PHP_EOL, FILE_APPEND);
}
А теперь про получение файлов с сервера, т.к. иногда нужно было вытащить файл к себе. Команда отправляет путь до файла, отправитель получает этот путь и в ответ читает содержимое файла.
protected $signature = 'app:get-file {filepath} {filename}';
public function handle()
{
$salt = 'some_salt';
$name = $this->argument('filename');
$path = $this->argument('filepath');
$opts = [
'http' => [
'method' => "POST",
'header' => "Content-Type: application/x-www-form-urlencoded",
'content' => http_build_query([
'path' => $path,
'hash' => md5($path . $salt),
])
]
];
$context = stream_context_create($opts);
$result = file_get_contents('https://host/reader.php', false, $context);
if (!empty($result)) {
file_put_contents($name, $result);
} else {
echo 'Failed to read from the file';
}
}
Код отправителя, читает файл в stdout
<?php
$salt = 'some_salt';
$path = $_POST["path"] ?? '';
$inputHash = $_POST['hash'] ?? '';
$hash = md5( $path . $salt);
if ($hash != $inputHash) {
echo 0;
exit;
}
header('Content-Description: File Transfer');
header('Content-Type: application/octet-stream');
header('Content-Disposition: attachment; filename="'.basename($path).'"');
header('Expires: 0');
header('Cache-Control: must-revalidate');
header('Pragma: public');
header('Content-Length: ' . filesize($path));
readfile($path);
exit;
Оставить комментарий