aboutsummaryrefslogtreecommitdiffhomepage
diff options
context:
space:
mode:
authorChocobozzz <me@florianbigard.com>2018-05-23 10:03:26 +0200
committerChocobozzz <me@florianbigard.com>2018-05-23 10:03:26 +0200
commitb40f057594d51ae64e9d638d3b5877e544214b53 (patch)
treebd7918e8b5ab4f688422125a925cca9b6ff527bc
parente1a540b5fa14b0fafa63f99e344927b10fdbee00 (diff)
downloadPeerTube-b40f057594d51ae64e9d638d3b5877e544214b53.tar.gz
PeerTube-b40f057594d51ae64e9d638d3b5877e544214b53.tar.zst
PeerTube-b40f057594d51ae64e9d638d3b5877e544214b53.zip
Handle concurrent requests in cache middleware
-rw-r--r--package.json2
-rw-r--r--server/lib/redis.ts24
-rw-r--r--server/middlewares/cache.ts53
-rw-r--r--yarn.lock8
4 files changed, 55 insertions, 32 deletions
diff --git a/package.json b/package.json
index bf69c4ce0..910de3f39 100644
--- a/package.json
+++ b/package.json
@@ -69,6 +69,7 @@
69 }, 69 },
70 "dependencies": { 70 "dependencies": {
71 "async": "^2.0.0", 71 "async": "^2.0.0",
72 "async-lock": "^1.1.2",
72 "async-lru": "^1.1.1", 73 "async-lru": "^1.1.1",
73 "bcrypt": "^2.0.1", 74 "bcrypt": "^2.0.1",
74 "bittorrent-tracker": "^9.0.0", 75 "bittorrent-tracker": "^9.0.0",
@@ -120,6 +121,7 @@
120 }, 121 },
121 "devDependencies": { 122 "devDependencies": {
122 "@types/async": "^2.0.40", 123 "@types/async": "^2.0.40",
124 "@types/async-lock": "^1.1.0",
123 "@types/bcrypt": "^2.0.0", 125 "@types/bcrypt": "^2.0.0",
124 "@types/body-parser": "^1.16.3", 126 "@types/body-parser": "^1.16.3",
125 "@types/chai": "^4.0.4", 127 "@types/chai": "^4.0.4",
diff --git a/server/lib/redis.ts b/server/lib/redis.ts
index 97ff3598b..5bd55109c 100644
--- a/server/lib/redis.ts
+++ b/server/lib/redis.ts
@@ -88,6 +88,18 @@ class Redis {
88 }) 88 })
89 } 89 }
90 90
91 generateResetPasswordKey (userId: number) {
92 return 'reset-password-' + userId
93 }
94
95 buildViewKey (ip: string, videoUUID: string) {
96 return videoUUID + '-' + ip
97 }
98
99 buildCachedRouteKey (req: express.Request) {
100 return req.method + '-' + req.originalUrl
101 }
102
91 private getValue (key: string) { 103 private getValue (key: string) {
92 return new Promise<string>((res, rej) => { 104 return new Promise<string>((res, rej) => {
93 this.client.get(this.prefix + key, (err, value) => { 105 this.client.get(this.prefix + key, (err, value) => {
@@ -146,18 +158,6 @@ class Redis {
146 }) 158 })
147 } 159 }
148 160
149 private generateResetPasswordKey (userId: number) {
150 return 'reset-password-' + userId
151 }
152
153 private buildViewKey (ip: string, videoUUID: string) {
154 return videoUUID + '-' + ip
155 }
156
157 private buildCachedRouteKey (req: express.Request) {
158 return req.method + '-' + req.originalUrl
159 }
160
161 static get Instance () { 161 static get Instance () {
162 return this.instance || (this.instance = new this()) 162 return this.instance || (this.instance = new this())
163 } 163 }
diff --git a/server/middlewares/cache.ts b/server/middlewares/cache.ts
index c589ef683..bf6659687 100644
--- a/server/middlewares/cache.ts
+++ b/server/middlewares/cache.ts
@@ -1,39 +1,52 @@
1import * as express from 'express' 1import * as express from 'express'
2import * as AsyncLock from 'async-lock'
2import { Redis } from '../lib/redis' 3import { Redis } from '../lib/redis'
3import { logger } from '../helpers/logger' 4import { logger } from '../helpers/logger'
4 5
6const lock = new AsyncLock({ timeout: 5000 })
7
5function cacheRoute (lifetime: number) { 8function cacheRoute (lifetime: number) {
6 return async function (req: express.Request, res: express.Response, next: express.NextFunction) { 9 return async function (req: express.Request, res: express.Response, next: express.NextFunction) {
7 const cached = await Redis.Instance.getCachedRoute(req) 10 const redisKey = Redis.Instance.buildCachedRouteKey(req)
11
12 await lock.acquire(redisKey, async (done) => {
13 const cached = await Redis.Instance.getCachedRoute(req)
8 14
9 // Not cached 15 // Not cached
10 if (!cached) { 16 if (!cached) {
11 logger.debug('Not cached result for route %s.', req.originalUrl) 17 logger.debug('Not cached result for route %s.', req.originalUrl)
12 18
13 const sendSave = res.send.bind(res) 19 const sendSave = res.send.bind(res)
14 20
15 res.send = (body) => { 21 res.send = (body) => {
16 if (res.statusCode >= 200 && res.statusCode < 400) { 22 if (res.statusCode >= 200 && res.statusCode < 400) {
17 const contentType = res.getHeader('content-type').toString() 23 const contentType = res.getHeader('content-type').toString()
18 Redis.Instance.setCachedRoute(req, body, lifetime, contentType, res.statusCode) 24 Redis.Instance.setCachedRoute(req, body, lifetime, contentType, res.statusCode)
19 .catch(err => logger.error('Cannot cache route.', { err })) 25 .then(() => done())
26 .catch(err => {
27 logger.error('Cannot cache route.', { err })
28 return done(err)
29 })
30 }
31
32 return sendSave(body)
20 } 33 }
21 34
22 return sendSave(body) 35 return next()
23 } 36 }
24 37
25 return next() 38 if (cached.contentType) res.contentType(cached.contentType)
26 }
27 39
28 if (cached.contentType) res.contentType(cached.contentType) 40 if (cached.statusCode) {
41 const statusCode = parseInt(cached.statusCode, 10)
42 if (!isNaN(statusCode)) res.status(statusCode)
43 }
29 44
30 if (cached.statusCode) { 45 logger.debug('Use cached result for %s.', req.originalUrl)
31 const statusCode = parseInt(cached.statusCode, 10) 46 res.send(cached.body).end()
32 if (!isNaN(statusCode)) res.status(statusCode)
33 }
34 47
35 logger.debug('Use cached result for %s.', req.originalUrl) 48 return done()
36 return res.send(cached.body).end() 49 })
37 } 50 }
38} 51}
39 52
diff --git a/yarn.lock b/yarn.lock
index 49af4df03..a8660dbab 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -16,6 +16,10 @@
16 esutils "^2.0.2" 16 esutils "^2.0.2"
17 js-tokens "^3.0.0" 17 js-tokens "^3.0.0"
18 18
19"@types/async-lock@^1.1.0":
20 version "1.1.0"
21 resolved "https://registry.yarnpkg.com/@types/async-lock/-/async-lock-1.1.0.tgz#002b1ebeebd382aff66b68bed70a74c7bdd06e3e"
22
19"@types/async@^2.0.40": 23"@types/async@^2.0.40":
20 version "2.0.49" 24 version "2.0.49"
21 resolved "https://registry.yarnpkg.com/@types/async/-/async-2.0.49.tgz#92e33d13f74c895cb9a7f38ba97db8431ed14bc0" 25 resolved "https://registry.yarnpkg.com/@types/async/-/async-2.0.49.tgz#92e33d13f74c895cb9a7f38ba97db8431ed14bc0"
@@ -618,6 +622,10 @@ async-limiter@~1.0.0:
618 version "1.0.0" 622 version "1.0.0"
619 resolved "https://registry.yarnpkg.com/async-limiter/-/async-limiter-1.0.0.tgz#78faed8c3d074ab81f22b4e985d79e8738f720f8" 623 resolved "https://registry.yarnpkg.com/async-limiter/-/async-limiter-1.0.0.tgz#78faed8c3d074ab81f22b4e985d79e8738f720f8"
620 624
625async-lock@^1.1.2:
626 version "1.1.2"
627 resolved "https://registry.yarnpkg.com/async-lock/-/async-lock-1.1.2.tgz#d552b3f8fe93018bf917efcf66d3154b9035282a"
628
621async-lru@^1.1.1: 629async-lru@^1.1.1:
622 version "1.1.1" 630 version "1.1.1"
623 resolved "https://registry.yarnpkg.com/async-lru/-/async-lru-1.1.1.tgz#3edbf7e96484d5c2dd852a8bf9794fc07f5e7274" 631 resolved "https://registry.yarnpkg.com/async-lru/-/async-lru-1.1.1.tgz#3edbf7e96484d5c2dd852a8bf9794fc07f5e7274"