Skip to content

Commit

Permalink
Initial commit of code
Browse files Browse the repository at this point in the history
  • Loading branch information
avoidwork committed Jul 4, 2016
1 parent dd0b29f commit e7308d7
Show file tree
Hide file tree
Showing 11 changed files with 480 additions and 0 deletions.
167 changes: 167 additions & 0 deletions .eslintrc
Original file line number Diff line number Diff line change
@@ -0,0 +1,167 @@
{
"env": {
"browser": false,
"node": true,
"es6": true,
"amd": false
},
"ecmaFeatures": {
"jsx": true,
"superInFunctions": false,
"classes": false,
"modules": [2]
},
"rules": {
"block-scoped-var": [0],
"brace-style": [2, "1tbs", { "allowSingleLine": true }],
"camelcase": [0],
"comma-dangle": [2, "never"],
"comma-spacing": [2],
"comma-style": [2, "last"],
"complexity": [0, 11],
"consistent-return": [2],
"consistent-this": [0, "that"],
"curly": [2, "multi-line"],
"default-case": [2],
"dot-notation": [2, { "allowKeywords": true }],
"eol-last": [2],
"eqeqeq": [2],
"func-names": [0],
"func-style": [0, "declaration"],
"generator-star-spacing": [2, "after"],
"guard-for-in": [0],
"handle-callback-err": [0],
"key-spacing": [2, { "beforeColon": false, "afterColon": true }],
"quotes": [2, "double", "avoid-escape"],
"max-depth": [0, 4],
"max-len": [0, 80, 4],
"max-nested-callbacks": [0, 2],
"max-params": [0, 3],
"max-statements": [0, 10],
"new-parens": [2],
"new-cap": [2, { "capIsNewExceptions": ["ToInteger", "ToObject", "ToPrimitive", "ToUint32"] }],
"newline-after-var": [0],
"no-alert": [2],
"no-array-constructor": [2],
"no-bitwise": [0],
"no-caller": [2],
"no-catch-shadow": [2],
"no-cond-assign": [2],
"no-console": [0],
"no-constant-condition": [1],
"no-continue": [2],
"no-control-regex": [2],
"no-debugger": [2],
"no-delete-var": [2],
"no-div-regex": [0],
"no-dupe-args": [2],
"no-dupe-keys": [2],
"no-duplicate-case": [2],
"no-else-return": [0],
"no-empty": [2],
"no-eq-null": [0],
"no-eval": [2],
"no-ex-assign": [2],
"no-extend-native": [1],
"no-extra-bind": [2],
"no-extra-boolean-cast": [2],
"no-extra-semi": [1],
"no-empty-character-class": [2],
"no-fallthrough": [2],
"no-floating-decimal": [2],
"no-func-assign": [2],
"no-implied-eval": [2],
"no-inline-comments": [0],
"no-inner-declarations": [2, "functions"],
"no-invalid-regexp": [2],
"no-irregular-whitespace": [2],
"no-iterator": [2],
"no-label-var": [2],
"no-labels": [2],
"no-lone-blocks": [2],
"no-lonely-if": [2],
"no-loop-func": [2],
"no-mixed-requires": [0, false],
"no-mixed-spaces-and-tabs": [2, false],
"no-multi-spaces": [2],
"no-multi-str": [2],
"no-multiple-empty-lines": [2, { "max": 2 }],
"no-native-reassign": [0],
"no-negated-in-lhs": [2],
"no-nested-ternary": [0],
"no-new": [2],
"no-new-func": [0],
"no-new-object": [2],
"no-new-require": [0],
"no-new-wrappers": [2],
"no-obj-calls": [2],
"no-octal": [2],
"no-octal-escape": [2],
"no-param-reassign": [0],
"no-path-concat": [0],
"no-plusplus": [0],
"no-process-env": [0],
"no-process-exit": [0],
"no-proto": [2],
"no-redeclare": [2],
"no-regex-spaces": [2],
"no-reserved-keys": [0],
"no-reno-new-funced-modules": [0],
"no-return-assign": [2],
"no-script-url": [2],
"no-self-compare": [0],
"no-sequences": [2],
"no-shadow": [2],
"no-shadow-restricted-names": [2],
"no-spaced-func": [2],
"no-sparse-arrays": [2],
"no-sync": [0],
"no-ternary": [0],
"no-throw-literal": [2],
"no-trailing-spaces": [2],
"no-undef": [2],
"no-undef-init": [2],
"no-undefined": [0],
"no-underscore-dangle": [0],
"no-unreachable": [2],
"no-unused-expressions": [2],
"no-unused-vars": [1, { "vars": "all", "args": "after-used" }],
"no-use-before-define": [2],
"no-void": [0],
"no-warning-comments": [0, { "terms": ["todo", "fixme", "xxx"], "location": "start" }],
"no-with": [2],
"no-extra-parens": [2],
"one-var": [0],
"operator-assignment": [0, "always"],
"operator-linebreak": [2, "after"],
"padded-blocks": [0],
"quote-props": [0],
"radix": [0],
"semi": [2],
"semi-spacing": [2, { "before": false, "after": true }],
"sort-vars": [0],
"keyword-spacing": [2],
"space-before-function-paren": [2, { "anonymous": "always", "named": "always" }],
"space-before-blocks": [0, "always"],
"space-in-brackets": [0, "never", {
"singleValue": true,
"arraysInArrays": false,
"arraysInObjects": false,
"objectsInArrays": true,
"objectsInObjects": true,
"propertyName": false
}],
"space-in-parens": [2, "never"],
"space-infix-ops": [2],
"space-unary-ops": [2, { "words": true, "nonwords": false }],
"spaced-line-comment": [0, "always"],
"strict": [0],
"use-isnan": [2],
"valid-jsdoc": [0],
"valid-typeof": [2],
"vars-on-top": [0],
"wrap-iife": [2],
"wrap-regex": [2],
"yoda": [2, "never", { "exceptRange": true }]
}
}
3 changes: 3 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -35,3 +35,6 @@ jspm_packages

# Optional REPL history
.node_repl_history

.idea

8 changes: 8 additions & 0 deletions .npmignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
/.idea/
/node_modules/
/test/
.eslintrc
.git
.gitignore
.travis.yml
Gruntfile.js
9 changes: 9 additions & 0 deletions .travis.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
language: node_js
node_js:
- "6.2"
- "6.1"
- "6.0"

branches:
only:
- master
27 changes: 27 additions & 0 deletions Gruntfile.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
module.exports = function (grunt) {
grunt.initConfig({
eslint: {
target: ["lib/*.js"]
},
mochaTest : {
options: {
reporter: "spec"
},
test : {
src : ["test/*.js"]
}
},
nsp: {
package: grunt.file.readJSON("package.json")
}
});

// tasks
grunt.loadNpmTasks("grunt-eslint");
grunt.loadNpmTasks("grunt-mocha-test");
grunt.loadNpmTasks("grunt-nsp");

// aliases
grunt.registerTask("test", ["eslint", "mochaTest", "nsp"]);
grunt.registerTask("default", ["test"]);
};
47 changes: 47 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,2 +1,49 @@
# tiny-etag

[![build status](https://secure.travis-ci.org/avoidwork/tiny-parse.svg)](http://travis-ci.org/avoidwork/tiny-parse)

ETag middleware

## Example
```javascript
"use strict";

const http = require("http"),
max = 1000,
seed = Math.floor(Math.random() * max) + 1;

let router = require("woodland")({cacheSize: max, defaultHeaders: {"Cache-Control": "no-cache"}, seed: seed}),
etag = require("tiny-etag")({cacheSize: max, seed: seed});

router.use(etag.middleware).blacklist(etag.middleware);

router.use("/", (req, res) => {
const body = "Hello World!";

res.writeHead(200, {"Content-Type": "text/plain", "ETag": etag.create(body)});
res.end(body);
});

http.createServer(router.route).listen(8000);
```

## API

##### etag ({cacheSize: 1000, seed: random})
Returns an tiny-etag instance

##### create (arg)
Creates a strong ETag value from `arg`

##### hash (arg)
Creates a hash of `arg`, used by `create()`

##### middleware (req, res, next)
Middleware to be used by an http framework

##### register (url, state)
Registers a state in the cache

## License
Copyright (c) 2016 Jason Mulligan
Licensed under the BSD-3 license
15 changes: 15 additions & 0 deletions index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
"use strict";

const path = require("path"),
ETag = require(path.join(__dirname, "lib", "etag.js"));

function factory (config) {
let obj = new ETag(config);

obj.create = obj.create.bind(obj);
obj.middleware = obj.middleware.bind(obj);

return obj;
}

module.exports = factory;
83 changes: 83 additions & 0 deletions lib/etag.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,83 @@
"use strict";

const path = require("path"),
lru = require("tiny-lru"),
mmh3 = require("murmurhash3js").x86.hash32,
parse = require("tiny-parse"),
regex = require(path.join(__dirname, "regex.js")),
max = 1000,
random = Math.floor(Math.random() * max) + 1;

function clone (arg) {
return JSON.parse(JSON.stringify(arg));
}

class ETag {
constructor ({cacheSize = max, seed = random} = {}) {
this.cache = lru(cacheSize);
this.seed = seed;
}

create (arg) {
return "\"" + this.hash(arg) + "\"";
}

hash (arg) {
return mmh3(arg, this.seed);
}

middleware (req, res, next) {
const parsed = parse(req),
implicit = regex.implicitGet.test(req.method);

let cached, headers;

res.on("finish", () => {
if (implicit && !cached && res.statusCode < 400 && res._headers.etag) {
this.register(parsed.href, {
etag: res._headers.etag.replace(/"/g, ""),
headers: clone(res._headers),
timestamp: parseInt(new Date().getTime() / 1000, 10)
});
}
});

if (implicit && !req.headers.range && req.headers["if-none-match"] !== undefined) {
cached = this.cache.get(this.hash(parsed.href));

if (regex.explicitGet.test(req.method) && cached && (req.headers["if-none-match"] || "").replace(/\"/g, "") === cached.etag) {
headers = clone(cached.headers);
headers.age = parseInt(new Date().getTime() / 1000 - cached.timestamp, 10);
res.writeHead(304, headers);
res.end("");
} else {
next();
}
} else {
next();
}
}

register (uri, state) {
let newState = {};

Object.keys(state).forEach(key => {
if (key !== "headers") {
newState[key] = state[key];
} else {
newState[key] = {};
Object.keys(state[key]).forEach(header => {
if (!regex.invalid.test(header)) {
newState[key][header] = state[key][header];
}
});
}
});

this.cache.set(this.hash(uri), newState);

return this;
}
}

module.exports = ETag;
9 changes: 9 additions & 0 deletions lib/regex.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
"use strict";

const regex = {
implicitGet: /^(HEAD|GET|OPTIONS)$/,
explicitGet: /^GET$/i,
invalid: /^(a|cache|connection|content-(d|e|l|m|r)|d|ex|l|p|r|s|t|u|v|w|x)/
};

module.exports = regex;
Loading

0 comments on commit e7308d7

Please sign in to comment.