1 import cors from 'cors'
2 import express from 'express'
3 import { PassThrough, pipeline } from 'stream'
4 import { logger } from '@server/helpers/logger'
5 import { StreamReplacer } from '@server/helpers/stream-replacer'
6 import { OBJECT_STORAGE_PROXY_PATHS } from '@server/initializers/constants'
7 import { injectQueryToPlaylistUrls } from '@server/lib/hls'
8 import { getHLSFileReadStream, getWebTorrentFileReadStream } from '@server/lib/object-storage'
11 ensureCanAccessPrivateVideoHLSFiles,
12 ensureCanAccessVideoPrivateWebTorrentFiles,
13 ensurePrivateObjectStorageProxyIsEnabled,
15 } from '@server/middlewares'
16 import { HttpStatusCode } from '@shared/models'
17 import { buildReinjectVideoFileTokenQuery, doReinjectVideoFileToken } from './shared/m3u8-playlist'
18 import { GetObjectCommandOutput } from '@aws-sdk/client-s3'
20 const objectStorageProxyRouter = express.Router()
22 objectStorageProxyRouter.use(cors())
24 objectStorageProxyRouter.get(OBJECT_STORAGE_PROXY_PATHS.PRIVATE_WEBSEED + ':filename',
25 ensurePrivateObjectStorageProxyIsEnabled,
27 asyncMiddleware(ensureCanAccessVideoPrivateWebTorrentFiles),
28 asyncMiddleware(proxifyWebTorrent)
31 objectStorageProxyRouter.get(OBJECT_STORAGE_PROXY_PATHS.STREAMING_PLAYLISTS.PRIVATE_HLS + ':videoUUID/:filename',
32 ensurePrivateObjectStorageProxyIsEnabled,
34 asyncMiddleware(ensureCanAccessPrivateVideoHLSFiles),
35 asyncMiddleware(proxifyHLS)
38 // ---------------------------------------------------------------------------
41 objectStorageProxyRouter
44 async function proxifyWebTorrent (req: express.Request, res: express.Response) {
45 const filename = req.params.filename
47 logger.debug('Proxifying WebTorrent file %s from object storage.', filename)
50 const { response: s3Response, stream } = await getWebTorrentFileReadStream({
52 rangeHeader: req.header('range')
55 setS3Headers(res, s3Response)
57 return stream.pipe(res)
59 return handleObjectStorageFailure(res, err)
63 async function proxifyHLS (req: express.Request, res: express.Response) {
64 const playlist = res.locals.videoStreamingPlaylist
65 const video = res.locals.onlyVideo
66 const filename = req.params.filename
68 logger.debug('Proxifying HLS file %s from object storage.', filename)
71 const { response: s3Response, stream } = await getHLSFileReadStream({
72 playlist: playlist.withVideo(video),
74 rangeHeader: req.header('range')
77 setS3Headers(res, s3Response)
79 const streamReplacer = filename.endsWith('.m3u8') && doReinjectVideoFileToken(req)
80 ? new StreamReplacer(line => injectQueryToPlaylistUrls(line, buildReinjectVideoFileTokenQuery(req, filename.endsWith('master.m3u8'))))
90 handleObjectStorageFailure(res, err)
94 return handleObjectStorageFailure(res, err)
98 function handleObjectStorageFailure (res: express.Response, err: Error) {
99 if (err.name === 'NoSuchKey') {
100 logger.debug('Could not find key in object storage to proxify private HLS video file.', { err })
101 return res.sendStatus(HttpStatusCode.NOT_FOUND_404)
105 status: HttpStatusCode.INTERNAL_SERVER_ERROR_500,
106 message: err.message,
111 function setS3Headers (res: express.Response, s3Response: GetObjectCommandOutput) {
112 if (s3Response.$metadata.httpStatusCode === HttpStatusCode.PARTIAL_CONTENT_206) {
113 res.setHeader('Content-Range', s3Response.ContentRange)
114 res.status(HttpStatusCode.PARTIAL_CONTENT_206)