diff options
Diffstat (limited to 'server/lib/redis.ts')
-rw-r--r-- | server/lib/redis.ts | 220 |
1 files changed, 76 insertions, 144 deletions
diff --git a/server/lib/redis.ts b/server/lib/redis.ts index 8aec4b793..52766663a 100644 --- a/server/lib/redis.ts +++ b/server/lib/redis.ts | |||
@@ -1,31 +1,30 @@ | |||
1 | import express from 'express' | 1 | import { createClient, RedisClientOptions, RedisModules } from 'redis' |
2 | import { createClient, RedisClient } from 'redis' | 2 | import { exists } from '@server/helpers/custom-validators/misc' |
3 | import { sha256 } from '@shared/extra-utils' | ||
3 | import { logger } from '../helpers/logger' | 4 | import { logger } from '../helpers/logger' |
4 | import { generateRandomString } from '../helpers/utils' | 5 | import { generateRandomString } from '../helpers/utils' |
6 | import { CONFIG } from '../initializers/config' | ||
5 | import { | 7 | import { |
8 | AP_CLEANER, | ||
6 | CONTACT_FORM_LIFETIME, | 9 | CONTACT_FORM_LIFETIME, |
10 | RESUMABLE_UPLOAD_SESSION_LIFETIME, | ||
11 | TRACKER_RATE_LIMITS, | ||
7 | USER_EMAIL_VERIFY_LIFETIME, | 12 | USER_EMAIL_VERIFY_LIFETIME, |
8 | USER_PASSWORD_RESET_LIFETIME, | ||
9 | USER_PASSWORD_CREATE_LIFETIME, | 13 | USER_PASSWORD_CREATE_LIFETIME, |
14 | USER_PASSWORD_RESET_LIFETIME, | ||
10 | VIEW_LIFETIME, | 15 | VIEW_LIFETIME, |
11 | WEBSERVER, | 16 | WEBSERVER |
12 | TRACKER_RATE_LIMITS, | ||
13 | RESUMABLE_UPLOAD_SESSION_LIFETIME | ||
14 | } from '../initializers/constants' | 17 | } from '../initializers/constants' |
15 | import { CONFIG } from '../initializers/config' | ||
16 | import { exists } from '@server/helpers/custom-validators/misc' | ||
17 | 18 | ||
18 | type CachedRoute = { | 19 | // Only used for typings |
19 | body: string | 20 | const redisClientWrapperForType = () => createClient<{}>() |
20 | contentType?: string | ||
21 | statusCode?: string | ||
22 | } | ||
23 | 21 | ||
24 | class Redis { | 22 | class Redis { |
25 | 23 | ||
26 | private static instance: Redis | 24 | private static instance: Redis |
27 | private initialized = false | 25 | private initialized = false |
28 | private client: RedisClient | 26 | private connected = false |
27 | private client: ReturnType<typeof redisClientWrapperForType> | ||
29 | private prefix: string | 28 | private prefix: string |
30 | 29 | ||
31 | private constructor () { | 30 | private constructor () { |
@@ -38,26 +37,43 @@ class Redis { | |||
38 | 37 | ||
39 | this.client = createClient(Redis.getRedisClientOptions()) | 38 | this.client = createClient(Redis.getRedisClientOptions()) |
40 | 39 | ||
41 | this.client.on('error', err => { | 40 | logger.info('Connecting to redis...') |
42 | logger.error('Error in Redis client.', { err }) | ||
43 | process.exit(-1) | ||
44 | }) | ||
45 | 41 | ||
46 | if (CONFIG.REDIS.AUTH) { | 42 | this.client.connect() |
47 | this.client.auth(CONFIG.REDIS.AUTH) | 43 | .then(() => { |
48 | } | 44 | logger.info('Connected to redis.') |
45 | |||
46 | this.connected = true | ||
47 | }).catch(err => { | ||
48 | logger.error('Cannot connect to redis', { err }) | ||
49 | process.exit(-1) | ||
50 | }) | ||
49 | 51 | ||
50 | this.prefix = 'redis-' + WEBSERVER.HOST + '-' | 52 | this.prefix = 'redis-' + WEBSERVER.HOST + '-' |
51 | } | 53 | } |
52 | 54 | ||
53 | static getRedisClientOptions () { | 55 | static getRedisClientOptions () { |
54 | return Object.assign({}, | 56 | let config: RedisClientOptions<RedisModules, {}> = { |
55 | (CONFIG.REDIS.AUTH && CONFIG.REDIS.AUTH != null) ? { password: CONFIG.REDIS.AUTH } : {}, | 57 | socket: { |
56 | (CONFIG.REDIS.DB) ? { db: CONFIG.REDIS.DB } : {}, | 58 | connectTimeout: 20000 // Could be slow since node use sync call to compile PeerTube |
57 | (CONFIG.REDIS.HOSTNAME && CONFIG.REDIS.PORT) | 59 | } |
58 | ? { host: CONFIG.REDIS.HOSTNAME, port: CONFIG.REDIS.PORT } | 60 | } |
59 | : { path: CONFIG.REDIS.SOCKET } | 61 | |
60 | ) | 62 | if (CONFIG.REDIS.AUTH) { |
63 | config = { ...config, password: CONFIG.REDIS.AUTH } | ||
64 | } | ||
65 | |||
66 | if (CONFIG.REDIS.DB) { | ||
67 | config = { ...config, database: CONFIG.REDIS.DB } | ||
68 | } | ||
69 | |||
70 | if (CONFIG.REDIS.HOSTNAME && CONFIG.REDIS.PORT) { | ||
71 | config.socket = { ...config.socket, host: CONFIG.REDIS.HOSTNAME, port: CONFIG.REDIS.PORT } | ||
72 | } else { | ||
73 | config.socket = { ...config.socket, path: CONFIG.REDIS.SOCKET } | ||
74 | } | ||
75 | |||
76 | return config | ||
61 | } | 77 | } |
62 | 78 | ||
63 | getClient () { | 79 | getClient () { |
@@ -68,6 +84,10 @@ class Redis { | |||
68 | return this.prefix | 84 | return this.prefix |
69 | } | 85 | } |
70 | 86 | ||
87 | isConnected () { | ||
88 | return this.connected | ||
89 | } | ||
90 | |||
71 | /* ************ Forgot password ************ */ | 91 | /* ************ Forgot password ************ */ |
72 | 92 | ||
73 | async setResetPasswordVerificationString (userId: number) { | 93 | async setResetPasswordVerificationString (userId: number) { |
@@ -146,25 +166,6 @@ class Redis { | |||
146 | return this.exists(this.generateTrackerBlockIPKey(ip)) | 166 | return this.exists(this.generateTrackerBlockIPKey(ip)) |
147 | } | 167 | } |
148 | 168 | ||
149 | /* ************ API cache ************ */ | ||
150 | |||
151 | async getCachedRoute (req: express.Request) { | ||
152 | const cached = await this.getObject(this.generateCachedRouteKey(req)) | ||
153 | |||
154 | return cached as CachedRoute | ||
155 | } | ||
156 | |||
157 | setCachedRoute (req: express.Request, body: any, lifetime: number, contentType?: string, statusCode?: number) { | ||
158 | const cached: CachedRoute = Object.assign( | ||
159 | {}, | ||
160 | { body: body.toString() }, | ||
161 | (contentType) ? { contentType } : null, | ||
162 | (statusCode) ? { statusCode: statusCode.toString() } : null | ||
163 | ) | ||
164 | |||
165 | return this.setObject(this.generateCachedRouteKey(req), cached, lifetime) | ||
166 | } | ||
167 | |||
168 | /* ************ Video views stats ************ */ | 169 | /* ************ Video views stats ************ */ |
169 | 170 | ||
170 | addVideoViewStats (videoId: number) { | 171 | addVideoViewStats (videoId: number) { |
@@ -275,12 +276,19 @@ class Redis { | |||
275 | return this.deleteKey('resumable-upload-' + uploadId) | 276 | return this.deleteKey('resumable-upload-' + uploadId) |
276 | } | 277 | } |
277 | 278 | ||
278 | /* ************ Keys generation ************ */ | 279 | /* ************ AP ressource unavailability ************ */ |
279 | 280 | ||
280 | generateCachedRouteKey (req: express.Request) { | 281 | async addAPUnavailability (url: string) { |
281 | return req.method + '-' + req.originalUrl | 282 | const key = this.generateAPUnavailabilityKey(url) |
283 | |||
284 | const value = await this.increment(key) | ||
285 | await this.setExpiration(key, AP_CLEANER.PERIOD * 2) | ||
286 | |||
287 | return value | ||
282 | } | 288 | } |
283 | 289 | ||
290 | /* ************ Keys generation ************ */ | ||
291 | |||
284 | private generateLocalVideoViewsKeys (videoId?: Number) { | 292 | private generateLocalVideoViewsKeys (videoId?: Number) { |
285 | return { setKey: `local-video-views-buffer`, videoKey: `local-video-views-buffer-${videoId}` } | 293 | return { setKey: `local-video-views-buffer`, videoKey: `local-video-views-buffer-${videoId}` } |
286 | } | 294 | } |
@@ -317,128 +325,52 @@ class Redis { | |||
317 | return 'contact-form-' + ip | 325 | return 'contact-form-' + ip |
318 | } | 326 | } |
319 | 327 | ||
328 | private generateAPUnavailabilityKey (url: string) { | ||
329 | return 'ap-unavailability-' + sha256(url) | ||
330 | } | ||
331 | |||
320 | /* ************ Redis helpers ************ */ | 332 | /* ************ Redis helpers ************ */ |
321 | 333 | ||
322 | private getValue (key: string) { | 334 | private getValue (key: string) { |
323 | return new Promise<string>((res, rej) => { | 335 | return this.client.get(this.prefix + key) |
324 | this.client.get(this.prefix + key, (err, value) => { | ||
325 | if (err) return rej(err) | ||
326 | |||
327 | return res(value) | ||
328 | }) | ||
329 | }) | ||
330 | } | 336 | } |
331 | 337 | ||
332 | private getSet (key: string) { | 338 | private getSet (key: string) { |
333 | return new Promise<string[]>((res, rej) => { | 339 | return this.client.sMembers(this.prefix + key) |
334 | this.client.smembers(this.prefix + key, (err, value) => { | ||
335 | if (err) return rej(err) | ||
336 | |||
337 | return res(value) | ||
338 | }) | ||
339 | }) | ||
340 | } | 340 | } |
341 | 341 | ||
342 | private addToSet (key: string, value: string) { | 342 | private addToSet (key: string, value: string) { |
343 | return new Promise<void>((res, rej) => { | 343 | return this.client.sAdd(this.prefix + key, value) |
344 | this.client.sadd(this.prefix + key, value, err => err ? rej(err) : res()) | ||
345 | }) | ||
346 | } | 344 | } |
347 | 345 | ||
348 | private deleteFromSet (key: string, value: string) { | 346 | private deleteFromSet (key: string, value: string) { |
349 | return new Promise<void>((res, rej) => { | 347 | return this.client.sRem(this.prefix + key, value) |
350 | this.client.srem(this.prefix + key, value, err => err ? rej(err) : res()) | ||
351 | }) | ||
352 | } | 348 | } |
353 | 349 | ||
354 | private deleteKey (key: string) { | 350 | private deleteKey (key: string) { |
355 | return new Promise<void>((res, rej) => { | 351 | return this.client.del(this.prefix + key) |
356 | this.client.del(this.prefix + key, err => err ? rej(err) : res()) | ||
357 | }) | ||
358 | } | ||
359 | |||
360 | private deleteFieldInHash (key: string, field: string) { | ||
361 | return new Promise<void>((res, rej) => { | ||
362 | this.client.hdel(this.prefix + key, field, err => err ? rej(err) : res()) | ||
363 | }) | ||
364 | } | 352 | } |
365 | 353 | ||
366 | private setValue (key: string, value: string, expirationMilliseconds: number) { | 354 | private async setValue (key: string, value: string, expirationMilliseconds: number) { |
367 | return new Promise<void>((res, rej) => { | 355 | const result = await this.client.set(this.prefix + key, value, { PX: expirationMilliseconds }) |
368 | this.client.set(this.prefix + key, value, 'PX', expirationMilliseconds, (err, ok) => { | ||
369 | if (err) return rej(err) | ||
370 | 356 | ||
371 | if (ok !== 'OK') return rej(new Error('Redis set result is not OK.')) | 357 | if (result !== 'OK') throw new Error('Redis set result is not OK.') |
372 | |||
373 | return res() | ||
374 | }) | ||
375 | }) | ||
376 | } | 358 | } |
377 | 359 | ||
378 | private removeValue (key: string) { | 360 | private removeValue (key: string) { |
379 | return new Promise<void>((res, rej) => { | 361 | return this.client.del(this.prefix + key) |
380 | this.client.del(this.prefix + key, err => { | ||
381 | if (err) return rej(err) | ||
382 | |||
383 | return res() | ||
384 | }) | ||
385 | }) | ||
386 | } | ||
387 | |||
388 | private setObject (key: string, obj: { [id: string]: string }, expirationMilliseconds: number) { | ||
389 | return new Promise<void>((res, rej) => { | ||
390 | this.client.hmset(this.prefix + key, obj, (err, ok) => { | ||
391 | if (err) return rej(err) | ||
392 | if (!ok) return rej(new Error('Redis mset result is not OK.')) | ||
393 | |||
394 | this.client.pexpire(this.prefix + key, expirationMilliseconds, (err, ok) => { | ||
395 | if (err) return rej(err) | ||
396 | if (!ok) return rej(new Error('Redis expiration result is not OK.')) | ||
397 | |||
398 | return res() | ||
399 | }) | ||
400 | }) | ||
401 | }) | ||
402 | } | ||
403 | |||
404 | private getObject (key: string) { | ||
405 | return new Promise<{ [id: string]: string }>((res, rej) => { | ||
406 | this.client.hgetall(this.prefix + key, (err, value) => { | ||
407 | if (err) return rej(err) | ||
408 | |||
409 | return res(value) | ||
410 | }) | ||
411 | }) | ||
412 | } | ||
413 | |||
414 | private setValueInHash (key: string, field: string, value: string) { | ||
415 | return new Promise<void>((res, rej) => { | ||
416 | this.client.hset(this.prefix + key, field, value, (err) => { | ||
417 | if (err) return rej(err) | ||
418 | |||
419 | return res() | ||
420 | }) | ||
421 | }) | ||
422 | } | 362 | } |
423 | 363 | ||
424 | private increment (key: string) { | 364 | private increment (key: string) { |
425 | return new Promise<number>((res, rej) => { | 365 | return this.client.incr(this.prefix + key) |
426 | this.client.incr(this.prefix + key, (err, value) => { | ||
427 | if (err) return rej(err) | ||
428 | |||
429 | return res(value) | ||
430 | }) | ||
431 | }) | ||
432 | } | 366 | } |
433 | 367 | ||
434 | private exists (key: string) { | 368 | private exists (key: string) { |
435 | return new Promise<boolean>((res, rej) => { | 369 | return this.client.exists(this.prefix + key) |
436 | this.client.exists(this.prefix + key, (err, existsNumber) => { | 370 | } |
437 | if (err) return rej(err) | ||
438 | 371 | ||
439 | return res(existsNumber === 1) | 372 | private setExpiration (key: string, ms: number) { |
440 | }) | 373 | return this.client.expire(this.prefix + key, ms / 1000) |
441 | }) | ||
442 | } | 374 | } |
443 | 375 | ||
444 | static get Instance () { | 376 | static get Instance () { |