-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathserver.js
91 lines (73 loc) · 2.64 KB
/
server.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
import Koa from 'koa';
import Router from 'koa-router';
import serve from 'koa-static';
import snapshot from './app/lib/snapshot.js';
import winston from 'winston';
import crypto from 'crypto';
import fs from 'fs';
import { RateLimiterMemory } from 'rate-limiter-flexible';
const rateLimiter = new RateLimiterMemory({points: 3, duration: 1});
const devicesContent = fs.readFileSync('./src/emulateDevices.json', 'utf8');
const devices = JSON.parse(devicesContent);
const logger = winston.createLogger({
level: 'info',
format: winston.format.combine(winston.format.timestamp(), winston.format.json()),
transports: [new winston.transports.Console()],
});
const app = new Koa();
const router = new Router();
const cacheDir = process.env.CACHE_DIR || '/tmp/url-snapshot';
const metaDir = cacheDir + '/meta';
if (!fs.existsSync(metaDir)) {
fs.mkdirSync(metaDir);
}
const buildeSnapshotOption = ctx => {
return {
full: ctx.query.full === 'true',
toBase64: ctx.query.toBase64 === 'true',
width: ctx.query.width ? parseInt(ctx.query.width) : undefined,
height: ctx.query.height ? parseInt(ctx.query.height) : undefined,
userAgent: devices.find(device => device.title === ctx.query.device)?.['user-agent'],
}
}
router.get('/api/snapshot', async ctx => {
if (!ctx.query.url) {
ctx.status = 400;
ctx.body = 'url is required';
return;
}
logger.info(`Snapshot taken for ${ctx.query.url}`);
ctx.set('Content-Type', 'image/png');
const option = buildeSnapshotOption(ctx);
const urlDigest = crypto.createHash('md5').update(ctx.query.url + JSON.stringify(option)).digest('hex');
const cahcedImage = cacheDir + '/' + urlDigest + '.png';
// Check cache first
if (fs.existsSync(cahcedImage)) {
logger.info(`Snapshot cache hit for ${ctx.query.url}`);
ctx.body = fs.createReadStream(cahcedImage);
return;
}
// Take snapshot for the url
const image = await snapshot(decodeURIComponent(ctx.query.url), option);
logger.info(`Snapshot complete for ${ctx.query.url}`);
// Cache the snapshot
fs.writeFileSync(cahcedImage, image);
logger.info(`Snapshot cached for ${ctx.query.url}`);
// Cache the snapshot meta
fs.writeFileSync(metaDir + '/' + urlDigest, JSON.stringify({url: ctx.query.url, createdAt: new Date(), option}));
logger.info(`Snapshot meta cached for ${ctx.query.url}`);
ctx.body = Buffer.alloc(image.length, image);
})
app.use(router.routes());
app.use(serve('build'));
app.use(async (ctx, next) => {
try {
await rateLimiter.consume(ctx.ip);
await next();
} catch (e) {
ctx.status = 429;
ctx.body = 'Too many requests';
}
})
logger.info('Server listend: 0.0.0.0:30001')
app.listen(3001);