-Пример создания сервиса видеоинструкций на основе ReactPlayer-
1. Обзор проекта
Данный случай является проектом, выполненным для перевода существующей системы пользовательских руководств на основе PDF в сервис, основанный на видео-контенте. В существующей системе пользователи нажимали на кнопку руководства, и Frontend напрямую получал доступ к AWS S3 для скачивания PDF-файлов. Этот подход был простым в реализации, но имел ограничения, такие как трудности, связанные с одновременной проверкой рабочего экрана и руководства, а также сложностью интуитивной передачи сложных процедур.
Клиент потребовал, чтобы пользователи могли одновременно просматривать руководство в формате видео, сохраняя при этом рабочий экран, с использованием функции PIP (Картина в картине). В то же время в соответствии с политикой безопасности прямой доступ к AWS S3 должен был быть заблокирован, и существовали ограничения на возможность включения чувствительной информации для аутентификации в URL-параметры. Таким образом, ключевым моментом этого проекта стало не просто внедрение функции воспроизведения видео, а проектирование структуры аутентификации и потоковой передачи, которая удовлетворяет требованиям безопасности и использует браузерную технологию воспроизведения видео.
2. Выбор технологий и направление проектирования
В области фронтенда была введена библиотека видеоплеера на базе React под названием ReactPlayer. ReactPlayer работает на основе тега HTML5 video, обладает высокой совместимостью с окружением React и имеет преимущества в виде простоты управления функцией PIP и состоянием воспроизведения.
Однако ReactPlayer использует способ запроса тега video, поэтому запросы виде файлов изначально выполняются по методу GET. Поскольку не удается включить учетные данные в тело запроса или свободно настраивать заголовки, потребовалось разделить процесс аутентификации и фактический запрос на воспроизведение видео.
Пример кода 1. Пример вызова ReactPlayer
<ReactPlayer
url={videoEndpoint}
controls
pip
playing={false}
width="100%"
height="100%"
/>
Этот пример иллюстрирует структуру, в которой ReactPlayer воспроизводит видео на основе конечной точки воспроизведения видео, полученной от сервера. Учетные данные не включаются в URL, а передаются в виде временного токена в Cookie, выданного на этапе предварительной аутентификации.
3. Переработка архитектуры
Существующая структура позволяла Frontend напрямую обращаться к AWS S3. Однако при предоставлении видеоконтента сохранение той же структуры могло привести к утечке путей хранения, повторному использованию URL и возможности обхода контроля доступа. В связи с этим были разделены роли серверов следующим образом.
Frontend
-> Server A (사용자 인증 및 스트리밍 요청 중계)
-> Server B (파일 조회 전용 서버)
-> AWS S3
Сервер A отвечает за аутентификацию пользователей, проверку прав, выдачу временных токенов и промежуточную передачу запросов на транслирование. Сервер B был отделен, чтобы отвечать только за получение фактических видеофайлов и доступ к AWS S3. Это блокирует возможность клиенту узнать путь хранения и обеспечивает прохождение всех запросов на видео через проверку аутентификации сервера.
4. Аутентификация и поток воспроизведения
Когда пользователь нажимает кнопку меню, сначала отправляется запрос API для проверки пользователя на Server A, не вызывая видеоплеер сразу. После завершения проверки Server A выдает конечную точку для воспроизведения видео и временный токен с ограниченным сроком действия. Временный токен сохраняется в Cookie браузера, а затем передается вместе с вызовом конечной точки видео при вызове ReactPlayer.
Пример кода 2. Запрос информации о воспроизведении видео
const openManualVideo = async (manualId: string) => {
const response = await api.get(`/api/videos/${manualId}/authorize`);
setVideoEndpoint(response.data.videoEndpoint);
setManualModalOpen(true);
};
В этом коде показан пример запроса к конечной точке воспроизведения видео при нажатии на кнопку руководства. На этом этапе сервер проверяет права пользователя и отвечает, сохраняя временный токен в Cookie.
Пример кода 3. Пример выпуска временного токена Cookie
ResponseCookie cookie = ResponseCookie.from("VIDEO_ACCESS_TOKEN", token)
.httpOnly(true)
.secure(true)
.path("/api/videos/")
.maxAge(Duration.ofMinutes(30))
.sameSite("Strict")
.build();
return ResponseEntity.ok()
.header(HttpHeaders.SET_COOKIE, cookie.toString())
.body(new VideoAuthorizeResponse(videoEndpoint));
Cookie настроены с параметрами HttpOnly, Secure, SameSite, чтобы ограничить доступ к скриптам и ненужную передачу данных за пределы. Кроме того, path и maxAge были указаны, чтобы использовать их только для ограниченных запросов на воспроизведение видео.
5. Обработка потоковой трансляции
Потоковая трансляция видео, в отличие от обычного скачивания файлов, подразумевает непрерывную передачу больших объемов данных, и когда пользователь перемещает позицию воспроизведения, могут возникать частичные запросы на основе заголовка Range. Поэтому Сервер A должен передать заголовок Range клиента на Сервер B, а Сервер B должен вернуть статус ответа и основные заголовки обратно клиенту.
Образец кода 4. Потоковая передача на основе заголовка Range
@GetMapping("/api/videos/{manualId}/stream")
public Mono>> stream(
@PathVariable String manualId,
@RequestHeader(value = HttpHeaders.RANGE, required = false) String range
) {
return webClient.get()
.uri("/internal/files/{manualId}", manualId)
.headers(headers -> {
if (range != null) headers.set(HttpHeaders.RANGE, range);
})
.exchangeToMono(response -> {
Flux body = response.bodyToFlux(DataBuffer.class);
ResponseEntity.BodyBuilder builder = ResponseEntity.status(response.statusCode());
response.headers().asHttpHeaders().forEach((name, values) -> {
if (isStreamingHeader(name)) builder.header(name, values.toArray(String[]::new));
});
return Mono.just(builder.body(body));
});
}
Ключевым моментом этого подхода является то, что он не загружает весь файл в память, как это происходит с bodyToMono(Resource.class) или bodyToMono(byte[].class). Используя bodyToFlux(DataBuffer.class), можно передавать видео данные по частям (chunk), что делает его подходящим для обработки больших объемов видео.
Пример кода 5. Основная фильтрация заголовков потоковой передачи
private boolean isStreamingHeader(String name) {
return HttpHeaders.CONTENT_TYPE.equalsIgnoreCase(name)
|| HttpHeaders.CONTENT_LENGTH.equalsIgnoreCase(name)
|| HttpHeaders.CONTENT_RANGE.equalsIgnoreCase(name)
|| HttpHeaders.ACCEPT_RANGES.equalsIgnoreCase(name)
|| HttpHeaders.CACHE_CONTROL.equalsIgnoreCase(name);
}
Заголовки, такие как Content-Range, Accept-Ranges, Content-Length, необходимы браузеру для определения длины видео и диапазона частичных ответов. Особенно важно сохранить эти заголовки для функции навигации и надежного воспроизведения.
6. Результаты и эффекты
Результаты реализации позволили пользователям сразу воспроизводить видео в браузере и одновременно использовать экран рабочего процесса и видеоинструкцию через функцию PIP. Кроме того, удалив прямой доступ к AWS S3 и внедрив управление доступом на серверной основе, мы смогли удовлетворить требования безопасности.
• Устранение прямой экспозиции AWS S3
• Устранение передачи конфиденциальной информации на основе URL
• Разрешение доступа к видео endpoint после верификации пользователя
• Ограниченный контроль доступа на основе временного токена
• Поддержка воспроизведения и навигации видео на основе заголовка диапазона
7. Заключение
В данном случае мы не просто добавили видеоплеер, но и получили опыт проектирования, учитывая способы работы ReactPlayer и HTML5 video тегов, политику безопасности клиента, разделение ролей между серверами и способы потоковой обработки. Разделив процессы аутентификации и воспроизведения, а также четко разграничив ответственность между Сервером A и Сервером B, мы смогли одновременно обеспечить пользователям хороший опыт и безопасность.
Особенно в практической среде я почувствовал, что важно понимать ограничения не только реализации функционала, но и политики безопасности и поведения браузеров. В ходе данного проекта я подтвердил, что для обеспечения надежного предоставления услуг в видеобазированных сервисах необходимо учитывать структуру аутентификации, поток данных, обработку потоковых заголовков и оперативную политику безопасности.
НЗ