diff options
author | Chocobozzz <me@florianbigard.com> | 2018-05-23 10:03:26 +0200 |
---|---|---|
committer | Chocobozzz <me@florianbigard.com> | 2018-05-23 10:03:26 +0200 |
commit | b40f057594d51ae64e9d638d3b5877e544214b53 (patch) | |
tree | bd7918e8b5ab4f688422125a925cca9b6ff527bc | |
parent | e1a540b5fa14b0fafa63f99e344927b10fdbee00 (diff) | |
download | PeerTube-b40f057594d51ae64e9d638d3b5877e544214b53.tar.gz PeerTube-b40f057594d51ae64e9d638d3b5877e544214b53.tar.zst PeerTube-b40f057594d51ae64e9d638d3b5877e544214b53.zip |
Handle concurrent requests in cache middleware
-rw-r--r-- | package.json | 2 | ||||
-rw-r--r-- | server/lib/redis.ts | 24 | ||||
-rw-r--r-- | server/middlewares/cache.ts | 53 | ||||
-rw-r--r-- | yarn.lock | 8 |
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 @@ | |||
1 | import * as express from 'express' | 1 | import * as express from 'express' |
2 | import * as AsyncLock from 'async-lock' | ||
2 | import { Redis } from '../lib/redis' | 3 | import { Redis } from '../lib/redis' |
3 | import { logger } from '../helpers/logger' | 4 | import { logger } from '../helpers/logger' |
4 | 5 | ||
6 | const lock = new AsyncLock({ timeout: 5000 }) | ||
7 | |||
5 | function cacheRoute (lifetime: number) { | 8 | function 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 | ||
@@ -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 | ||
625 | async-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 | |||
621 | async-lru@^1.1.1: | 629 | async-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" |