@smpx/koa-request 1.1.0 → 1.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/GeoIP.js +167 -55
- package/package.json +2 -2
package/GeoIP.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
const
|
|
1
|
+
const mmdb = require('mmdb-lib');
|
|
2
2
|
const https = require('https');
|
|
3
3
|
const zlib = require('zlib');
|
|
4
4
|
const path = require('path');
|
|
@@ -9,17 +9,89 @@ const fileName = 'GeoLite2-City';
|
|
|
9
9
|
const fileUrl = `https://raw.githubusercontent.com/GitSquared/node-geolite2-redist/master/redist/${fileName}.tar.gz`;
|
|
10
10
|
const fileDir = __dirname;
|
|
11
11
|
const filePath = `${fileDir}/${fileName}.mmdb`;
|
|
12
|
+
const ONE_DAY = 24 * 3600 * 1000;
|
|
13
|
+
// eslint-disable-next-line max-len
|
|
14
|
+
const processMs = ((process.env.pm_id || process.pid || Math.floor(Math.random() * 60)) % 60) * 60 * 1000;
|
|
12
15
|
let geoInitPromise;
|
|
16
|
+
let geoInitSynced;
|
|
13
17
|
let geoip;
|
|
18
|
+
let updateTimer;
|
|
14
19
|
|
|
15
|
-
|
|
20
|
+
class LRU {
|
|
21
|
+
constructor() {
|
|
22
|
+
this.maxItems = 10000;
|
|
23
|
+
this.cache = new Map();
|
|
24
|
+
this.oldCache = new Map();
|
|
25
|
+
this._size = 0;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
_set(key, value) {
|
|
29
|
+
this.cache.set(key, value);
|
|
30
|
+
if (this.cache.size >= this.maxItems) {
|
|
31
|
+
this._size = this.cache.size;
|
|
32
|
+
this.oldCache = this.cache;
|
|
33
|
+
this.cache = new Map();
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
get(key) {
|
|
38
|
+
const value = this.cache.get(key);
|
|
39
|
+
if (value !== undefined) return value;
|
|
40
|
+
const oldValue = this.oldCache.get(key);
|
|
41
|
+
if (oldValue) {
|
|
42
|
+
this._set(key, oldValue);
|
|
43
|
+
return oldValue;
|
|
44
|
+
}
|
|
45
|
+
return undefined;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
set(key, value) {
|
|
49
|
+
if (this.cache.has(key)) {
|
|
50
|
+
this.cache.set(key, value);
|
|
51
|
+
}
|
|
52
|
+
else {
|
|
53
|
+
this._size++;
|
|
54
|
+
this._set(key, value);
|
|
55
|
+
}
|
|
56
|
+
return this;
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
const readFile = fs.promises.readFile;
|
|
61
|
+
async function mtime(file) {
|
|
62
|
+
try {
|
|
63
|
+
const stats = await fs.promises.stat(file);
|
|
64
|
+
return stats.mtimeMs || stats.ctimeMs;
|
|
65
|
+
}
|
|
66
|
+
catch (e) {
|
|
67
|
+
return 0;
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
function getMmdbReader(buffer) {
|
|
72
|
+
const cache = new LRU();
|
|
73
|
+
return new mmdb.Reader(buffer, {cache});
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
function validateReader(geoIPReader) {
|
|
77
|
+
try {
|
|
78
|
+
const country = geoIPReader.get('1.2.3.4')?.country?.iso_code;
|
|
79
|
+
return !!country;
|
|
80
|
+
}
|
|
81
|
+
catch (e) {
|
|
82
|
+
console.error(e);
|
|
83
|
+
return false;
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
function downloadNewDatabase() {
|
|
16
88
|
return new Promise((resolve, reject) => {
|
|
17
|
-
https.get(
|
|
89
|
+
https.get(fileUrl, (res) => {
|
|
18
90
|
try {
|
|
19
91
|
const untar = res.pipe(zlib.createGunzip({})).pipe(tar.t());
|
|
20
92
|
untar.on('entry', (entry) => {
|
|
21
93
|
if (entry.path.endsWith('.mmdb')) {
|
|
22
|
-
const dstFilename = path.join(
|
|
94
|
+
const dstFilename = path.join(fileDir, path.basename(entry.path) + '-tmp');
|
|
23
95
|
try {
|
|
24
96
|
entry.pipe(fs.createWriteStream(dstFilename));
|
|
25
97
|
}
|
|
@@ -30,88 +102,128 @@ function save(url, outDir) {
|
|
|
30
102
|
});
|
|
31
103
|
untar.on('error', e => reject(e));
|
|
32
104
|
untar.on('finish', () => {
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
105
|
+
setTimeout(async () => {
|
|
106
|
+
try {
|
|
107
|
+
const newGeoIp = getMmdbReader(await readFile(filePath + '-tmp'));
|
|
108
|
+
if (validateReader(newGeoIp)) {
|
|
109
|
+
resolve(newGeoIp);
|
|
110
|
+
fs.promises.rename(filePath + '-tmp', filePath).catch(console.error);
|
|
111
|
+
}
|
|
112
|
+
else {
|
|
113
|
+
reject(new Error('Invalid GeoIP Database!'));
|
|
114
|
+
}
|
|
36
115
|
}
|
|
37
|
-
|
|
38
|
-
|
|
116
|
+
catch (e) {
|
|
117
|
+
reject(e);
|
|
39
118
|
}
|
|
40
|
-
});
|
|
119
|
+
}, 100);
|
|
41
120
|
});
|
|
42
121
|
}
|
|
43
122
|
catch (error) {
|
|
44
|
-
|
|
123
|
+
reject(error);
|
|
45
124
|
}
|
|
46
125
|
});
|
|
47
126
|
});
|
|
48
127
|
}
|
|
49
128
|
|
|
50
|
-
function mtime(file) {
|
|
51
|
-
return new Promise((resolve) => {
|
|
52
|
-
try {
|
|
53
|
-
fs.stat(file, (err, stats) => {
|
|
54
|
-
if (err) {
|
|
55
|
-
resolve(false);
|
|
56
|
-
}
|
|
57
|
-
else {
|
|
58
|
-
resolve(stats.mtimeMs || stats.ctimeMs);
|
|
59
|
-
}
|
|
60
|
-
});
|
|
61
|
-
}
|
|
62
|
-
catch (e) {
|
|
63
|
-
resolve(false);
|
|
64
|
-
}
|
|
65
|
-
});
|
|
66
|
-
}
|
|
67
|
-
|
|
68
129
|
async function download() {
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
await save(fileUrl, fileDir);
|
|
130
|
+
console.log(`[Request::GeoIP] Downloading ${fileName} ...`);
|
|
131
|
+
try {
|
|
132
|
+
const newGeoIp = await downloadNewDatabase();
|
|
73
133
|
console.log(`[Request::GeoIP] Downloaded ${fileName} !`);
|
|
134
|
+
return newGeoIp;
|
|
135
|
+
}
|
|
136
|
+
catch (e) {
|
|
137
|
+
console.error(e);
|
|
138
|
+
return null;
|
|
74
139
|
}
|
|
75
140
|
}
|
|
76
141
|
|
|
77
142
|
async function updateDb() {
|
|
143
|
+
clearTimeout(updateTimer);
|
|
78
144
|
const modifyTime = await mtime(filePath);
|
|
79
|
-
if (modifyTime < Date.now() -
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
145
|
+
if (modifyTime < Date.now() - 3 * ONE_DAY) {
|
|
146
|
+
const newGeoIp = await download();
|
|
147
|
+
if (newGeoIp) {
|
|
148
|
+
console.log('[Request::GeoIP] Updated GeoIP Database!');
|
|
149
|
+
geoip = newGeoIp;
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
// add some random time in timeout to avoid race condition between multiple processes
|
|
153
|
+
const randomMs = Math.floor(Math.random() * 10000000);
|
|
154
|
+
updateTimer = setTimeout(updateDb, 3 * ONE_DAY + randomMs + processMs);
|
|
155
|
+
updateTimer.unref();
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
async function updateDbOnInit() {
|
|
159
|
+
if (process.env.NODE_ENV !== 'production') return;
|
|
160
|
+
clearTimeout(updateTimer);
|
|
161
|
+
// add some random time in timeout to avoid race condition between multiple processes
|
|
162
|
+
const randomMs = Math.floor(Math.random() * 1000000);
|
|
163
|
+
updateTimer = setTimeout(updateDb, randomMs + processMs);
|
|
164
|
+
updateTimer.unref();
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
async function _geoIpInit() {
|
|
168
|
+
try {
|
|
169
|
+
const newGeoIp = getMmdbReader(await readFile(filePath));
|
|
170
|
+
if (!validateReader(newGeoIp)) {
|
|
171
|
+
throw new Error('Invalid GeoIP file');
|
|
172
|
+
}
|
|
173
|
+
geoip = newGeoIp;
|
|
174
|
+
}
|
|
175
|
+
catch (e) {
|
|
176
|
+
console.error(e);
|
|
177
|
+
const newGeoIp = await download();
|
|
178
|
+
if (newGeoIp) {
|
|
179
|
+
geoip = newGeoIp;
|
|
180
|
+
}
|
|
84
181
|
}
|
|
85
|
-
|
|
86
|
-
timer.unref();
|
|
182
|
+
updateDbOnInit();
|
|
87
183
|
}
|
|
88
184
|
|
|
89
185
|
async function geoIpInit() {
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
186
|
+
if (!geoInitPromise) {
|
|
187
|
+
geoInitPromise = _geoIpInit();
|
|
188
|
+
}
|
|
189
|
+
return geoInitPromise;
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
function _geoIpInitSync() {
|
|
193
|
+
try {
|
|
194
|
+
const newGeoIp = getMmdbReader(fs.readFileSync(filePath));
|
|
195
|
+
if (!validateReader(newGeoIp)) {
|
|
196
|
+
throw new Error('Invalid GeoIP file');
|
|
197
|
+
}
|
|
198
|
+
geoip = newGeoIp;
|
|
199
|
+
updateDbOnInit();
|
|
200
|
+
}
|
|
201
|
+
catch (e) {
|
|
202
|
+
console.error(e);
|
|
203
|
+
geoIpInit().catch((err) => {
|
|
204
|
+
console.error(err);
|
|
205
|
+
});
|
|
94
206
|
}
|
|
95
207
|
}
|
|
96
208
|
|
|
209
|
+
function geoIpInitSync() {
|
|
210
|
+
if (!geoInitSynced) {
|
|
211
|
+
geoInitSynced = true;
|
|
212
|
+
_geoIpInitSync();
|
|
213
|
+
}
|
|
214
|
+
return geoInitSynced;
|
|
215
|
+
}
|
|
216
|
+
|
|
97
217
|
async function getGeoIp() {
|
|
98
218
|
if (!geoip) {
|
|
99
|
-
|
|
100
|
-
geoInitPromise = geoIpInit();
|
|
101
|
-
}
|
|
102
|
-
await geoInitPromise;
|
|
103
|
-
geoInitPromise = null;
|
|
219
|
+
await geoIpInit();
|
|
104
220
|
}
|
|
105
221
|
return geoip;
|
|
106
222
|
}
|
|
107
223
|
|
|
108
224
|
function getGeoIpSync() {
|
|
109
225
|
if (!geoip) {
|
|
110
|
-
|
|
111
|
-
geoip = new maxmind.Reader(buffer);
|
|
112
|
-
if (process.env.NODE_ENV === 'production') {
|
|
113
|
-
updateDb();
|
|
114
|
-
}
|
|
226
|
+
geoIpInitSync();
|
|
115
227
|
}
|
|
116
228
|
return geoip;
|
|
117
229
|
}
|
|
@@ -119,7 +231,7 @@ function getGeoIpSync() {
|
|
|
119
231
|
class GeoIP {
|
|
120
232
|
static async get(ip) {
|
|
121
233
|
try {
|
|
122
|
-
return
|
|
234
|
+
return (await getGeoIp()).get(ip);
|
|
123
235
|
}
|
|
124
236
|
catch (e) {
|
|
125
237
|
console.error('Error getting geoip location', e);
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@smpx/koa-request",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.2.0",
|
|
4
4
|
"description": "Handle basic tasks for koajs",
|
|
5
5
|
"main": "index.js",
|
|
6
6
|
"types": "index.d.ts",
|
|
@@ -35,7 +35,7 @@
|
|
|
35
35
|
"koa-body": "^5.0.0",
|
|
36
36
|
"koa-send": "^5.0.1",
|
|
37
37
|
"koa2-ratelimit": "^1.1.3",
|
|
38
|
-
"
|
|
38
|
+
"mmdb-lib": "^3.0.1",
|
|
39
39
|
"tar": "^7.4.3",
|
|
40
40
|
"ua-parser-js": "^1.0.40"
|
|
41
41
|
},
|