Skip to content

Commit 948bce8

Browse files
author
Carlos R. L. Rodrigues
committed
no message
1 parent ee26286 commit 948bce8

File tree

10 files changed

+495
-0
lines changed

10 files changed

+495
-0
lines changed

.gitignore

Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,62 @@
1+
.vscode
2+
dist/
3+
4+
# Logs
5+
logs
6+
*.log
7+
npm-debug.log*
8+
yarn-debug.log*
9+
yarn-error.log*
10+
11+
# Runtime data
12+
pids
13+
*.pid
14+
*.seed
15+
*.pid.lock
16+
17+
# Directory for instrumented libs generated by jscoverage/JSCover
18+
lib-cov
19+
20+
# Coverage directory used by tools like istanbul
21+
coverage
22+
23+
# nyc test coverage
24+
.nyc_output
25+
26+
# Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files)
27+
.grunt
28+
29+
# Bower dependency directory (https://bower.io/)
30+
bower_components
31+
32+
# node-waf configuration
33+
.lock-wscript
34+
35+
# Compiled binary addons (http://nodejs.org/api/addons.html)
36+
build/Release
37+
38+
# Dependency directories
39+
node_modules/
40+
jspm_packages/
41+
42+
# Typescript v1 declaration files
43+
typings/
44+
45+
# Optional npm cache directory
46+
.npm
47+
48+
# Optional eslint cache
49+
.eslintcache
50+
51+
# Optional REPL history
52+
.node_repl_history
53+
54+
# Output of 'npm pack'
55+
*.tgz
56+
57+
# Yarn Integrity file
58+
.yarn-integrity
59+
60+
# dotenv environment variables file
61+
.env
62+

README.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
# node-cache-layer
2+
Cache layer for express framework

cache-config.json

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
{
2+
"prefix": "cache_",
3+
"handler": "",
4+
"status": [
5+
200
6+
],
7+
"default_expire": 86400,
8+
"rules": [
9+
{
10+
"name": "Home",
11+
"group": "static",
12+
"route": "/test",
13+
"regex": "test/[0-9]+",
14+
"ignore_case": false,
15+
"expire": 10,
16+
"methods": [ "OPTIONS", "GET", "HEAD", "POST", "PUT", "DELETE", "TRACE", "CONNECT" ],
17+
"variables": {
18+
"params": [ "product_id", "id" ],
19+
"query": [
20+
"get_var",
21+
{
22+
"value": "search",
23+
"ignore_case": true
24+
},
25+
"abc"
26+
],
27+
"cookies": [ "cookie_name" ],
28+
"body": [ "post_var", "username" ],
29+
"session": [ "post_var", "username" ],
30+
"header": [ "Authorization" ],
31+
"subdomains": [ "static" ]
32+
}
33+
}
34+
]
35+
}

index.ts

Lines changed: 85 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,85 @@
1+
import { config, updateConfig, loadHandler } from "./src/config"
2+
import { RouteCheck } from "./src/RouteCheck"
3+
4+
export default (options: any = {}) => {
5+
updateConfig(options);
6+
7+
const module = loadHandler(config);
8+
const cacheControl = new module.Handler(config);
9+
10+
return (req, res, next) => {
11+
const route = new RouteCheck(req);
12+
if (!route.isCacheable)
13+
return next();
14+
15+
const end = res.end;
16+
const write = res.write;
17+
const cache = [];
18+
const key = route.key;
19+
const expire = route.expire;
20+
21+
cacheControl.get(key, (error, reply) => {
22+
if (error) {
23+
return next();
24+
}
25+
else if (reply) {
26+
// TESTTEEEEE
27+
reply.header["x-cached"] = "1";
28+
res.set(reply.header);
29+
30+
res.removeHeader("X-Powered-By");
31+
32+
return res.end(reply.content, reply.encoding);
33+
}
34+
else {
35+
res.write = function (chunk, encoding) {
36+
if(typeof chunk == "string") {
37+
chunk = Buffer.from(chunk, encoding);
38+
}
39+
40+
cache.push(chunk);
41+
write.call(res, chunk, encoding);
42+
}
43+
44+
res.end = function (chunk, encoding) {
45+
if (chunk)
46+
this.write(chunk, encoding);
47+
48+
if(!~config.status.indexOf(res.statusCode)){
49+
end.call(res);
50+
return;
51+
}
52+
53+
// TESTEEEE
54+
cache.push(Buffer.from("<hr /><b>cac<font color='red'>hed</font></b>", encoding));
55+
56+
const now = new Date();
57+
let dateExpire = new Date();
58+
dateExpire.setSeconds(now.getSeconds() + expire);
59+
60+
const rawUrl = req.originalUrl;
61+
const buf = Buffer.concat(cache);
62+
63+
const headers = route.mergeHeaders(
64+
res._headers,
65+
{
66+
"Content-Length": buf.length,
67+
"Last-Modified": now.toUTCString(),
68+
"Cache-Control": "max-age=" + (dateExpire.getTime() / 1000)
69+
}
70+
);
71+
72+
cacheControl.set(
73+
key,
74+
buf,
75+
headers,
76+
rawUrl,
77+
expire
78+
);
79+
end.call(res);
80+
};
81+
return next();
82+
}
83+
});
84+
}
85+
}

package.json

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
{
2+
"name": "node-cache-layer",
3+
"version": "0.0.1",
4+
"description": "Extensible cache layer for express framework.",
5+
"main": "dist/index",
6+
"typings": "dist/index",
7+
"scripts": {
8+
"prepublishOnly": "npm run compile",
9+
"compile": "tsc -p ."
10+
},
11+
"bugs": {
12+
"url": "http://github.com/JSFromHell/node-cache-layer/issues"
13+
},
14+
"repository": {
15+
"type": "git",
16+
"url": "http://github.com/JSFromHell/node-cache-layer.git"
17+
},
18+
"keywords": [
19+
"cache",
20+
"cache layer",
21+
"node cache layer",
22+
"nosql cache",
23+
"express cache",
24+
"response cache"
25+
],
26+
"author": "Carlos R. L. Rodrigues",
27+
"license": "MIT",
28+
"devDependencies": {
29+
"@types/node": "^8.0.0",
30+
"typescript": "^2.6.1"
31+
},
32+
"files": [
33+
"dist/*"
34+
]
35+
}

src/RouteCheck.ts

Lines changed: 143 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,143 @@
1+
import { config } from "./config"
2+
import { hash } from "./hash"
3+
import * as url from "url"
4+
5+
export class RouteCheck {
6+
private _key: string;
7+
private _expire: number;
8+
private _url: string;
9+
private _original: string;
10+
11+
private _isCacheable: boolean = false;
12+
constructor(private req) {
13+
this._original = url.parse(req.originalUrl).pathname;
14+
if(config.ignore_case) {
15+
this._original = this._original.toLocaleLowerCase();
16+
}
17+
18+
const mayCache = this.matchesRule();
19+
if (mayCache) {
20+
if (typeof config.isCacheable == "function") {
21+
this._isCacheable = config.isCacheable(this.req);
22+
}
23+
else {
24+
this._isCacheable = true;
25+
}
26+
}
27+
}
28+
29+
private getNormalizedKey(): string {
30+
const vars = config.variables;
31+
32+
let baseUrl: string[] = [
33+
this._original,
34+
this.req.method
35+
];
36+
let param: string[] = [];
37+
38+
for (let o in vars) {
39+
if (o in this.req) {
40+
const values = this.req[o];
41+
const type = vars[o];
42+
for (let i = 0; i < type.length; i++) {
43+
let n: string;
44+
let v: string;
45+
if (typeof type[i] == "object") {
46+
[n, v] = [type[i].value, values[n]];
47+
48+
if (typeof v == "undefined") {
49+
continue;
50+
}
51+
52+
if (type[i].ignore_case) {
53+
v = v.toLocaleLowerCase();
54+
}
55+
}
56+
else {
57+
[n, v] = [type[i], values[i]];
58+
59+
if (typeof v == "undefined") {
60+
continue;
61+
}
62+
}
63+
param.push(n + "=" + v);
64+
}
65+
}
66+
}
67+
68+
if (param.length) {
69+
baseUrl.push("?");
70+
}
71+
return baseUrl.join("_") + param.join("&");
72+
}
73+
74+
private hasMethod(rule: any): boolean {
75+
if (rule.methods && rule.methods != "*") {
76+
if (!~rule.methods.indexOf(this.req.method)) {
77+
return false;
78+
}
79+
}
80+
return true;
81+
}
82+
83+
private matchesRule(): boolean {
84+
let url = this._original;
85+
let has: boolean = false;
86+
87+
let rule;
88+
for (rule of config.rules) {
89+
if(!this.hasMethod(rule))
90+
continue;
91+
92+
if (rule.regex && rule.regex.test(url)) {
93+
has = true;
94+
break;
95+
}
96+
else if (rule.route) {
97+
if (rule.route == url) {
98+
has = true;
99+
break;
100+
}
101+
}
102+
}
103+
if (has) {
104+
this.useMatchedRule(rule);
105+
}
106+
return has;
107+
}
108+
109+
private useMatchedRule(rule) {
110+
let group = rule.group ? rule.group + "_" : "";
111+
this._url = this.getNormalizedKey();
112+
this._key = group + hash(this._url);
113+
this._expire = rule.expire;
114+
}
115+
116+
get isCacheable(): boolean {
117+
return this._isCacheable;
118+
}
119+
120+
get key(): string {
121+
return this._key;
122+
}
123+
124+
get expire(): number {
125+
return this._expire;
126+
}
127+
128+
get normalizedKey(): string {
129+
return this._url;
130+
}
131+
132+
mergeHeaders(original: any, newValues: any): any {
133+
let head = Object.assign({}, original);
134+
135+
for (let o in head) {
136+
let n = o.toLocaleLowerCase();
137+
if (n in newValues)
138+
delete head[o];
139+
}
140+
141+
return Object.assign(head, newValues);
142+
}
143+
}

0 commit comments

Comments
 (0)