-
Notifications
You must be signed in to change notification settings - Fork 0
/
index.js
178 lines (156 loc) · 4.24 KB
/
index.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
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
const express = require("express");
const app = express();
const port = 3000;
var logger = require("morgan");
// logs for development
app.use(logger("dev"));
// parse application/json
app.use(express.json());
app.use(express.urlencoded({ extended: false }));
let configuration = {
routes: [],
clients: [],
};
/**
* SCHEMA
* configuration = {
* routes: [{
* sourcePath: string,
* destinationUrl: number,
* }],
* clients: [{
* clientId: string,
* limit: number,
* seconds: number,
* }]
* }
*/
let rateLimitData = {};
/**
* SCHEMA
* rateLimitData = [
* clientId[string]: {
* requestCount: number,
* startingTime: Date,
* }
* ]
*/
/* POST - set configuration. */
app.post("/configure", (request, response) => {
const { routes, clients } = request.body;
for (var i = 0; i < routes.length; i++) {
const route = routes[i];
if (
"sourcePath" in route &&
"destinationUrl" in route &&
typeof route.sourcePath === "string" &&
typeof route.destinationUrl === "string"
) {
if (route.sourcePath.startsWith("/configure")) {
// Invalid sourcePath.
return response.sendStatus(400);
}
} else {
// Invalid route object.
return response.sendStatus(400);
}
}
for (var j = 0; j < clients.length; j++) {
const client = clients[j];
if ("clientId" in client && typeof client.clientId === "string") {
let defaultValue = 1;
if (client.limit === undefined) {
client.limit = defaultValue;
} else if (typeof client.limit !== "number") {
// Invalid client object.
return response.sendStatus(400);
}
if (client.seconds === undefined) {
client.seconds = defaultValue;
} else if (typeof client.seconds !== "number") {
// Invalid client object.
return response.sendStatus(400);
}
} else {
// Invalid client object.
return response.sendStatus(400);
}
}
configuration = {
routes,
clients,
};
console.log("configuration", configuration);
// Configuration Implemented Successfully.
return response.sendStatus(200);
});
function rateLimitChecker(client) {
// This function checks if the request made with a particular clientId should be throttled or not.
if (client.limit === 0) {
return {
isValid: false,
};
}
const rateLimitObject = rateLimitData[client.clientId];
if (rateLimitObject === undefined) {
rateLimitData[client.clientId] = {
requestCount: 1,
startingTime: new Date(),
};
} else {
const timeNow = new Date();
var diff = timeNow.getTime() - rateLimitObject.startingTime.getTime();
var diff_seconds = Math.abs(diff / 1000);
if (diff_seconds > client.seconds) {
rateLimitData[client.clientId] = {
requestCount: 1,
startingTime: new Date(),
};
} else if (rateLimitObject.requestCount >= client.limit) {
return {
isValid: false,
};
} else {
rateLimitObject.requestCount += 1;
}
}
return {
isValid: true,
};
}
/* GET - redirect requests. */
app.get("/*", function (request, response) {
// since each request is required to have "client-id" header, we will check it first.
const client_id = request.headers["client-id"];
if (!client_id) {
// ClientId not found.
return response.sendStatus(400);
}
if (configuration.clients.length > 0) {
// since configuration.clients.length is greater than 0, it means rate-limits need to be applied.
const client = configuration.clients.find((c) => {
return c.clientId === client_id;
});
if (client === undefined) {
// Invalid clientId.
return response.sendStatus(403);
}
const rateLimitCheckerResponse = rateLimitChecker(client);
if (rateLimitCheckerResponse.isValid === false) {
// Limit exceeded.
return response.sendStatus(429);
}
}
const route = configuration.routes.find((r) => {
return r.sourcePath === request.path;
});
if (route) {
return response.redirect(302, route.destinationUrl);
} else {
// Invalid path.
return response.sendStatus(404);
}
});
app.listen(port, () => {
console.log(`Listening on port ${port}`);
});