Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
44 commits
Select commit Hold shift + click to select a range
60852bc
renamed test.js to benchmark.js (which is more correct)
nitram509 Oct 21, 2012
b79555d
added a NodeUnit test for proving correctness
nitram509 Oct 21, 2012
1d5086e
renamed lookup.js to lookupTest.js
nitram509 Oct 31, 2012
0eaa066
added some hints about nodeunit
nitram509 Oct 31, 2012
1188781
cosmetics
nitram509 Oct 31, 2012
51d7aec
cosmetics
nitram509 Oct 31, 2012
101f943
reordered paragraphs
nitram509 Oct 31, 2012
f1e93b7
added IntelliJIdea working files to git ignore list
nitram509 Oct 31, 2012
cb89a8f
made separate test cases for each corner case
nitram509 Oct 31, 2012
115770b
FIX: wrong calculation of midpoints
nitram509 Oct 31, 2012
3f5eaf3
only reformatting
nitram509 Oct 31, 2012
c3dac81
only reformatting and renaming variables for better readability
nitram509 Oct 31, 2012
32ad6cd
FIX: lower IP ranges, leftmost and rightmost borders are correctly de…
nitram509 Oct 31, 2012
24a1d33
reformatted code, only
nitram509 Nov 4, 2012
a843b2c
renamed variable
nitram509 Nov 4, 2012
ea898f5
generating country maps and lists and saving them to .JS source files
nitram509 Nov 4, 2012
e918eb9
reworked benchmark.js, so it's no more recursive and easier to unders…
nitram509 Nov 4, 2012
716bdd9
Merge branch 'master' into code_generation
nitram509 Nov 4, 2012
6085abd
reworked benchmark.js, by using generated sources there's no need to …
nitram509 Nov 4, 2012
3a58a3b
direct array generation speeds up loading of the source file
nitram509 Nov 4, 2012
9a4384d
direct array generation speeds up loading of the source file
nitram509 Nov 4, 2012
40099f5
added generate_sources.js as script to build phase within package.json
nitram509 Nov 4, 2012
ee1918b
removed warm up test, not needed anymore
nitram509 Nov 4, 2012
4ddf392
fix: don't write the line twice
nitram509 Nov 4, 2012
68c9e1f
removed warm up test, not needed anymore
nitram509 Nov 4, 2012
bd05731
reworked binary search (see wikipedia) and FIXed last 'red' test
nitram509 Nov 4, 2012
b9bb72d
merged new binary search algorithm
nitram509 Nov 4, 2012
a356fa8
simplyfied return statement
nitram509 Nov 4, 2012
1e7969f
updated package.json - fixed preinstall hook
nitram509 Mar 24, 2013
21fee47
shortened the generated code
nitram509 Mar 24, 2013
9021226
fixed example in readme
nitram509 Mar 24, 2013
d5fc3df
fixed example in readme
nitram509 Mar 24, 2013
dbedc8f
cleanups: tools folder for benchmark.js, consistence naming
nitram509 Mar 24, 2013
0ef8603
fixed require statement in benchmark.js
nitram509 Mar 24, 2013
7a4f992
fixed geoip require statement
nitram509 Apr 18, 2013
4ee1fe8
fixed binary search, by using Deferred detection of equality approach…
nitram509 Apr 18, 2013
592baed
fixed range check
nitram509 Apr 27, 2013
c72fe1c
re-ident to two spaces
nitram509 Apr 27, 2013
d8634ca
refactored: extracted methods for better readability
nitram509 Apr 27, 2013
2751af7
improved lookupTest, and uncovered a bug: last country in the list wi…
nitram509 Apr 27, 2013
c74d4df
improved lookupTest, and uncovered one more bug: "Virgin Islands" and…
nitram509 Apr 27, 2013
bfa84f4
Fixed problem of commas in country names (example: "Virgin Island" vs…
nitram509 Apr 27, 2013
3a21f8c
Fixed problem that last records in database weren't resolved correctly
nitram509 Apr 27, 2013
73262d9
added one more assertion for ending range
nitram509 Apr 27, 2013
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1,3 +1,6 @@
mongodb
node_modules
.DS_Store
.idea
*.iml
generated-*.js
34 changes: 23 additions & 11 deletions README.markdown
Original file line number Diff line number Diff line change
Expand Up @@ -17,28 +17,40 @@ Benchmarks on my 2011 Macbook Air whilst running lots of software. The test too

1. Comes with the [standard CSV database by MaxMind](http://www.maxmind.com/app/geolite) which may require updating.

## How to use

## Install

Simple run

npm install geoip-native

## Install from source
1. git clone https://github.com/benlowry/node-geoip-native
2. cd node-geoip-native
3. node test.js

or just ```npm install geoip-native```
3. npm install
4. node benchmark.js

## Methods

Node GeoIP Native provides methods for:

1. ```lookup``` performs the lookup, takes the ip address as a parameter

## Examples
## Testing
First, you have to install nodeunit (https://github.com/caolan/nodeunit)

$> npm install nodeunit

Second, run the unit test:

var geoip = require("geoip-native");
var ip = "123.123.123.123";
geoip.lookup(ip);
console.log("country: " + ip.name + " / " + ip.code);
$> ./node_modules/nodeunit/bin/nodeunit test/unit/lookupTest.js

## Examples

// in practice you'd want:
// ip = request.headers["x-forwarded-for"] || request.connection.remoteAddress,
var geoip = require("geoip-native");
var ip = geoip.lookup("134.12.12.123");
console.log("numeric ip value: " + ip.ipstart);
console.log("country: " + ip.name + " / " + ip.code);

### What's missing
Be neat to expand this to include cities.
Expand Down
124 changes: 124 additions & 0 deletions generate_sources.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,124 @@
"use strict";

var countries = [];
var countryNamesAndCodes = [];

function read_csv_file_and_prepare_data() {

function load_CSV_file() {
var fs = require("fs");
var data = fs.readFileSync(__dirname + "/GeoIPCountryWhois.csv")
var buffer = "";
buffer += data.toString();
return buffer;
}

function extractPartsFromCsv(line) {
var matches = line.match(/("(?:[^"]|"")*"|[^,]*)/g);
var result = [];
for (var i = 0; i < matches.length; i++) {
var part = matches[i].replace(/"/g, "").trim();
if (part.length > 0) {
result.push(part);
}
}
return result;
}

var entries = load_CSV_file().split("\n");
var offsetCounter = 0;
var countryIndex = 0;
var countrySet = {};
var lastIpRangeEnd = 0;

for (var i = 0; i < entries.length; i++) {
var parts = extractPartsFromCsv(entries[i]);
if (parts.length > 5) {
var countryName = parts[5].trim();
var countryCode = parts[4];
if (!countrySet[countryName]) {
countryIndex = offsetCounter++;
countrySet[countryName] = {index: countryIndex};
countryNamesAndCodes.push(countryName);
countryNamesAndCodes.push(countryCode);
} else {
countryIndex = countrySet[countryName].index;
}
countries.push(createCountryInformation(parseInt(parts[2]), parseInt(parts[3]), countryCode, countryName, (countryIndex * 2)));
lastIpRangeEnd = parseInt(parts[3]);
}
}

// add a special country, which indicates the END
countryIndex = offsetCounter;
countrySet["UNKNOWN"] = {index: countryIndex};
countryNamesAndCodes.push("UNKNOWN");
countryNamesAndCodes.push("N/A");
countries.push(
createCountryInformation(lastIpRangeEnd + 1, lastIpRangeEnd + 1, "N/A", "UNKNOWN", countryIndex * 2)
);

countries.sort(function (a, b) {
return a.ipstart - b.ipstart;
});

}

function createCountryInformation(ipStart, ipEnd, countryCode, countryName, index) {
return {
ipstart: ipStart,
ipend: ipEnd,
code: countryCode,
name: countryName,
index: index
};
}


function write_sourceFile_countryNamesAndCodes() {
var data = "";
data += "// AUTOGENERATED CODE - DO NOT MODIFY! " + new Date() + " \n";
data += "var countryNamesAndCodes = [\n";
for (var i = 0, len = countryNamesAndCodes.length >> 1; i < len; i++) {
var countryName = countryNamesAndCodes[i << 1].replace("'", "\\'");
var countryCode = countryNamesAndCodes[(i << 1) + 1].replace("'", "\\'");
data += "'" + countryName + "','" + countryCode + "',\n";
}
data += "];\n";
data += "module.exports = {\n";
data += " countryNamesAndCodes:countryNamesAndCodes\n";
data += "};\n";

var fs = require("fs");
fs.writeFileSync('generated-namesandcodes.js', data, 'utf8');
}

function write_sourceFile_countries() {
var data = "";
data += "// AUTOGENERATED CODE - DO NOT MODIFY! " + new Date() + " \n";
data += "var countries = [\n";
for (var i = 0, len = countries.length; i < len; i++) {
var country = countries[i];
data += "{s:" + country.ipstart + ",";
data += "e:" + country.ipend + ",";
data += "i:" + country.index + "},";
data += "//" + countryNamesAndCodes[country.index + 1] + "\n";
}
data += "];\n";
data += "module.exports = {\n";
data += " countries:countries\n";
data += "};\n";

var fs = require("fs");
fs.writeFileSync('generated-countries.js', data, 'utf8');
}

/**
* Prepare the data. This uses the standard free GeoIP CSV database
* from MaxMind, you should be able to update it at any time by just
* overwriting GeoIPCountryWhois.csv with a new version.
*/

read_csv_file_and_prepare_data()
write_sourceFile_countryNamesAndCodes();
write_sourceFile_countries();
65 changes: 65 additions & 0 deletions geoip-native.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
var countryNamesAndCodes = require('./generated-namesandcodes.js').countryNamesAndCodes;
var countries = require('./generated-countries.js').countries;
var countriesLength = countries.length;

module.exports = geoip = {
lookup: function (ip) {
return _lookup(ip);
}
};

/**
* @param ip the ip we're looking for
* @return {*}
* @see http://en.wikipedia.org/wiki/Binary_search_algorithm (Deferred detection of equality approach)
*/
function _lookup(ip) {

var parts = ip.split(".");
var target_ip = parseInt(parts[3], 10) +
(parseInt(parts[2], 10) * 256) +
(parseInt(parts[1], 10) * 65536) +
(parseInt(parts[0], 10) * 16777216);

var idxMin = 0;
var idxMiddle = 0;
var idxMax = countriesLength - 1;

while (idxMin < idxMax) {
idxMiddle = (idxMax + idxMin) >> 1;
if (!(idxMiddle < idxMax)) {
throw "assertion error: idxMiddle is not lower then idxMax"
}
if (countries[idxMiddle].s < target_ip) {
idxMin = idxMiddle + 1;
} else {
idxMax = idxMiddle;
}
}

var pickedCountry = countries[idxMin];
if ((idxMax == idxMin) && (pickedCountry.s == target_ip)) {
pickedCountry = countries[idxMin];
return createCountry(pickedCountry.s, pickedCountry.e, countryNamesAndCodes[pickedCountry.i], countryNamesAndCodes[pickedCountry.i + 1]);
}

if ((idxMiddle > 0) && (countries[idxMiddle - 1].s < target_ip) && (target_ip < countries[idxMiddle].s)) {
pickedCountry = countries[idxMiddle - 1]
return createCountry(pickedCountry.s, pickedCountry.e, countryNamesAndCodes[pickedCountry.i], countryNamesAndCodes[pickedCountry.i + 1]);
}

if ((idxMiddle < idxMax) && (countries[idxMiddle].s < target_ip) && (target_ip < countries[idxMiddle + 1].s)) {
pickedCountry = countries[idxMiddle]
return createCountry(pickedCountry.s, pickedCountry.e, countryNamesAndCodes[pickedCountry.i], countryNamesAndCodes[pickedCountry.i + 1]);
}
return createCountry(target_ip, target_ip, "UNKNOWN", "N/A");
}

function createCountry(ipstart, ipend, countryName, countryCode) {
return {
ipstart: ipstart,
ipend: ipend,
name: countryName,
code: countryCode
};
}
122 changes: 0 additions & 122 deletions geoip.js

This file was deleted.

1 change: 0 additions & 1 deletion index.js

This file was deleted.

Loading