aboutsummaryrefslogtreecommitdiffhomepage
diff options
context:
space:
mode:
authorChocobozzz <me@florianbigard.com>2018-04-17 14:01:06 +0200
committerChocobozzz <me@florianbigard.com>2018-04-17 14:04:34 +0200
commit4195cd2bc5046d4cdf1c677c27cd41f427d7a13a (patch)
tree3300258d96a427da29cc4785686d741e336102bb
parentcff8b272b1631661b8d5f5f4b59bd534ad8c86ca (diff)
downloadPeerTube-4195cd2bc5046d4cdf1c677c27cd41f427d7a13a.tar.gz
PeerTube-4195cd2bc5046d4cdf1c677c27cd41f427d7a13a.tar.zst
PeerTube-4195cd2bc5046d4cdf1c677c27cd41f427d7a13a.zip
Add redis cache to feed route
-rw-r--r--server/controllers/feeds.ts23
-rw-r--r--server/initializers/constants.ts8
-rw-r--r--server/lib/redis.ts57
-rw-r--r--server/middlewares/cache.ts41
4 files changed, 113 insertions, 16 deletions
diff --git a/server/controllers/feeds.ts b/server/controllers/feeds.ts
index 700c50ec8..3e384c48a 100644
--- a/server/controllers/feeds.ts
+++ b/server/controllers/feeds.ts
@@ -1,16 +1,11 @@
1import * as express from 'express' 1import * as express from 'express'
2import { CONFIG } from '../initializers' 2import { CONFIG, FEEDS } from '../initializers/constants'
3import { 3import { asyncMiddleware, feedsValidator, setDefaultSort, videosSortValidator } from '../middlewares'
4 asyncMiddleware,
5 feedsValidator,
6 setDefaultPagination,
7 setDefaultSort,
8 videosSortValidator
9} from '../middlewares'
10import { VideoModel } from '../models/video/video' 4import { VideoModel } from '../models/video/video'
11import * as Feed from 'pfeed' 5import * as Feed from 'pfeed'
12import { ResultList } from '../../shared/models' 6import { ResultList } from '../../shared/models'
13import { AccountModel } from '../models/account/account' 7import { AccountModel } from '../models/account/account'
8import { cacheRoute } from '../middlewares/cache'
14 9
15const feedsRouter = express.Router() 10const feedsRouter = express.Router()
16 11
@@ -18,6 +13,7 @@ feedsRouter.get('/feeds/videos.:format',
18 videosSortValidator, 13 videosSortValidator,
19 setDefaultSort, 14 setDefaultSort,
20 asyncMiddleware(feedsValidator), 15 asyncMiddleware(feedsValidator),
16 asyncMiddleware(cacheRoute),
21 asyncMiddleware(generateFeed) 17 asyncMiddleware(generateFeed)
22) 18)
23 19
@@ -31,8 +27,7 @@ export {
31 27
32async function generateFeed (req: express.Request, res: express.Response, next: express.NextFunction) { 28async function generateFeed (req: express.Request, res: express.Response, next: express.NextFunction) {
33 let feed = initFeed() 29 let feed = initFeed()
34 const paginationStart = 0 30 const start = 0
35 const paginationCount = 20
36 31
37 let resultList: ResultList<VideoModel> 32 let resultList: ResultList<VideoModel>
38 const account: AccountModel = res.locals.account 33 const account: AccountModel = res.locals.account
@@ -40,15 +35,15 @@ async function generateFeed (req: express.Request, res: express.Response, next:
40 if (account) { 35 if (account) {
41 resultList = await VideoModel.listAccountVideosForApi( 36 resultList = await VideoModel.listAccountVideosForApi(
42 account.id, 37 account.id,
43 paginationStart, 38 start,
44 paginationCount, 39 FEEDS.COUNT,
45 req.query.sort, 40 req.query.sort,
46 true 41 true
47 ) 42 )
48 } else { 43 } else {
49 resultList = await VideoModel.listForApi( 44 resultList = await VideoModel.listForApi(
50 paginationStart, 45 start,
51 paginationCount, 46 FEEDS.COUNT,
52 req.query.sort, 47 req.query.sort,
53 req.query.filter, 48 req.query.filter,
54 true 49 true
diff --git a/server/initializers/constants.ts b/server/initializers/constants.ts
index 56d39529e..9fde989c5 100644
--- a/server/initializers/constants.ts
+++ b/server/initializers/constants.ts
@@ -423,6 +423,13 @@ const OPENGRAPH_AND_OEMBED_COMMENT = '<!-- open graph and oembed tags -->'
423 423
424// --------------------------------------------------------------------------- 424// ---------------------------------------------------------------------------
425 425
426const FEEDS = {
427 COUNT: 20,
428 CACHE_LIFETIME: 1000 * 60 * 15 // 15 minutes
429}
430
431// ---------------------------------------------------------------------------
432
426// Special constants for a test instance 433// Special constants for a test instance
427if (isTestInstance() === true) { 434if (isTestInstance() === true) {
428 ACTOR_FOLLOW_SCORE.BASE = 20 435 ACTOR_FOLLOW_SCORE.BASE = 20
@@ -462,6 +469,7 @@ export {
462 SERVER_ACTOR_NAME, 469 SERVER_ACTOR_NAME,
463 PRIVATE_RSA_KEY_SIZE, 470 PRIVATE_RSA_KEY_SIZE,
464 SORTABLE_COLUMNS, 471 SORTABLE_COLUMNS,
472 FEEDS,
465 STATIC_MAX_AGE, 473 STATIC_MAX_AGE,
466 STATIC_PATHS, 474 STATIC_PATHS,
467 ACTIVITY_PUB, 475 ACTIVITY_PUB,
diff --git a/server/lib/redis.ts b/server/lib/redis.ts
index 41f4c9869..1e7c0a821 100644
--- a/server/lib/redis.ts
+++ b/server/lib/redis.ts
@@ -1,7 +1,14 @@
1import * as express from 'express'
1import { createClient, RedisClient } from 'redis' 2import { createClient, RedisClient } from 'redis'
2import { logger } from '../helpers/logger' 3import { logger } from '../helpers/logger'
3import { generateRandomString } from '../helpers/utils' 4import { generateRandomString } from '../helpers/utils'
4import { CONFIG, USER_PASSWORD_RESET_LIFETIME, VIDEO_VIEW_LIFETIME } from '../initializers' 5import { CONFIG, FEEDS, USER_PASSWORD_RESET_LIFETIME, VIDEO_VIEW_LIFETIME } from '../initializers'
6
7type CachedRoute = {
8 body: string,
9 contentType?: string
10 statusCode?: string
11}
5 12
6class Redis { 13class Redis {
7 14
@@ -54,6 +61,22 @@ class Redis {
54 return this.exists(this.buildViewKey(ip, videoUUID)) 61 return this.exists(this.buildViewKey(ip, videoUUID))
55 } 62 }
56 63
64 async getCachedRoute (req: express.Request) {
65 const cached = await this.getObject(this.buildCachedRouteKey(req))
66
67 return cached as CachedRoute
68 }
69
70 setCachedRoute (req: express.Request, body: any, contentType?: string, statusCode?: number) {
71 const cached: CachedRoute = {
72 body: body.toString(),
73 contentType,
74 statusCode: statusCode.toString()
75 }
76
77 return this.setObject(this.buildCachedRouteKey(req), cached, FEEDS.CACHE_LIFETIME)
78 }
79
57 listJobs (jobsPrefix: string, state: string, mode: 'alpha', order: 'ASC' | 'DESC', offset: number, count: number) { 80 listJobs (jobsPrefix: string, state: string, mode: 'alpha', order: 'ASC' | 'DESC', offset: number, count: number) {
58 return new Promise<string[]>((res, rej) => { 81 return new Promise<string[]>((res, rej) => {
59 this.client.sort(jobsPrefix + ':jobs:' + state, 'by', mode, order, 'LIMIT', offset.toString(), count.toString(), (err, values) => { 82 this.client.sort(jobsPrefix + ':jobs:' + state, 'by', mode, order, 'LIMIT', offset.toString(), count.toString(), (err, values) => {
@@ -79,13 +102,39 @@ class Redis {
79 this.client.set(this.prefix + key, value, 'PX', expirationMilliseconds, (err, ok) => { 102 this.client.set(this.prefix + key, value, 'PX', expirationMilliseconds, (err, ok) => {
80 if (err) return rej(err) 103 if (err) return rej(err)
81 104
82 if (ok !== 'OK') return rej(new Error('Redis result is not OK.')) 105 if (ok !== 'OK') return rej(new Error('Redis set result is not OK.'))
83 106
84 return res() 107 return res()
85 }) 108 })
86 }) 109 })
87 } 110 }
88 111
112 private setObject (key: string, obj: { [ id: string ]: string }, expirationMilliseconds: number) {
113 return new Promise<void>((res, rej) => {
114 this.client.hmset(this.prefix + key, obj, (err, ok) => {
115 if (err) return rej(err)
116 if (!ok) return rej(new Error('Redis mset result is not OK.'))
117
118 this.client.pexpire(this.prefix + key, expirationMilliseconds, (err, ok) => {
119 if (err) return rej(err)
120 if (!ok) return rej(new Error('Redis expiration result is not OK.'))
121
122 return res()
123 })
124 })
125 })
126 }
127
128 private getObject (key: string) {
129 return new Promise<{ [ id: string ]: string }>((res, rej) => {
130 this.client.hgetall(this.prefix + key, (err, value) => {
131 if (err) return rej(err)
132
133 return res(value)
134 })
135 })
136 }
137
89 private exists (key: string) { 138 private exists (key: string) {
90 return new Promise<boolean>((res, rej) => { 139 return new Promise<boolean>((res, rej) => {
91 this.client.exists(this.prefix + key, (err, existsNumber) => { 140 this.client.exists(this.prefix + key, (err, existsNumber) => {
@@ -104,6 +153,10 @@ class Redis {
104 return videoUUID + '-' + ip 153 return videoUUID + '-' + ip
105 } 154 }
106 155
156 private buildCachedRouteKey (req: express.Request) {
157 return req.method + '-' + req.originalUrl
158 }
159
107 static get Instance () { 160 static get Instance () {
108 return this.instance || (this.instance = new this()) 161 return this.instance || (this.instance = new this())
109 } 162 }
diff --git a/server/middlewares/cache.ts b/server/middlewares/cache.ts
new file mode 100644
index 000000000..a2c7f7cbd
--- /dev/null
+++ b/server/middlewares/cache.ts
@@ -0,0 +1,41 @@
1import * as express from 'express'
2import { Redis } from '../lib/redis'
3import { logger } from '../helpers/logger'
4
5async function cacheRoute (req: express.Request, res: express.Response, next: express.NextFunction) {
6 const cached = await Redis.Instance.getCachedRoute(req)
7
8 // Not cached
9 if (!cached) {
10 logger.debug('Not cached result for route %s.', req.originalUrl)
11
12 const sendSave = res.send.bind(res)
13
14 res.send = (body) => {
15 if (res.statusCode >= 200 && res.statusCode < 400) {
16 Redis.Instance.setCachedRoute(req, body, res.getHeader('content-type').toString(), res.statusCode)
17 .catch(err => logger.error('Cannot cache route.', { err }))
18 }
19
20 return sendSave(body)
21 }
22
23 return next()
24 }
25
26 if (cached.contentType) res.contentType(cached.contentType)
27
28 if (cached.statusCode) {
29 const statusCode = parseInt(cached.statusCode, 10)
30 if (!isNaN(statusCode)) res.status(statusCode)
31 }
32
33 logger.debug('Use cached result for %s.', req.originalUrl)
34 return res.send(cached.body).end()
35}
36
37// ---------------------------------------------------------------------------
38
39export {
40 cacheRoute
41}