1
+ import { NextFunction , Request , Response } from "express" ;
2
+ import { VideoID , VideoIDHash , Service } from "../types/segments.model" ;
3
+ import { QueryCacher } from "../utils/queryCacher" ;
4
+ import { skipSegmentsHashKey , skipSegmentsKey , videoLabelsHashKey , videoLabelsKey } from "../utils/redisKeys" ;
5
+
6
+ type hashType = "skipSegments" | "skipSegmentsHash" | "videoLabel" | "videoLabelHash" ;
7
+ type ETag = `${hashType } ;${VideoIDHash } ;${Service } ;${number } `;
8
+
9
+ export function cacheMiddlware ( req : Request , res : Response , next : NextFunction ) : void {
10
+ const reqEtag = req . get ( "If-None-Match" ) as string ;
11
+ // if weak etag, do not handle
12
+ if ( ! reqEtag || reqEtag . startsWith ( "W/" ) ) return next ( ) ;
13
+ // split into components
14
+ const [ hashType , hashKey , service , lastModified ] = reqEtag . split ( ";" ) ;
15
+ // fetch last-modified
16
+ getLastModified ( hashType as hashType , hashKey as VideoIDHash , service as Service )
17
+ . then ( redisLastModified => {
18
+ if ( redisLastModified <= new Date ( Number ( lastModified ) + 1000 ) ) {
19
+ // match cache, generate etag
20
+ const etag = `${ hashType } ;${ hashKey } ;${ service } ;${ redisLastModified . getTime ( ) } ` as ETag ;
21
+ res . status ( 304 ) . set ( "etag" , etag ) . send ( ) ;
22
+ }
23
+ else next ( ) ;
24
+ } )
25
+ . catch ( next ) ;
26
+ }
27
+
28
+ function getLastModified ( hashType : hashType , hashKey : ( VideoID | VideoIDHash ) , service : Service ) : Promise < Date | null > {
29
+ let redisKey : string | null ;
30
+ if ( hashType === "skipSegments" ) redisKey = skipSegmentsKey ( hashKey as VideoID , service ) ;
31
+ else if ( hashType === "skipSegmentsHash" ) redisKey = skipSegmentsHashKey ( hashKey as VideoIDHash , service ) ;
32
+ else if ( hashType === "videoLabel" ) redisKey = videoLabelsKey ( hashKey as VideoID , service ) ;
33
+ else if ( hashType === "videoLabelHash" ) redisKey = videoLabelsHashKey ( hashKey as VideoIDHash , service ) ;
34
+ else return Promise . reject ( ) ;
35
+ return QueryCacher . getKeyLastModified ( redisKey ) ;
36
+ }
37
+
38
+ export async function getEtag ( hashType : hashType , hashKey : VideoIDHash , service : Service ) : Promise < ETag > {
39
+ const lastModified = await getLastModified ( hashType , hashKey , service ) ;
40
+ return `${ hashType } ;${ hashKey } ;${ service } ;${ lastModified . getTime ( ) } ` as ETag ;
41
+ }
42
+
43
+ /* example usage
44
+ import { getEtag } from "../middleware/etag";
45
+ await getEtag(hashType, hashPrefix, service)
46
+ .then(etag => res.set("ETag", etag))
47
+ .catch(() => null);
48
+ */
0 commit comments