aboutsummaryrefslogtreecommitdiffhomepage
path: root/server/lib/redis.ts
diff options
context:
space:
mode:
Diffstat (limited to 'server/lib/redis.ts')
-rw-r--r--server/lib/redis.ts220
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 @@
1import express from 'express' 1import { createClient, RedisClientOptions, RedisModules } from 'redis'
2import { createClient, RedisClient } from 'redis' 2import { exists } from '@server/helpers/custom-validators/misc'
3import { sha256 } from '@shared/extra-utils'
3import { logger } from '../helpers/logger' 4import { logger } from '../helpers/logger'
4import { generateRandomString } from '../helpers/utils' 5import { generateRandomString } from '../helpers/utils'
6import { CONFIG } from '../initializers/config'
5import { 7import {
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'
15import { CONFIG } from '../initializers/config'
16import { exists } from '@server/helpers/custom-validators/misc'
17 18
18type CachedRoute = { 19// Only used for typings
19 body: string 20const redisClientWrapperForType = () => createClient<{}>()
20 contentType?: string
21 statusCode?: string
22}
23 21
24class Redis { 22class 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 () {