diff --git a/README.md b/README.md index e7155d6..a0d5e30 100644 --- a/README.md +++ b/README.md @@ -18,7 +18,7 @@ ip2region - 最自由的ip地址查询库,ip到地区的映射库,提供Bina **4. 多查询客户端的支持,0.0x毫秒级别的查询** -已经集成的客户端有:java, php, c, python,php扩展(支持linux, php5, php7版本已支持)。 +已经集成的客户端有:java, php, c, python,nodejs,php扩展(支持linux, php5, php7版本已支持)。 提供了两种查询算法,响应时间如下: 客户端/binary算法/b-tree算法/Memory算法: diff --git a/binding/nodejs/README.md b/binding/nodejs/README.md new file mode 100644 index 0000000..4d09712 --- /dev/null +++ b/binding/nodejs/README.md @@ -0,0 +1,3 @@ +# nodejs 客户端 + +现已实现同步查询,异步查询未实现,用法参考 test.js diff --git a/binding/nodejs/ip2region.js b/binding/nodejs/ip2region.js new file mode 100644 index 0000000..3bfbac9 --- /dev/null +++ b/binding/nodejs/ip2region.js @@ -0,0 +1,277 @@ +/** + * ip2region client for nodejs + * + * project: https://github.com/lionsoul2014/ip2region + * + * @author dongyado + * */ +var fs = require('fs'); + +var ipbase = [16777216, 65536, 256, 1]; // for ip2long +var ip2region = {}; +ip2region.db_file_path = null; +ip2region.db_fd = null; + +var totalBlocks = 0; +var firstIndexPtr = 0; +var lastIndexPtr = 0; +var superBlock = new Buffer(8); + +var indexBlockLength = 12; +var totalHeaderLength = 4096; + + +/** + * binary search synchronized + * */ +ip2region.binarySearchSync = function(ip) +{ + var low = 0; + var mid = 0; + var high = totalBlocks; + var dataPos = 0; + var pos = 0; + var sip = 0; + var eip = 0; + var indexBuffer = new Buffer(12); + + if( typeof(ip) == 'string' ) ip = ip2long(ip); + + // binary search + while( low <= high ) { + mid = ((low + high) >> 1); + pos = firstIndexPtr + mid * indexBlockLength; + fs.readSync(this.db_fd, indexBuffer, 0, indexBlockLength, pos); + sip = getLong(indexBuffer, 0); + + //console.log( ' sip : ' + sip + ' eip : ' + eip ); + if ( ip < sip) { + high = mid - 1; + } else { + eip = getLong(indexBuffer, 4); + if ( ip > eip ) { + low = mid + 1; + } else { + dataPos = getLong(indexBuffer, 8); + break; + } + } + } + + // read data + if (dataPos == 0) return null; + var dataLen = ((dataPos >> 24) & 0xFF); + var dataPos = (dataPos & 0x00FFFFFF); + var dataBuffer = new Buffer(dataLen); + + fs.readSync(this.db_fd, dataBuffer, 0, dataLen, dataPos); + + var city_id = getLong(dataBuffer, 0); + var data = dataBuffer.toString('utf8', 4, dataLen); + + //console.log(city_id); + //console.log(data); + + return { city: city_id, region: data }; +} + + +var headerSip = null; +var headerPtr = 0; +var headerLen = 0; + + +/** + * btree search synchronized + * */ +ip2region.btreeSearchSync = function(ip) +{ + var indexBlockBuffer = new Buffer(indexBlockLength); + var headerIndexBuffer = new Buffer(totalHeaderLength); + if( typeof(ip) == 'string' ) ip = ip2long(ip); + + var i = 0; + // header index handler + if (headerSip == null) { + fs.readSync(this.db_fd, headerIndexBuffer, 0, totalHeaderLength, 8); + headerSip = new Array(); + headerPtr = new Array(); + + var startIp = 0; + var dataPtr = 0; + for ( i = 0; i < totalHeaderLength; i += 8) { + startIp = getLong(headerIndexBuffer, i); + dataPtr = getLong(headerIndexBuffer, i + 4); + if ( dataPtr == 0) break; + + headerSip.push(startIp); + headerPtr.push(dataPtr); + headerLen++; // header index size count + } + } + + // first search (in header index) + var low = 0; + var mid = 0; + var high = headerLen; + var sptr = 0; + var eptr = 0; + + while(low <= high) { + mid = ((low + high) >> 1); + + if (ip == headerSip[mid]) { + if ( m > 0) { + sptr = headerPtr[mid - 1]; + eptr = headerPtr[mid]; + } else { + sptr = headerPtr[mid]; + eptr = headerPtr[mid + 1]; + } + break; + } + + if ( ip < headerSip[mid]) { + if (mid == 0) { + sptr = headerPtr[mid]; + eptr = headerPtr[mid + 1]; + break; + } else if ( ip > headerSip[mid - 1]) { + sptr = headerPtr[mid - 1]; + eptr = headerPtr[mid]; + break; + } + high = mid - 1; + } else { + if ( mid == headerLen - 1) { + sptr = headerPtr[mid - 1]; + eptr = headerPtr[mid]; + break; + } else if ( ip <= headerSip[mid + 1]) { + sptr = headerPtr[mid]; + eptr = headerPtr[mid + 1]; + break; + } + low = mid + 1; + } + } + + // match nothing + if (sptr == 0) return null; + + // second search (in index) + var blockLen = eptr - sptr; + var blockBuffer = new Buffer(blockLen + indexBlockLength); + fs.readSync(this.db_fd, blockBuffer, 0, blockLen + indexBlockLength, sptr); + + low = 0; + high = blockLen / indexBlockLength; + + var p = 0; + var sip = 0; + var eip = 0; + var dataPtr = 0; + + while(low <= high) { + mid = ((low + high) >> 1); + p = mid * indexBlockLength; + sip = getLong(blockBuffer, p); + + if (ip < sip) { + high = mid - 1; + } else { + eip = getLong(blockBuffer, p + 4); + if (ip > eip) { + low = mid + 1; + } else { + dataPtr = getLong(blockBuffer, p + 8); + break; + } + } + } + + // read data + if (dataPtr == 0) return null; + var dataLen = ((dataPtr >> 24) & 0xFF); + var dataPtr = (dataPtr & 0x00FFFFFF); + var dataBuffer = new Buffer(dataLen); + + fs.readSync(this.db_fd, dataBuffer, 0, dataLen, dataPtr); + + var city_id = getLong(dataBuffer, 0); + var data = dataBuffer.toString('utf8', 4, dataLen); + + //console.log(city_id); + //console.log(data); + + return { city: city_id, region: data }; +} + + +/** + * convert ip to long (xxx.xxx.xxx.xxx to a integer) + * */ +function ip2long(ip) +{ + var val = 0; + ip.split('.').forEach(function(ele, i){ + val += ipbase[i] * ele; + }); + + return val; +} + + +/** + * get long value from buffer with specified offset + * */ +function getLong(buffer, offset) +{ + var val = ( + (buffer[offset] & 0x000000FF) | + ((buffer[offset + 1] << 8) & 0x0000FF00) | + ((buffer[offset + 2] << 16) & 0x00FF0000) | + ((buffer[offset + 3] << 24) & 0xFF000000) + ); + + // convert to unsigned int + if (val < 0) { + val = val >>> 0; + } + return val; +} + + +exports.create = function(db_path) +{ + if (typeof(db_path) == "undefined" || fs.exists(db_path) ) { + throw("[ip2region] db file not exists : " + db_path); + } + + ip2region.db_file_path = db_path; + + try { + ip2region.db_fd = fs.openSync(ip2region.db_file_path, 'r'); + } catch(e) { + throw("[ip2region] Can not open ip2region.db file , path : " + + ip2region.db_file_path); + } + + // init basic search environment + if (totalBlocks == 0) { + fs.readSync(ip2region.db_fd, superBlock, 0, 8, 0); + firstIndexPtr = getLong(superBlock, 0); + lastIndexPtr = getLong(superBlock, 4); + totalBlocks = (lastIndexPtr - firstIndexPtr) + / indexBlockLength + 1; + } + + return ip2region; +} + + +exports.destroy = function(ip2rObj) +{ + ip2rObj.db_file_path = null; + fs.closeSync(ip2rObj.db_fd); +} diff --git a/binding/nodejs/package.json b/binding/nodejs/package.json new file mode 100644 index 0000000..99068c1 --- /dev/null +++ b/binding/nodejs/package.json @@ -0,0 +1,24 @@ +{ + "name": "ip2region", + "version": "0.0.1", + "description": "ip database ", + "main": "ip2region.js", + "scripts": { + "test": "echo \"Error: no test specified\" && exit 1" + }, + "repository": { + "type": "git", + "url": "https://github.com/lionsoul2016/ip2region" + }, + "keywords": [ + "ip2region", + "ip", + "region" + ], + "author": "dongyado", + "license": "ISC", + "bugs": { + "url": "https://github.com/lionsoul2016/ip2region/issues" + }, + "homepage": "https://github.com/lionsoul2016/ip2region" +} diff --git a/binding/nodejs/test.js b/binding/nodejs/test.js new file mode 100644 index 0000000..ec43d0b --- /dev/null +++ b/binding/nodejs/test.js @@ -0,0 +1,15 @@ +/** + * test program + * */ + +var Ip2region = require('./ip2region'); + +// 1. create a ip2region object +var ipObj = Ip2region.create('../../data/ip2region.db'); + +// 2. use +//console.log(ipObj.binarySearchSync("120.24.78.68")); +console.log(ipObj.btreeSearchSync("120.24.78.68")); + +// 3. destroy +Ip2region.destroy(ipObj);