├── .gitignore ├── GeoIPCountryWhois.csv ├── LICENSE ├── README.markdown ├── geoip.js ├── index.js ├── package.json └── test.js /.gitignore: -------------------------------------------------------------------------------- 1 | mongodb 2 | node_modules 3 | .DS_Store 4 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright Playtomic Inc, 2012. Licensed under the MIT license. Certain portions may come from 3rd parties and carry their own licensing terms and are referenced where applicable. 2 | 3 | This product includes GeoLite data created by MaxMind, available from http://www.maxmind.com 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a 6 | copy of this software and associated documentation files (the "Software"), 7 | to deal in the Software without restriction, including without limitation 8 | the rights to use, copy, modify, merge, publish, distribute, sublicense, 9 | and/or sell copies of the Software, and to permit persons to whom the 10 | Software is furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included 13 | in all copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS 16 | OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL 18 | THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 20 | FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS 21 | IN THE SOFTWARE. -------------------------------------------------------------------------------- /README.markdown: -------------------------------------------------------------------------------- 1 | # Node GeoIP Native 2 | 3 | This package is a lightning-fast, native JavaScript geoip lookup built on [MaxMind](http://www.maxmind.com/)'s free country database. 4 | 5 | It is non-blocking and operates without any IO after initially loading the data into memory. 6 | 7 | Results are 4 - 5 times faster than [geoip-lite](https://github.com/bluesmoon/node-geoip) with the caveat that it takes 2 or 3 times longer to initialize and uses 60 or 70 megabytes memory. 8 | 9 | This is used in production at [Playtomic](https://playtomic.com/) up to about 20,000 times a second. 10 | 11 | Benchmarks on my 2011 Macbook Air whilst running lots of software. The test took the middle 10 results from 20 iterations and averaged them. The APIs are interchangeable so tests were identical. 12 | 13 | geoip-native: average: 1540.3ms / million lookups 14 | geoip-lite: average: 8375.3ms / million lookups 15 | 16 | ## Requires 17 | 18 | 1. Comes with the [standard CSV database by MaxMind](http://www.maxmind.com/app/geolite) which may require updating. 19 | 20 | ## How to use 21 | 1. git clone https://github.com/benlowry/node-geoip-native 22 | 2. cd node-geoip-native 23 | 3. node test.js 24 | 25 | or just ```npm install geoip-native``` 26 | 27 | ## Methods 28 | 29 | Node GeoIP Native provides methods for: 30 | 31 | 1. ```lookup``` performs the lookup, takes the ip address as a parameter 32 | 33 | ## Examples 34 | 35 | var geoip = require("geoip-native"); 36 | var ip = "123.123.123.123"; 37 | geoip.lookup(ip); 38 | console.log("country: " + ip.name + " / " + ip.code); 39 | 40 | // in practice you'd want: 41 | // ip = request.headers["x-forwarded-for"] || request.connection.remoteAddress, 42 | 43 | ### What's missing 44 | Be neat to expand this to include cities. 45 | 46 | ### License 47 | Copyright [Playtomic Inc](https://playtomic.com), 2012. Licensed under the MIT license. Certain portions may come from 3rd parties and carry their own licensing terms and are referenced where applicable. 48 | 49 | This product includes GeoLite data created by MaxMind, available from http://www.maxmind.com 50 | -------------------------------------------------------------------------------- /geoip.js: -------------------------------------------------------------------------------- 1 | var countries = [], 2 | midpoints = [], 3 | numcountries = 0; 4 | 5 | var geoip = module.exports = { 6 | 7 | ready: false, 8 | 9 | lookup: function(ip) { 10 | 11 | if(!geoip.ready) { 12 | return { error: "GeoIP not ready" }; 13 | } 14 | 15 | var ipl = iplong(ip); 16 | 17 | if(ipl == 0) { 18 | return { error: "Invalid ip address " + ip + " -> " + ipl + " as integer" }; 19 | } 20 | 21 | return find(ipl); 22 | } 23 | }; 24 | 25 | function iplong(ip) { 26 | 27 | if(!ip) { 28 | return 0; 29 | } 30 | 31 | ip = ip.toString(); 32 | 33 | if(isNaN(ip) && ip.indexOf(".") == -1) { 34 | return 0; 35 | } 36 | 37 | if(ip.indexOf(".") == -1) { 38 | 39 | try { 40 | ip = parseFloat(ip); 41 | return ip < 0 || ip > 4294967296 ? 0 : ip; 42 | } 43 | catch(s) { 44 | } 45 | } 46 | 47 | var parts = ip.split("."); 48 | 49 | if(parts.length != 4) { 50 | return 0; 51 | } 52 | 53 | var ipl = 0; 54 | 55 | for(var i=0; i<4; i++) { 56 | parts[i] = parseInt(parts[i], 10); 57 | 58 | if(parts[i] < 0 || parts[i] > 255) { 59 | return 0; 60 | } 61 | 62 | ipl += parts[3-i] * (Math.pow(256, i)); 63 | } 64 | 65 | return ipl > 4294967296 ? 0 : ipl; 66 | } 67 | 68 | /** 69 | * A qcuick little binary search 70 | * @param ip the ip we're looking for 71 | * @return {*} 72 | */ 73 | function find(ipl) { 74 | 75 | var mpi = 0; 76 | var n = midpoints[0]; 77 | var step; 78 | var current; 79 | var next; 80 | var prev; 81 | var nn; 82 | var pn; 83 | 84 | while(true) { 85 | 86 | step = midpoints[mpi]; 87 | mpi++; 88 | current = countries[n]; 89 | nn = n + 1; 90 | pn = n - 1; 91 | 92 | next = nn < numcountries ? countries[nn] : null; 93 | prev = pn > -1 ? countries[pn] : null; 94 | 95 | // take another step? 96 | if(step > 0) { 97 | 98 | if(!next || next.ipstart < ipl) { 99 | n += step; 100 | } else { 101 | n -= step; 102 | } 103 | 104 | continue; 105 | } 106 | 107 | // we're either current, next or previous depending on which is closest to ipl 108 | var cd = Math.abs(ipl - current.ipstart); 109 | var nd = next && next.ipstart< ipl ? ipl - next.ipstart : 1000000000; 110 | var pd = prev && prev.ipstart < ipl ? ipl - prev.ipstart : 1000000000; 111 | 112 | // current wins 113 | if(cd < nd && cd < pd) { 114 | return current; 115 | } 116 | 117 | // next wins 118 | if(nd < cd && nd < pd) { 119 | return next; 120 | 121 | } 122 | 123 | // prev wins 124 | return prev; 125 | } 126 | } 127 | 128 | /** 129 | * Prepare the data. This uses the standard free GeoIP CSV database 130 | * from MaxMind, you should be able to update it at any time by just 131 | * overwriting GeoIPCountryWhois.csv with a new version. 132 | */ 133 | (function() { 134 | 135 | var fs = require("fs"); 136 | var sys = require("util"); 137 | var stream = fs.createReadStream(__dirname + "/GeoIPCountryWhois.csv"); 138 | var buffer = ""; 139 | 140 | stream.addListener("data", function(data) { 141 | buffer += data.toString().replace(/"/g, ""); 142 | }); 143 | 144 | stream.addListener("end", function() { 145 | 146 | var entries = buffer.split("\n"); 147 | 148 | for(var i=0; i= 1) { 159 | n = Math.floor(n / 2); 160 | midpoints.push(n); 161 | } 162 | 163 | numcountries = countries.length; 164 | geoip.ready = true; 165 | }); 166 | 167 | }()); 168 | -------------------------------------------------------------------------------- /index.js: -------------------------------------------------------------------------------- 1 | module.exports = require("./geoip.js"); -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "geoip-native", 3 | "description": "A fast, native JavaScript geoip api. This product includes GeoLite data created by MaxMind, available from http://www.maxmind.com", 4 | "keywords": [ 5 | "geoip", 6 | "iplookup", 7 | "ip", 8 | "country" 9 | ], 10 | "version": "0.0.7", 11 | "author": { 12 | "name": "Playtomic Inc" 13 | }, 14 | "repository": { 15 | "type": "git", 16 | "url": "https://github.com/benlowry/node-geoip-native.git" 17 | }, 18 | "engines": { 19 | "node": ">=0.6.0" 20 | }, 21 | "main" : "./index.js", 22 | "licenses": [ 23 | { 24 | "type": "MIT", 25 | "url": "http://opensource.org/licenses/mit-license.php" 26 | } 27 | ], 28 | "readme": "# Node GeoIP Native\n\nThis package is a lightning-fast, native JavaScript geoip lookup built on [MaxMind](http://www.maxmind.com/)'s free country database.\n\nIt is non-blocking and operates without any IO after initially loading the data into memory.\n\nResults are 4 - 5 times faster than [geoip-lite](https://github.com/bluesmoon/node-geoip) with the caveat that it takes 2 or 3 times longer to initialize and uses 60 or 70 megabytes memory.\n\nThis is used in production at [Playtomic](https://playtomic.com/) in a [high volume API](https://success.heroku.com/playtomic) where performance matters.\n\nBenchmarks on my 2011 Macbook Air whilst running lots of software. The test took the middle 10 results from 20 iterations and averaged them. The APIs are interchangeable so tests were identical.\n\n\tgeoip-native:\taverage: 1540.3ms / million lookups\n\tgeoip-lite: \taverage: 8375.3ms / million lookups\n\n## Requires\n\n1. Comes with the [standard CSV database by MaxMind](http://www.maxmind.com/app/geolite) which may require updating.\n\n## How to use\n1. git clone https://github.com/benlowry/node-geoip-native\n2. cd node-geoip-native\n3. node test.js\n\nor just ```npm install geoip-native```\n\n## Methods\n\nNode GeoIP Native provides methods for:\n\n1. ```lookup``` performs the lookup, takes the ip address as a parameter\n\n## Examples\n\n\tvar geoip = require(\"geoip-native\");\n\tvar ip = \"123.123.123.123\";\n\tgeoip.lookup(ip);\n\tconsole.log(\"country: \" + ip.name + \" / \" + ip.code);\n\n\t// in practice you'd want:\n\t// ip = request.headers[\"x-forwarded-for\"] || request.connection.remoteAddress,\n\n### What's missing\nBe neat to expand this to include cities.\n\n### License\nCopyright [Playtomic Inc](https://playtomic.com), 2012. Licensed under the MIT license. Certain portions may come from 3rd parties and carry their own licensing terms and are referenced where applicable.\n\nThis product includes GeoLite data created by MaxMind, available from http://www.maxmind.com\n", 29 | "_id": "geoip-native@0.0.2", 30 | "_from": "geoip-native" 31 | } 32 | -------------------------------------------------------------------------------- /test.js: -------------------------------------------------------------------------------- 1 | var geoip = require("./geoip.js"); 2 | //var geoip = require("geoip-lite"); 3 | 4 | var test1 = true; 5 | 6 | function test() { 7 | 8 | var total = 0; 9 | var numtests = 20; 10 | var numiterations = 1000000; 11 | 12 | console.log("starting test: " + (test1 ? "geoip-native" : "geoip-lite")); 13 | 14 | for(var t=0; t 4 && t < 15) { 31 | total += (finish - start); 32 | console.log("time " + (finish - start)); 33 | } 34 | } 35 | 36 | console.log("average: " + (total / 10)); 37 | 38 | if(!test1) { 39 | return; 40 | } 41 | 42 | geoip = require("geoip-lite"); 43 | test1 = false; 44 | test(); 45 | } 46 | 47 | setTimeout(test, 3000); 48 | 49 | /* 50 | benchmark results: 51 | 52 | geoip-native 53 | time 1500 54 | time 1824 55 | time 1526 56 | time 1495 57 | time 1543 58 | time 1509 59 | time 1511 60 | time 1492 61 | time 1505 62 | time 1498 63 | average: 1540.3 64 | 65 | geoip-lite 66 | time 8339 67 | time 8335 68 | time 8314 69 | time 8327 70 | time 8631 71 | time 8315 72 | time 8512 73 | time 8303 74 | time 8416 75 | time 8261 76 | average: 8375.3*/ --------------------------------------------------------------------------------