Перейти к содержанию
HexaTransfer
Вернуться к блогу
Технические погружения

Создайте Your Own Передача файлов Service: Dev Руководство

Создайте a файл transfer service from scratch. Backend API design, storage integration, и user interface development tutorial.

Чтобы создать собственный сервис передачи файлов, вам нужно пять компонентов: фронтенд, который нарезает и опционально шифрует файлы в браузере; REST API, который создаёт сессии загрузки и формирует предподписанные URL хранилища; S3-совместимое объектное хранилище (Cloudflare R2, Backblaze B2 или MinIO); небольшая база метаданных (SQLite или Postgres); и TLS 1.3 reverse proxy. На Node 22, Fastify и Vite можно выпустить сервис в духе WeTransfer примерно в 600 строк кода и хостить всё это за $5–15 в месяц при умеренном трафике.

Начните с выбора слоя хранилища

Экономика хранилища определяет судьбу сервиса передачи файлов. AWS S3 берёт $0,09 за ГБ исходящего трафика — значит, популярная загрузка на 2 ГБ, скачанная 1 000 раз, обойдётся вам в $180 только на пропускную способность. Cloudflare R2 не берёт за исходящий и берёт $0,015 за ГБ хранения. Backblaze B2: $0,01 за ГБ исходящего и $0,006 за ГБ хранения. Для бесплатного сервиса передачи R2 — очевидный выбор. MinIO позволяет вам размещать S3-семантику на собственном железе — подходит, если у вас уже есть простаивающая полоса и диски.

Используйте предподписанные URL, чтобы клиенты загружали и скачивали напрямую из хранилища, минуя ваш сервер. Это делает API CPU-нагруженным, а не bandwidth-нагруженным.

Набросок поверхности API

Держите API небольшим. Пять эндпоинтов покрывают 95% потребностей:

  • POST /sessions — создать сессию загрузки, вернуть ID и предподписанные URL для составной загрузки
  • POST /sessions/:id/complete — завершить составную загрузку, вернуть URL ссылки
  • GET /shares/:id — вернуть метаданные файла (размер, срок, оставшиеся загрузки)
  • GET /shares/:id/download — вернуть предподписанный URL скачивания
  • DELETE /shares/:id — позволить владельцу отозвать доступ досрочно (требует токен отзыва)

Жёстко ограничьте POST /sessions по скорости — 20 в час с одного IP вполне достаточно. Все эндпоинты возвращают JSON, используют Cache-Control: no-store и устанавливают Content-Security-Policy на HTML-страницах.

Подключение бэкенда с Fastify

Fastify даёт валидацию JSON-схем, разумное логирование и 30 000 запросов в секунду на ядро без лишних усилий. Минимальный POST /sessions:

fastify.post('/sessions', {
  schema: {
    body: {
      type: 'object', required: ['size'],
      properties: {
        size: { type: 'integer', minimum: 1, maximum: 10737418240 },
        contentType: { type: 'string', maxLength: 120 }
      }
    }
  }
}, async (req) => {
  const id = nanoid(16);
  const key = `blobs/${id}`;
  const upload = await s3.createMultipartUpload({
    Bucket: 'transfers', Key: key, ContentType: req.body.contentType
  });
  const partCount = Math.ceil(req.body.size / (8 * 1024 * 1024));
  const urls = await Promise.all(
    Array.from({ length: partCount }, (_, i) =>
      getSignedUrl(s3, new UploadPartCommand({
        Bucket: 'transfers', Key: key, UploadId: upload.UploadId, PartNumber: i + 1
      }), { expiresIn: 3600 }))
  );
  db.prepare('INSERT INTO sessions VALUES (?, ?, ?, ?)')
    .run(id, upload.UploadId, key, Date.now());
  return { id, uploadId: upload.UploadId, partUrls: urls };
});

Потолок 10 ГБ, части по 8 МБ, срок действия URL 1 час. Корректируйте под структуру вашего трафика.

Создание UI загрузки

Vite + React + без UI-библиотек — примерно 150 строк до работающего drag-and-drop. Ключевой элемент — воркер загрузки: создайте выделенный Web Worker, передайте ему хэндл File и URL частей, пусть он PUT-ит каждый чанк с параллельностью 3. Главный поток остаётся отзывчивым, обновления прогресса приходят через postMessage.

// worker.js
self.onmessage = async ({ data: { file, partUrls } }) => {
  const partSize = 8 * 1024 * 1024;
  const etags = [];
  for (let i = 0; i < partUrls.length; i++) {
    const blob = file.slice(i * partSize, (i + 1) * partSize);
    const res = await fetch(partUrls[i], { method: 'PUT', body: blob });
    etags[i] = res.headers.get('etag');
    self.postMessage({ type: 'progress', done: i + 1, total: partUrls.length });
  }
  self.postMessage({ type: 'done', etags });
};

Добавьте поле пароля, которое получает ключ через PBKDF2 (600 000 итераций, SHA-256) и пропускает каждый чанк через AES-256-GCM перед PUT. Это превращает ваш сервис из серверно-шифрованного в E2EE.

Проектирование схемы базы данных

SQLite через better-sqlite3 обрабатывает десятки миллионов строк без проблем. Держите схему компактной:

CREATE TABLE sessions (
  id TEXT PRIMARY KEY, upload_id TEXT, object_key TEXT, created_at INTEGER
);
CREATE TABLE shares (
  id TEXT PRIMARY KEY, object_key TEXT, size_bytes INTEGER,
  content_type TEXT, expires_at INTEGER, max_downloads INTEGER,
  download_count INTEGER DEFAULT 0, revocation_token TEXT
);
CREATE INDEX idx_shares_expires ON shares(expires_at);

Никакой таблицы пользователей, никаких email, никаких IP-логов. Это намеренное решение — оно делает соответствие требованиям 152-ФЗ (и GDPR) почти тривиальным. Ночной крон удаляет истёкшие строки и их объекты S3:

const expired = db.prepare('SELECT object_key FROM shares WHERE expires_at < ?').all(Date.now());
for (const { object_key } of expired) {
  await s3.deleteObject({ Bucket: 'transfers', Key: object_key });
}
db.prepare('DELETE FROM shares WHERE expires_at < ?').run(Date.now());

Реализация ссылок и скачиваний

URL ссылок выглядят как https://yourapp.com/f/abc123#k=<base64key> при добавлении клиентского шифрования. Фрагмент не попадает в серверные логи. Страница скачивания получает /shares/abc123, берёт предподписанный URL S3, стримит байты напрямую из R2, расшифровывает в браузере и передаёт результат в StreamSaver.js или File System Access API.

Установите Content-Disposition: attachment; filename*=UTF-8''encoded-name, чтобы браузер открывал диалог сохранения, а не рендерил файл встроенно. Это важно для PDF, HTML и SVG — все они могут выполнять скрипты в браузере в противном случае.

Защита от злоупотреблений

Сервисы передачи файлов привлекают злоумышленников. Ждите три категории: распространение вредоносного ПО, фишинговые нагрузки и загрузки с нарушением авторских прав. Меры защиты по убыванию эффективности: ограничение скорости загрузок по IP, минимальный размер файла (менее 10 КБ — почти всегда мусор), вычисление SHA-256 шифротекста и проверка по списку блокировки известных нежелательных хешей, форма для жалоб на злоупотребления без лишних шагов. ClamAV на расшифрованном контенте работает только при наличии ключа, что нарушает E2EE — правильный ответ: контентно-агностичные лимиты скорости и оперативные удаления по жалобам.

Подписывайте каждый предподписанный URL с коротким TTL (максимум 1 час) и привязывайте сессии к сгенерированному браузером эфемерному токену в Secure; HttpOnly; SameSite=Strict куки.

Дешёвое развёртывание и эксплуатация

Один Hetzner CX22 за $10 (2 vCPU, 4 ГБ RAM) запускает Fastify + SQLite + Caddy reverse proxy и обслуживает тысячи активных пользователей. Caddy автоматически обеспечивает TLS 1.3 через Let's Encrypt. Поставьте Cloudflare перед ним для защиты от DDoS и бесплатного WAF. Мониторинг через Uptime Kuma (бесплатно, self-hosted), логи в stdout, захватываемый journald.

Для географической избыточности реплицируйте SQLite через Litestream в R2 и направьте второй регион на то же хранилище объектов. Переключение при сбое — на уровне DNS. Вся история о DR умещается на салфетке.

Описанный подход во многом повторяет архитектуру HexaTransfer — Fastify API, R2-хранилище, Web Crypto в браузере, SQLite-метаданные, без аккаунтов. Попробуйте на hexatransfer.com — бесплатно, без аккаунта, до 10 ГБ.

Что пропустить до момента, когда оно понадобится

Не делайте аккаунты, команды и папки в v1. Не добавляйте отправку email. Не реализуйте предпросмотр видео или миниатюры изображений — оба вынуждают сервер видеть открытый текст. Не выпускайте мобильное приложение, пока веб-версия не будет безупречна на мобильном Safari. Функциональное раздутие, убившее ранние клоны сервисов передачи файлов, всегда следовало одному паттерну: аккаунты, затем квоты, затем оплата, затем командные планы — и вот вы уже конкурируете с Dropbox вместо того, чтобы выпускать быстрый, приватный инструмент передачи. Держите поверхность небольшой, а криптографию — строгой.

Безопасная отправка больших файлов со сквозным шифрованием

Передавайте файлы до 10 ГБ бесплатно со сквозным шифрованием. Регистрация не требуется. Ваши файлы шифруются в браузере перед загрузкой — никто другой не может их прочитать.

Отправить файл