diff --git a/binding/c#/BenchmarkTest/App.config b/binding/c#/BenchmarkTest/App.config deleted file mode 100644 index 1ab39519..00000000 --- a/binding/c#/BenchmarkTest/App.config +++ /dev/null @@ -1,34 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/binding/c#/BenchmarkTest/BenchTests/BenchTestBase.cs b/binding/c#/BenchmarkTest/BenchTests/BenchTestBase.cs deleted file mode 100644 index 835aed6d..00000000 --- a/binding/c#/BenchmarkTest/BenchTests/BenchTestBase.cs +++ /dev/null @@ -1,21 +0,0 @@ -using BenchmarkDotNet.Attributes; -using IP2Region; - -namespace BenchmarkTest.BenchTests -{ - public class BenchTestBase - { - protected DbSearcher _dbSearcher = null; - - [GlobalSetup] - public void Init() - { - _dbSearcher = new DbSearcher("../../../../../data/ip2region.db"); - } - [GlobalCleanup] - public void Clearup() - { - _dbSearcher.Dispose(); - } - } -} diff --git a/binding/c#/BenchmarkTest/BenchTests/NormalBenchmarkTests.cs b/binding/c#/BenchmarkTest/BenchTests/NormalBenchmarkTests.cs deleted file mode 100644 index f3e4fec6..00000000 --- a/binding/c#/BenchmarkTest/BenchTests/NormalBenchmarkTests.cs +++ /dev/null @@ -1,46 +0,0 @@ -using BenchmarkDotNet.Attributes; -using System.Threading.Tasks; - -namespace BenchmarkTest.BenchTests -{ - public class NormalBenchmarkTests : BenchTestBase - { - [Params("0.0.0.0", "210.109.255.230", "192.168.0.1", "255.255.255.255", "183.196.233.159", - "77.49.66.88", "210.248.255.231", "10.10.10.10", "197.84.60.202", "35.193.251.120", - "20.108.91.101", "120.196.148.137", "249.255.250.200", "112.65.1.130")] - public string validIp = null; - - [Benchmark] - public void TestSyncSpeedForMemorySearch() - { - _dbSearcher.MemorySearch(validIp); - } - [Benchmark] - public async Task TestAsyncSpeedForMemorySearch() - { - await _dbSearcher.MemorySearchAsync(validIp); - } - - [Benchmark] - public void TestSyncSpeedForBinarySearch() - { - _dbSearcher.BinarySearch(validIp); - } - [Benchmark] - public async Task TestAsyncBinarySearch() - { - await _dbSearcher.BinarySearchAsync(validIp); - } - - [Benchmark] - public void TestSyncSpeedForBTreeSearch() - { - _dbSearcher.BtreeSearch(validIp); - } - [Benchmark] - public async Task TestAsyncSpeedForBTreeSearch() - { - await _dbSearcher.BtreeSearchAsync(validIp); - } - } -} diff --git a/binding/c#/BenchmarkTest/IP2Region.Test.NetFxBenchmark.csproj b/binding/c#/BenchmarkTest/IP2Region.Test.NetFxBenchmark.csproj deleted file mode 100644 index 3145a232..00000000 --- a/binding/c#/BenchmarkTest/IP2Region.Test.NetFxBenchmark.csproj +++ /dev/null @@ -1,154 +0,0 @@ - - - - - Debug - AnyCPU - {053EA51E-2246-4FCB-A106-9B53E70FDC3F} - Exe - BenchmarkTest - BenchmarkTest - v4.6 - 512 - - - - AnyCPU - true - full - false - bin\Debug\ - DEBUG;TRACE - prompt - 4 - - - AnyCPU - pdbonly - true - bin\Release\ - TRACE - prompt - 4 - - - - ..\packages\BenchmarkDotNet.0.10.14\lib\net46\BenchmarkDotNet.dll - - - ..\packages\BenchmarkDotNet.Core.0.10.14\lib\net46\BenchmarkDotNet.Core.dll - - - ..\packages\BenchmarkDotNet.Toolchains.Roslyn.0.10.14\lib\net46\BenchmarkDotNet.Toolchains.Roslyn.dll - - - ..\packages\Microsoft.CodeAnalysis.Common.2.6.1\lib\netstandard1.3\Microsoft.CodeAnalysis.dll - - - ..\packages\Microsoft.CodeAnalysis.CSharp.2.6.1\lib\netstandard1.3\Microsoft.CodeAnalysis.CSharp.dll - - - ..\packages\Microsoft.DotNet.InternalAbstractions.1.0.0\lib\net451\Microsoft.DotNet.InternalAbstractions.dll - - - ..\packages\Microsoft.DotNet.PlatformAbstractions.1.1.1\lib\net451\Microsoft.DotNet.PlatformAbstractions.dll - - - ..\packages\Microsoft.Win32.Registry.4.3.0\lib\net46\Microsoft.Win32.Registry.dll - - - - ..\packages\System.AppContext.4.3.0\lib\net46\System.AppContext.dll - - - ..\packages\System.Collections.Immutable.1.3.1\lib\portable-net45+win8+wp8+wpa81\System.Collections.Immutable.dll - - - - ..\packages\System.Console.4.3.0\lib\net46\System.Console.dll - - - - - - ..\packages\System.Diagnostics.FileVersionInfo.4.3.0\lib\net46\System.Diagnostics.FileVersionInfo.dll - - - ..\packages\System.Diagnostics.StackTrace.4.3.0\lib\net46\System.Diagnostics.StackTrace.dll - - - ..\packages\System.IO.Compression.4.3.0\lib\net46\System.IO.Compression.dll - - - ..\packages\System.IO.FileSystem.4.3.0\lib\net46\System.IO.FileSystem.dll - - - ..\packages\System.IO.FileSystem.Primitives.4.3.0\lib\net46\System.IO.FileSystem.Primitives.dll - - - - - - ..\packages\System.Reflection.Metadata.1.4.2\lib\portable-net45+win8\System.Reflection.Metadata.dll - - - ..\packages\System.Security.Cryptography.Algorithms.4.3.0\lib\net46\System.Security.Cryptography.Algorithms.dll - - - ..\packages\System.Security.Cryptography.Encoding.4.3.0\lib\net46\System.Security.Cryptography.Encoding.dll - - - ..\packages\System.Security.Cryptography.Primitives.4.3.0\lib\net46\System.Security.Cryptography.Primitives.dll - - - ..\packages\System.Security.Cryptography.X509Certificates.4.3.0\lib\net46\System.Security.Cryptography.X509Certificates.dll - - - ..\packages\System.Text.Encoding.CodePages.4.3.0\lib\net46\System.Text.Encoding.CodePages.dll - - - ..\packages\System.Threading.Tasks.Extensions.4.3.0\lib\portable-net45+win8+wp8+wpa81\System.Threading.Tasks.Extensions.dll - - - ..\packages\System.Threading.Thread.4.3.0\lib\net46\System.Threading.Thread.dll - - - ..\packages\System.ValueTuple.4.3.0\lib\netstandard1.0\System.ValueTuple.dll - - - - - ..\packages\System.Xml.ReaderWriter.4.3.0\lib\net46\System.Xml.ReaderWriter.dll - - - ..\packages\System.Xml.XmlDocument.4.3.0\lib\net46\System.Xml.XmlDocument.dll - - - ..\packages\System.Xml.XPath.4.3.0\lib\net46\System.Xml.XPath.dll - - - ..\packages\System.Xml.XPath.XDocument.4.3.0\lib\net46\System.Xml.XPath.XDocument.dll - - - - - - - - - - - - - - - - - - - {4c6032ee-43a5-4d5c-88f8-c411069e49e0} - IP2Region - - - - \ No newline at end of file diff --git a/binding/c#/BenchmarkTest/Program.cs b/binding/c#/BenchmarkTest/Program.cs deleted file mode 100644 index 44da3d15..00000000 --- a/binding/c#/BenchmarkTest/Program.cs +++ /dev/null @@ -1,13 +0,0 @@ -using BenchmarkDotNet.Running; -using BenchmarkTest.BenchTests; - -namespace BenchmarkTest -{ - public class Program - { - public static void Main(string[] args) - { - BenchmarkRunner.Run(); - } - } -} diff --git a/binding/c#/BenchmarkTest/Properties/AssemblyInfo.cs b/binding/c#/BenchmarkTest/Properties/AssemblyInfo.cs deleted file mode 100644 index 6f7bdff7..00000000 --- a/binding/c#/BenchmarkTest/Properties/AssemblyInfo.cs +++ /dev/null @@ -1,35 +0,0 @@ -using System.Reflection; -using System.Runtime.InteropServices; - -// 有关程序集的一般信息由以下 -// 控制。更改这些特性值可修改 -// 与程序集关联的信息。 -[assembly: AssemblyTitle("BenchmarkTest")] -[assembly: AssemblyDescription("")] -[assembly: AssemblyConfiguration("")] -[assembly: AssemblyCompany("")] -[assembly: AssemblyProduct("BenchmarkTest")] -[assembly: AssemblyCopyright("Copyright © 2018")] -[assembly: AssemblyTrademark("")] -[assembly: AssemblyCulture("")] - -// 将 ComVisible 设置为 false 会使此程序集中的类型 -//对 COM 组件不可见。如果需要从 COM 访问此程序集中的类型 -//请将此类型的 ComVisible 特性设置为 true。 -[assembly: ComVisible(false)] - -// 如果此项目向 COM 公开,则下列 GUID 用于类型库的 ID -[assembly: Guid("053ea51e-2246-4fcb-a106-9b53e70fdc3f")] - -// 程序集的版本信息由下列四个值组成: -// -// 主版本 -// 次版本 -// 生成号 -// 修订号 -// -// 可以指定所有值,也可以使用以下所示的 "*" 预置版本号和修订号 -// 方法是按如下所示使用“*”: : -// [assembly: AssemblyVersion("1.0.*")] -[assembly: AssemblyVersion("1.0.0.0")] -[assembly: AssemblyFileVersion("1.0.0.0")] diff --git a/binding/c#/BenchmarkTest/packages.config b/binding/c#/BenchmarkTest/packages.config deleted file mode 100644 index 165b5a30..00000000 --- a/binding/c#/BenchmarkTest/packages.config +++ /dev/null @@ -1,54 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/binding/c#/IP2Region.Test.Benchmark/DbSearch_Test.cs b/binding/c#/IP2Region.Test.Benchmark/DbSearch_Test.cs index 5c387607..f43ad229 100644 --- a/binding/c#/IP2Region.Test.Benchmark/DbSearch_Test.cs +++ b/binding/c#/IP2Region.Test.Benchmark/DbSearch_Test.cs @@ -1,9 +1,5 @@ using BenchmarkDotNet.Attributes; -using BenchmarkDotNet.Order; using IP2Region.Models; -using System; -using System.Collections.Generic; -using System.Text; using System.Threading.Tasks; namespace IP2Region.Test.Benchmark @@ -17,7 +13,6 @@ public class DbSearch_Test : TestBase public DataBlock MemorySearch() { RandomIP = GetRandomIP(); - //Console.WriteLine(RandomIP); return _search.MemorySearch(RandomIP); } diff --git a/binding/c#/IP2Region.Test.Benchmark/Program.cs b/binding/c#/IP2Region.Test.Benchmark/Program.cs index 3c9d246a..7206d9cc 100644 --- a/binding/c#/IP2Region.Test.Benchmark/Program.cs +++ b/binding/c#/IP2Region.Test.Benchmark/Program.cs @@ -7,7 +7,7 @@ class Program { static void Main(string[] args) { - Console.WriteLine("Welcome To IP2Regin!"); + Console.WriteLine("Now starting benchmark test, please wait......"); var summary = BenchmarkRunner.Run(); Console.ReadLine(); } diff --git a/binding/c#/IP2Region.Test.Benchmark/TestBase.cs b/binding/c#/IP2Region.Test.Benchmark/TestBase.cs index 233a33a9..369391d7 100644 --- a/binding/c#/IP2Region.Test.Benchmark/TestBase.cs +++ b/binding/c#/IP2Region.Test.Benchmark/TestBase.cs @@ -1,7 +1,5 @@ using BenchmarkDotNet.Attributes; using System; -using System.Collections.Generic; -using System.Text; namespace IP2Region.Test.Benchmark { @@ -17,7 +15,7 @@ public TestBase() public TestBase(String DBFilePath) { - this._dBFilePath = DBFilePath; + _dBFilePath = DBFilePath; } [GlobalSetup] @@ -37,7 +35,7 @@ public void Init() [GlobalCleanup] public void Dispose() { - _search?.Dispose(); + _search.Dispose(); } public String GetRandomIP() diff --git a/binding/c#/IP2Region.Test.xUnit/SearchTest.cs b/binding/c#/IP2Region.Test.xUnit/SearchTest.cs new file mode 100644 index 00000000..d4f1ec43 --- /dev/null +++ b/binding/c#/IP2Region.Test.xUnit/SearchTest.cs @@ -0,0 +1,51 @@ +using System; +using System.Threading.Tasks; +using Xunit; + +namespace IP2Region.Test.xUnit +{ + public class SearchTest : IDisposable + { + private readonly DbSearcher _search; + + public SearchTest() + { + _search = new DbSearcher(Environment.CurrentDirectory + @"\DB\ip2region.db"); + } + [Fact] + public void Search_Test() + { + string memResult = _search.MemorySearch("183.192.62.65").Region; + string binarySearchResult = _search.BinarySearch("183.192.62.65").Region; + string binaryTreeSearchResult = _search.BtreeSearch("183.192.62.65").Region; + + Assert.NotNull(memResult); + Assert.NotNull(binarySearchResult); + Assert.NotNull(binaryTreeSearchResult); + + Assert.Equal(memResult, binarySearchResult); + Assert.Equal(binaryTreeSearchResult, memResult); + } + + [Fact] + public async Task SearchAsync_Test() + { + // We don't need the synchronizeContext, so just set to false + var memResult = await _search.MemorySearchAsync("183.192.62.65").ConfigureAwait(false); + var binarySearchResult = await _search.BinarySearchAsync("183.192.62.65").ConfigureAwait(false); + var bTreeSearchResult = await _search.BtreeSearchAsync("183.192.62.65").ConfigureAwait(false); + + Assert.NotNull(memResult.Region); + Assert.NotNull(binarySearchResult.Region); + Assert.NotNull(bTreeSearchResult.Region); + + Assert.Equal(memResult.Region, binarySearchResult.Region); + Assert.Equal(bTreeSearchResult.Region, memResult.Region); + } + + public void Dispose() + { + _search.Dispose(); + } + } +} diff --git a/binding/c#/IP2Region.Test.xUnit/UnitTest1.cs b/binding/c#/IP2Region.Test.xUnit/UnitTest1.cs deleted file mode 100644 index 09a6f9ba..00000000 --- a/binding/c#/IP2Region.Test.xUnit/UnitTest1.cs +++ /dev/null @@ -1,36 +0,0 @@ -using System; -using System.Threading.Tasks; -using Xunit; - -namespace IP2Region.Test.xUnit -{ - public class UnitTest1 - { - private readonly DbSearcher _search; - public UnitTest1() - { - _search = new DbSearcher(Environment.CurrentDirectory + @"\DB\ip2region.db"); - } - [Fact] - public void Search_Test() - { - - Assert.NotNull(_search.MemorySearch("183.192.62.65").Region); - Assert.NotNull(_search.MemorySearchAsync("183.192.62.65").Result.Region); - Assert.NotNull(_search.BinarySearch("183.192.62.65").Region); - Assert.NotNull(_search.BinarySearchAsync("183.192.62.65").Result.Region); - Assert.NotNull(_search.BtreeSearch("183.192.62.65").Region); - Assert.NotNull(_search.BtreeSearchAsync("183.192.62.65").Result.Region); - - } - - [Fact] - public async Task SearchAsync_Test() - { - var result = await _search.MemorySearchAsync("183.192.62.65"); - Assert.NotNull(result.Region); - } - - - } -} diff --git a/binding/c#/IP2Region/DBSearcher.cs b/binding/c#/IP2Region/DBSearcher.cs index 80756b3d..b99dba86 100644 --- a/binding/c#/IP2Region/DBSearcher.cs +++ b/binding/c#/IP2Region/DBSearcher.cs @@ -360,23 +360,23 @@ public DataBlock BinarySearch(String ip) /// /// Get the region throught the ip address with memory binary search algorithm. /// - public async Task MemorySearchAsync(string ip) + public Task MemorySearchAsync(string ip) { - return await Task.FromResult(MemorySearch(ip)); + return Task.FromResult(MemorySearch(ip)); } /// /// Get the region throught the ip address with b-tree search algorithm. /// - public async Task BtreeSearchAsync(string ip) + public Task BtreeSearchAsync(string ip) { - return await Task.FromResult(BtreeSearch(ip)); + return Task.FromResult(BtreeSearch(ip)); } /// /// Get the region throught the ip address with binary search algorithm. /// - public async Task BinarySearchAsync(string ip) + public Task BinarySearchAsync(string ip) { - return await Task.FromResult(BinarySearch(ip)); + return Task.FromResult(BinarySearch(ip)); } #endregion @@ -397,4 +397,4 @@ public void Dispose() } } -} \ No newline at end of file +} diff --git a/binding/c#/IP2Region_NetFx_Test/Program.cs b/binding/c#/IP2Region_NetFx_Test/Program.cs index f0de3e1d..2b968009 100644 --- a/binding/c#/IP2Region_NetFx_Test/Program.cs +++ b/binding/c#/IP2Region_NetFx_Test/Program.cs @@ -1,9 +1,5 @@ using IP2Region; using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; namespace IP2Region_NetFx_Test { @@ -11,9 +7,11 @@ class Program { static void Main(string[] args) { - DbSearcher _search = new DbSearcher(Environment.CurrentDirectory + @"\DB\ip2region.db"); - Console.WriteLine(_search.MemorySearch("183.192.62.65").Region); - Console.Read(); + using (var _search = new DbSearcher(Environment.CurrentDirectory + @"\DB\ip2region.db")) + { + Console.WriteLine(_search.MemorySearch("183.192.62.65").Region); + Console.Read(); + } } } } diff --git a/binding/c#/IP2Region_NetFx_Test/Properties/AssemblyInfo.cs b/binding/c#/IP2Region_NetFx_Test/Properties/AssemblyInfo.cs index 92c2b2d8..04e8005a 100644 --- a/binding/c#/IP2Region_NetFx_Test/Properties/AssemblyInfo.cs +++ b/binding/c#/IP2Region_NetFx_Test/Properties/AssemblyInfo.cs @@ -1,5 +1,4 @@ using System.Reflection; -using System.Runtime.CompilerServices; using System.Runtime.InteropServices; // 有关程序集的一般信息由以下 diff --git a/binding/c#/README.md b/binding/c#/README.md index f7f22ab4..a3fb0e14 100644 --- a/binding/c#/README.md +++ b/binding/c#/README.md @@ -1,22 +1,30 @@ # IP2Region C# Client ## How To Use -### 1.Install from Nuget. Support .Net Framework 4.5+ And netstandard 2.0(.net core) +### 1.Install from Nuget. Support .Net Framework (>=4.5) And netstandard 2.0(.net core) ```powershell Install-Package IP2Region ``` -### 2.Init DbSearcher with Newest DBFile Downloaded into your project.(https://github.com/lionsoul2014/ip2region/blob/master/data/ip2region.db) -```c# -DbSearcher _search=new DbSearcher(Environment.CurrentDirectory + @"\DB\ip2region.db"); -``` -### 3.Invoke Search Method.(MemorySearch,BtreeSearch,BinarySearch) -```c# +### 2.Init DbSearcher with [the newest DBFile](https://github.com/lionsoul2014/ip2region/blob/master/data/ip2region.db) Downloaded into your project, and invoke your search methods as below: +```csharp +using (var _search = new DbSearcher(Environment.CurrentDirectory + @"\DB\ip2region.db")) +{ _search.MemorySearch("183.192.62.65").Region; - _search.MemorySearchAsync("183.192.62.65").Result.Region; _search.BinarySearch("183.192.62.65").Region; - _search.BinarySearchAsync("183.192.62.65").Result.Region; _search.BtreeSearch("183.192.62.65").Region; - _search.BtreeSearchAsync("183.192.62.65").Result.Region; +} +``` +For async methods (Methods with the suffix "Async"), just put them with `async` and `await`. +If you don't need the `SynchronizedContext`, please use `ConfigureAwait(false)`. +For more about this, please read https://msdn.microsoft.com/en-us/magazine/jj991977.aspx. + +```csharp + public async Task SearchAsync_Test() + { + // We don't need the synchronizeContext, so just set to false. + // So as for BinarySearchAsync and BtreeSearchAsync + var memResult = await _search.MemorySearchAsync("183.192.62.65").ConfigureAwait(false); + } ``` ## Test Result(From /IP2Region.Test.Benchmark) @@ -28,11 +36,3 @@ BinarySearch | 52.22 us | 0.6403 us | 0.5347 us | 5 | BinarySearch_Async | 53.03 us | 1.0271 us | 0.9608 us | 6 | BtreeSearch | 19.05 us | 0.2464 us | 0.2305 us | 3 | BtreeSearch_Async | 19.40 us | 0.3820 us | 0.6690 us | 4 | - -## Contribute History -| Name | Github | Responsibility | Date | Remark | -| ------ | ------ | ------ | ------ | ------ | -| RocherKong | https://github.com/RocherKong | Creator | 20180209| -| Dongwei | https://github.com/Maledong | Contributor | 20180708 | 1.Async 2.NetFxBenchmark 3.Rename of some Methods -| RocherKong | https://github.com/RocherKong | Creator | 20180209| 1.CodeStandardized 2.Support Netfx4.5 3.TestStandardized - diff --git a/binding/c#/UnitTests/ExceptionTest.cs b/binding/c#/UnitTests/ExceptionTest.cs deleted file mode 100644 index 1eb9552f..00000000 --- a/binding/c#/UnitTests/ExceptionTest.cs +++ /dev/null @@ -1,50 +0,0 @@ -using IP2Region; -using IP2Region.Models; -using Microsoft.VisualStudio.TestTools.UnitTesting; - -namespace UnitTests -{ - /// - /// This test class is mainly used to test your exception types. - /// It should throw exceptions of differnt kinds of types on certain occations. - /// - [TestClass] - public class ExceptionTest - { - /// - /// Path error should throw "DbMakerConfigException" - /// - [TestMethod] - [ExpectedException(typeof(DbMakerConfigException))] - public void TestDbMakerConfigException() - { - using (var _dbSearcher = new DbSearcher(new DbConfig(255), "../../../../../data/ip2region.db")) { } - } - /// - /// Invalid IP should throw "IPInValidException" - /// - [TestMethod] - public void TestInvalidIP() - { - using (var _dbSearcher = new DbSearcher("../../../../../data/ip2region.db")) - { - var invalidIps = new string[] { "256.255.1.1", "-1.0.0.0", "192.168.4", "x.y.z" }; - var counter = 0; - - foreach (var item in invalidIps) - { - try - { - _dbSearcher.MemorySearch(item); - } - catch (IPInValidException) - { - counter++; - } - } - - Assert.AreEqual(counter, 4); - } - } - } -} diff --git a/binding/c#/UnitTests/NormalAsyncTest.cs b/binding/c#/UnitTests/NormalAsyncTest.cs deleted file mode 100644 index 4155d06e..00000000 --- a/binding/c#/UnitTests/NormalAsyncTest.cs +++ /dev/null @@ -1,148 +0,0 @@ -using Microsoft.VisualStudio.TestTools.UnitTesting; -using IP2Region; -using System.Threading.Tasks; - -namespace UnitTests -{ - /// - /// This class is used to test with normal IPs (Chinese, Foreign and Special). - /// Use async functions for testing. - /// - [TestClass] - public class NormalAsyncTest - { - private static DbSearcher _dbSearcher = null; - - [ClassInitialize] - public static void Init(TestContext context) - { - _dbSearcher = new DbSearcher("../../../../../data/ip2region.db"); - } - - [ClassCleanup] - public static void ClearUp() - { - _dbSearcher.Dispose(); - } - - #region Normal Valid Chinese IP Test Cases - [TestMethod] - [TestCategory("Async Chinese Ip Test")] - public async Task AsyncBinarySearchTestForChinese() - { - const int cityId = 2163; - const string region = "中国|0|广东省|深圳市|鹏博士"; - - var result = await _dbSearcher.AsyncBinarySearch("101.105.35.57"); - Assert.AreEqual(result.CityID, cityId); - Assert.AreEqual(result.Region, region); - } - [TestMethod] - [TestCategory("Async Chinese Ip Test")] - public async Task AsyncBtreeSearchTestForChinese() - { - const int cityId = 2163; - const string region = "中国|0|广东省|深圳市|鹏博士"; - - var result = await _dbSearcher.AsyncBtreeSearch("101.105.35.57"); - Assert.AreEqual(result.CityID, cityId); - Assert.AreEqual(result.Region, region); - } - [TestMethod] - [TestCategory("Async Chinese Ip Test")] - public async Task AsyncMemorySearchTestForChinese() - { - const int cityId = 2163; - const string region = "中国|0|广东省|深圳市|鹏博士"; - - var result = await _dbSearcher.AsyncMemorySearch("101.105.35.57"); - Assert.AreEqual(result.CityID, cityId); - Assert.AreEqual(result.Region, region); - } - #endregion - - #region Normal Valid Foreign IP Test Cases - [TestMethod] - [TestCategory("Async Foreign Ip Test")] - public async Task AsyncBinarySearchTestForForeign() - { - const int cityId = 71; - const string region = "荷兰|0|0|0|0"; - - var result = await _dbSearcher.AsyncBinarySearch("86.84.21.60"); - Assert.AreEqual(result.CityID, cityId); - Assert.AreEqual(result.Region, region); - } - [TestMethod] - [TestCategory("Async Foreign Ip Test")] - public async Task AsyncBtreeSearchTestForForeign() - { - const int cityId = 71; - const string region = "荷兰|0|0|0|0"; - - var result = await _dbSearcher.AsyncBtreeSearch("86.84.21.60"); - Assert.AreEqual(result.CityID, cityId); - Assert.AreEqual(result.Region, region); - } - [TestMethod] - [TestCategory("Async Foreign Ip Test")] - public async Task AsyncMemorySearchTestForForeign() - { - const int cityId = 71; - const string region = "荷兰|0|0|0|0"; - - var result = await _dbSearcher.AsyncMemorySearch("86.84.21.60"); - Assert.AreEqual(result.CityID, cityId); - Assert.AreEqual(result.Region, region); - } - #endregion - - #region Normal Valid Special Ip Test Cases - [TestMethod] - [TestCategory("Async Special Ip Test")] - public async Task AsyncBinarySearchTestForSpecial() - { - const int cityId = 0; - const string region = "0|0|0|内网IP|内网IP"; - string[] specialIps = new string[] { "255.255.255.255", "0.0.0.0" }; - - foreach (var item in specialIps) - { - var result = await _dbSearcher.AsyncBinarySearch(item); - Assert.AreEqual(result.CityID, cityId); - Assert.AreEqual(result.Region, region); - } - } - [TestMethod] - [TestCategory("Async Special Ip Test")] - public async Task AsyncBtreeSearchTestForSpecial() - { - const int cityId = 0; - const string region = "0|0|0|内网IP|内网IP"; - string[] specialIps = new string[] { "255.255.255.255", "0.0.0.0" }; - - foreach (var item in specialIps) - { - var result = await _dbSearcher.AsyncBtreeSearch(item); - Assert.AreEqual(result.CityID, cityId); - Assert.AreEqual(result.Region, region); - } - } - [TestMethod] - [TestCategory("Async Special Ip Test")] - public async Task AsyncMemorySearchTestForSpecial() - { - const int cityId = 0; - const string region = "0|0|0|内网IP|内网IP"; - string[] specialIps = new string[] { "255.255.255.255", "0.0.0.0" }; - - foreach (var item in specialIps) - { - var result = await _dbSearcher.AsyncMemorySearch(item); - Assert.AreEqual(result.CityID, cityId); - Assert.AreEqual(result.Region, region); - } - } - #endregion - } -} diff --git a/binding/c#/UnitTests/NormalSyncTest.cs b/binding/c#/UnitTests/NormalSyncTest.cs deleted file mode 100644 index 628e585e..00000000 --- a/binding/c#/UnitTests/NormalSyncTest.cs +++ /dev/null @@ -1,146 +0,0 @@ -using Microsoft.VisualStudio.TestTools.UnitTesting; -using IP2Region; -namespace UnitTests -{ - /// - /// This class is used to test with normal IPs (Chinese, Foreign and Special). - /// Use sync functions for testing. - /// - [TestClass] - public class NormalSyncTest - { - private static DbSearcher _dbSearcher = null; - - [ClassInitialize] - public static void Init(TestContext context) - { - _dbSearcher = new DbSearcher("../../../../../data/ip2region.db"); - } - - [ClassCleanup] - public static void ClearUp() - { - _dbSearcher.Dispose(); - } - - #region Normal Valid Chinese IP Test Cases - [TestMethod] - [TestCategory("Chinese Ip Test")] - public void BinarySearchTestForChinese() - { - const int cityId = 2163; - const string region = "中国|0|广东省|深圳市|鹏博士"; - - var result = _dbSearcher.BinarySearch("101.105.35.57"); - Assert.AreEqual(result.CityID, cityId); - Assert.AreEqual(result.Region, region); - } - [TestMethod] - [TestCategory("Chinese Ip Test")] - public void BtreeSearchTestForChinese() - { - const int cityId = 2163; - const string region = "中国|0|广东省|深圳市|鹏博士"; - - var result = _dbSearcher.BtreeSearch("101.105.35.57"); - Assert.AreEqual(result.CityID, cityId); - Assert.AreEqual(result.Region, region); - } - [TestMethod] - [TestCategory("Chinese Ip Test")] - public void MemorySearchTestForChinese() - { - const int cityId = 2163; - const string region = "中国|0|广东省|深圳市|鹏博士"; - - var result = _dbSearcher.MemorySearch("101.105.35.57"); - Assert.AreEqual(result.CityID, cityId); - Assert.AreEqual(result.Region, region); - } - #endregion - - #region Normal Valid Foreign IP Test Cases - [TestMethod] - [TestCategory("Foreign Ip Test")] - public void BinarySearchTestForForeign() - { - const int cityId = 71; - const string region = "荷兰|0|0|0|0"; - - var result = _dbSearcher.BinarySearch("86.84.21.60"); - Assert.AreEqual(result.CityID, cityId); - Assert.AreEqual(result.Region, region); - } - [TestMethod] - [TestCategory("Foreign Ip Test")] - public void BtreeSearchTestForForeign() - { - const int cityId = 71; - const string region = "荷兰|0|0|0|0"; - - var result = _dbSearcher.BtreeSearch("86.84.21.60"); - Assert.AreEqual(result.CityID, cityId); - Assert.AreEqual(result.Region, region); - } - [TestMethod] - [TestCategory("Foreign Ip Test")] - public void MemorySearchTestForForeign() - { - const int cityId = 71; - const string region = "荷兰|0|0|0|0"; - - var result = _dbSearcher.MemorySearch("86.84.21.60"); - Assert.AreEqual(result.CityID, cityId); - Assert.AreEqual(result.Region, region); - } - #endregion - - #region Normal Valid Special Ip Test Cases - [TestMethod] - [TestCategory("Special Ip Test")] - public void BinarySearchTestForSpecial() - { - const int cityId = 0; - const string region = "0|0|0|内网IP|内网IP"; - string[] specialIps = new string[] { "255.255.255.255", "0.0.0.0" }; - - foreach (var item in specialIps) - { - var result = _dbSearcher.BinarySearch(item); - Assert.AreEqual(result.CityID, cityId); - Assert.AreEqual(result.Region, region); - } - } - [TestMethod] - [TestCategory("Special Ip Test")] - public void BtreeSearchTestForSpecial() - { - const int cityId = 0; - const string region = "0|0|0|内网IP|内网IP"; - string[] specialIps = new string[] { "255.255.255.255", "0.0.0.0" }; - - foreach (var item in specialIps) - { - var result = _dbSearcher.BtreeSearch(item); - Assert.AreEqual(result.CityID, cityId); - Assert.AreEqual(result.Region, region); - } - } - [TestMethod] - [TestCategory("Special Ip Test")] - public void MemorySearchTestForSpecial() - { - const int cityId = 0; - const string region = "0|0|0|内网IP|内网IP"; - string[] specialIps = new string[] { "255.255.255.255", "0.0.0.0" }; - - foreach (var item in specialIps) - { - var result = _dbSearcher.MemorySearch(item); - Assert.AreEqual(result.CityID, cityId); - Assert.AreEqual(result.Region, region); - } - } - #endregion - } -} diff --git a/binding/c#/UnitTests/Properties/AssemblyInfo.cs b/binding/c#/UnitTests/Properties/AssemblyInfo.cs deleted file mode 100644 index 4ecebe18..00000000 --- a/binding/c#/UnitTests/Properties/AssemblyInfo.cs +++ /dev/null @@ -1,19 +0,0 @@ -using System.Reflection; -using System.Runtime.InteropServices; - -[assembly: AssemblyTitle("UnitTests")] -[assembly: AssemblyDescription("")] -[assembly: AssemblyConfiguration("")] -[assembly: AssemblyCompany("")] -[assembly: AssemblyProduct("UnitTests")] -[assembly: AssemblyCopyright("Copyright © 2018")] -[assembly: AssemblyTrademark("")] -[assembly: AssemblyCulture("")] - -[assembly: ComVisible(false)] - -[assembly: Guid("38abc99f-cc14-47e3-9eeb-06c67d5ac33f")] - -// [assembly: AssemblyVersion("1.0.*")] -[assembly: AssemblyVersion("1.0.0.0")] -[assembly: AssemblyFileVersion("1.0.0.0")] diff --git a/binding/c#/UnitTests/UnitTests.csproj b/binding/c#/UnitTests/UnitTests.csproj deleted file mode 100644 index 45271428..00000000 --- a/binding/c#/UnitTests/UnitTests.csproj +++ /dev/null @@ -1,76 +0,0 @@ - - - - - Debug - AnyCPU - {38ABC99F-CC14-47E3-9EEB-06C67D5AC33F} - Library - Properties - UnitTests - UnitTests - v4.6 - 512 - {3AC096D0-A1C2-E12C-1390-A8335801FDAB};{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC} - 15.0 - $(MSBuildExtensionsPath32)\Microsoft\VisualStudio\v$(VisualStudioVersion) - $(ProgramFiles)\Common Files\microsoft shared\VSTT\$(VisualStudioVersion)\UITestExtensionPackages - False - UnitTest - - - - - - true - full - false - bin\Debug\ - DEBUG;TRACE - prompt - 4 - - - pdbonly - true - bin\Release\ - TRACE - prompt - 4 - - - - ..\packages\MSTest.TestFramework.1.2.1\lib\net45\Microsoft.VisualStudio.TestPlatform.TestFramework.dll - - - ..\packages\MSTest.TestFramework.1.2.1\lib\net45\Microsoft.VisualStudio.TestPlatform.TestFramework.Extensions.dll - - - - - - - - - - - - - - - - {4c6032ee-43a5-4d5c-88f8-c411069e49e0} - IP2Region - - - - - - - 这台计算机上缺少此项目引用的 NuGet 程序包。使用“NuGet 程序包还原”可下载这些程序包。有关更多信息,请参见 http://go.microsoft.com/fwlink/?LinkID=322105。缺少的文件是 {0}。 - - - - - - \ No newline at end of file diff --git a/binding/c#/UnitTests/packages.config b/binding/c#/UnitTests/packages.config deleted file mode 100644 index 31240c15..00000000 --- a/binding/c#/UnitTests/packages.config +++ /dev/null @@ -1,5 +0,0 @@ - - - - - \ No newline at end of file diff --git a/binding/nodejs/README.md b/binding/nodejs/README.md index da52e32b..e6098f7a 100644 --- a/binding/nodejs/README.md +++ b/binding/nodejs/README.md @@ -1,3 +1,56 @@ # nodejs 客户端 -现已实现同步查询,异步查询未实现,用法参考 ip2region.spec.js +## 实现情况: + +现已实现同步和异步查询,具体使用方法可以参考 `nodejs\tests\constructorTest.spec.js` 和`nodejs\tests\createTest.spec.js`。 + +## 如何贡献? + +你可以任意修改代码,但必须确保通过全部的单元测试。要保证通过全部的单元测试,请在 Nodejs 控制台下切换到 nodejs 目录: + +1)在此之前,请先运行 `npm i` 确保你已经安装了各类初始化第三方工具。 +2)然后运行 `npm run coverage` 确保你的代码可以通过全部测试(必要时可以添加测试),同时保证代码覆盖率都是绿色(80%以上)。 + +```bash +D:\Projects\ip2region\binding\nodejs>npm run coverage + +> ip2region@0.0.1 coverage D:\Projects\ip2region\binding\nodejs +> npm run test && jest --coverage + + +> ip2region@0.0.1 test D:\Projects\ip2region\binding\nodejs +> jest + + PASS tests\constructorTest.spec.js + PASS tests\createTest.spec.js + PASS tests\exceptionTest.spec.js + +Snapshot Summary + › 168 snapshots written in 2 test suites. + +Test Suites: 3 passed, 3 total +Tests: 14 passed, 14 total +Snapshots: 168 added, 168 total +Time: 1.645s +Ran all test suites. + PASS tests\constructorTest.spec.js + PASS tests\createTest.spec.js + PASS tests\exceptionTest.spec.js +----------------------|----------|----------|----------|----------|-------------------| +File | % Stmts | % Branch | % Funcs | % Lines | Uncovered Line #s | +----------------------|----------|----------|----------|----------|-------------------| +All files | 92.34 | 80.77 | 96 | 93.83 | | + nodejs | 91.95 | 80.26 | 95.65 | 93.51 | | + ip2region.js | 91.95 | 80.26 | 95.65 | 93.51 |... 09,410,460,484 | + nodejs/tests/utils | 100 | 100 | 100 | 100 | | + asyncFor.js | 100 | 100 | 100 | 100 | | + fetchMainVersion.js | 100 | 100 | 100 | 100 | | + testData.js | 100 | 100 | 100 | 100 | | +----------------------|----------|----------|----------|----------|-------------------| + +Test Suites: 3 passed, 3 total +Tests: 14 passed, 14 total +Snapshots: 168 passed, 168 total +Time: 1.792s +Ran all test suites. +``` \ No newline at end of file diff --git a/binding/nodejs/__snapshots__/ip2region.spec.js.snap b/binding/nodejs/__snapshots__/ip2region.spec.js.snap deleted file mode 100644 index b738d52f..00000000 --- a/binding/nodejs/__snapshots__/ip2region.spec.js.snap +++ /dev/null @@ -1,29 +0,0 @@ -// Jest Snapshot v1, https://goo.gl/fbAQLP - -exports[`ip2region binarySearch 1`] = ` -Object { - "city": 2163, - "region": "中国|0|广东省|深圳市|阿里云", -} -`; - -exports[`ip2region binarySearch 2`] = ` -Object { - "city": 0, - "region": "0|0|0|内网IP|内网IP", -} -`; - -exports[`ip2region should query 1`] = ` -Object { - "city": 2163, - "region": "中国|0|广东省|深圳市|阿里云", -} -`; - -exports[`ip2region should query 2`] = ` -Object { - "city": 0, - "region": "0|0|0|内网IP|内网IP", -} -`; diff --git a/binding/nodejs/ip2region.js b/binding/nodejs/ip2region.js index fd81a9bd..a704c534 100644 --- a/binding/nodejs/ip2region.js +++ b/binding/nodejs/ip2region.js @@ -5,20 +5,18 @@ * * @author dongyado * @author leeching + * @author dongwei */ const fs = require('fs'); -const IP_BASE = [16777216, 65536, 256, 1]; -const INDEX_BLOCK_LENGTH = 12; -const TOTAL_HEADER_LENGTH = 8192; - +//#region Private Functions /** * Convert ip to long (xxx.xxx.xxx.xxx to a integer) * * @param {string} ip * @return {number} long value */ -function ip2long(ip) { +function _ip2long(ip) { const arr = ip.split('.'); if (arr.length !== 4) { throw new Error('invalid ip'); @@ -39,7 +37,7 @@ function ip2long(ip) { * @param {number} offset * @return {number} long value */ -function getLong(buffer, offset) { +function _getLong(buffer, offset) { const val = (buffer[offset] & 0x000000ff) | ((buffer[offset + 1] << 8) & 0x0000ff00) | @@ -48,82 +46,156 @@ function getLong(buffer, offset) { return val < 0 ? val >>> 0 : val; } -/** - * @typedef {Object} SearchResult - * @property {number} city - * @property {string} region - */ +//#endregion + +//#region Private Variables + +// We don't wanna expose a private global settings to +// the public for safety reason. +const _globalInstances = new Map(); + +const IP_BASE = [16777216, 65536, 256, 1]; +const INDEX_BLOCK_LENGTH = 12; +const TOTAL_HEADER_LENGTH = 8192; + +// Private Message Symbols for functions +const PrepareHeader = Symbol('#PrepareHeader'); +const CalTotalBlocks = Symbol('#CalsTotalBlocks'); +const ReadDataSync = Symbol('#ReadDataSync'); +const ReadData = Symbol('#ReadData'); +//#endregion class IP2Region { + + //#region Private Functions + + [CalTotalBlocks]() { + const superBlock = new Buffer(8); + fs.readSync(this.dbFd, superBlock, 0, 8, 0); + this.firstIndexPtr = _getLong(superBlock, 0); + this.lastIndexPtr = _getLong(superBlock, 4); + this.totalBlocks = + (this.lastIndexPtr - this.firstIndexPtr) / INDEX_BLOCK_LENGTH + 1; + } + + [PrepareHeader]() { + fs.readSync( + this.dbFd, + this.headerIndexBuffer, + 0, + TOTAL_HEADER_LENGTH, + 8 + ); + + for (let i = 0; i < TOTAL_HEADER_LENGTH; i += 8) { + const startIp = _getLong(this.headerIndexBuffer, i); + const dataPtr = _getLong(this.headerIndexBuffer, i + 4); + if (dataPtr == 0) break; + + this.headerSip.push(startIp); + this.headerPtr.push(dataPtr); + this.headerLen++; // header index size count + } + } + + [ReadData](dataPos, callBack) { + if (dataPos == 0) return callBack(null,null); + const dataLen = (dataPos >> 24) & 0xff; + dataPos = dataPos & 0x00ffffff; + const dataBuffer = new Buffer(dataLen); + + fs.read(this.dbFd, dataBuffer, 0, dataLen, dataPos, (err, result) => { + if (err) { + callBack(err, null); + } + else { + const city = _getLong(dataBuffer, 0); + const region = dataBuffer.toString('utf8', 4, dataLen); + callBack(null, { city, region }); + } + }); + } + + [ReadDataSync](dataPos) { + if (dataPos == 0) return null; + const dataLen = (dataPos >> 24) & 0xff; + dataPos = dataPos & 0x00ffffff; + const dataBuffer = new Buffer(dataLen); + + fs.readSync(this.dbFd, dataBuffer, 0, dataLen, dataPos); + + const city = _getLong(dataBuffer, 0); + const region = dataBuffer.toString('utf8', 4, dataLen); + + return { city, region }; + } + //#endregion + + //#region Static Functions + + // Single Instance static create(dbPath) { - const oldInstance = IP2Region._instances.get(dbPath); - if (oldInstance) { - return oldInstance; - } else { - const instance = new IP2Region({ dbPath }); - IP2Region._instances.set(dbPath, instance); - return instance; + let existInstance = _globalInstances.get(dbPath); + if (existInstance == null) { + existInstance = new IP2Region({ dbPath: dbPath }); } + return existInstance; } /** * For backward compatibility */ static destroy() { - IP2Region._instances.forEach(([key, instance]) => { + _globalInstances.forEach(([key, instance]) => { instance.destroy(); }); } + //#endregion + constructor(options = {}) { + const { dbPath } = options; - if (!dbPath || !fs.existsSync(dbPath)) { - throw new Error(`[ip2region] db file not exists : ${dbPath}`); - } - try { - this.dbFd = fs.openSync(dbPath, 'r'); - } catch (e) { - throw new Error( - `[ip2region] Can not open ip2region.db file , path: ${dbPath}` - ); - } + this.dbFd = fs.openSync(dbPath, 'r'); - IP2Region._instances.set((this.dbPath = dbPath), this); + this.dbPath = dbPath; + + _globalInstances.set(this.dbPath, this); this.totalBlocks = this.firstIndexPtr = this.lastIndexPtr = 0; - this.calcTotalBlocks(); + this[CalTotalBlocks](); this.headerIndexBuffer = new Buffer(TOTAL_HEADER_LENGTH); this.headerSip = []; this.headerPtr = []; this.headerLen = 0; - this.prepareHeader(); + this[PrepareHeader](); } + //#region Public Functions /** - * @public + * Destroy the current file by closing it. */ destroy() { - fs.closeSync(ip2rObj.dbFd); - IP2Region._instances.delete(this.dbPath); + fs.closeSync(this.dbFd); + _globalInstances.delete(this.dbPath); } /** - * @public - * @param {string} ip - * @return {SearchResult} + * Sync of binarySearch. + * @param {string} ip The IP address to search for. + * @return {SearchResult} A result something like `{ city: 2163, region: '中国|0|广东省|深圳市|阿里云' }` */ binarySearchSync(ip) { - ip = ip2long(ip); + + ip = _ip2long(ip); let low = 0; let mid = 0; let high = this.totalBlocks; - let dataPos = 0; let pos = 0; let sip = 0; - let eip = 0; const indexBuffer = new Buffer(12); // binary search @@ -131,30 +203,89 @@ class IP2Region { mid = (low + high) >> 1; pos = this.firstIndexPtr + mid * INDEX_BLOCK_LENGTH; fs.readSync(this.dbFd, indexBuffer, 0, INDEX_BLOCK_LENGTH, pos); - sip = getLong(indexBuffer, 0); + sip = _getLong(indexBuffer, 0); if (ip < sip) { high = mid - 1; } else { - eip = getLong(indexBuffer, 4); - if (ip > eip) { + sip = _getLong(indexBuffer, 4); + if (ip > sip) { low = mid + 1; } else { - dataPos = getLong(indexBuffer, 8); + sip = _getLong(indexBuffer, 8); break; } } } - return this.readData(dataPos); + return this[ReadDataSync](sip); } /** - * @public - * @param {string} ip - * @return {SearchResult} + * Async of binarySearch. + * @param {string} ip The IP address to search for. + * @param {Function} callBack The callBack function with two parameters, if successful, + * err is null and result is `{ city: 2163, region: '中国|0|广东省|深圳市|阿里云' }` + */ + binarySearch(ip, callBack) { + + ip = _ip2long(ip); + + let low = 0; + let mid = 0; + let high = this.totalBlocks; + let pos = 0; + let sip = 0; + const indexBuffer = new Buffer(12); + const _self = this; + + // Because `while` is a sync method, we have to convert this to a recursive loop + // and in each loop we should continue calling `setImmediate` until we found the IP. + function _innerAsyncWhile() { + if (low <= high) { + mid = (low + high) >> 1; + pos = _self.firstIndexPtr + mid * INDEX_BLOCK_LENGTH; + + // Now async read the file + fs.read(_self.dbFd, indexBuffer, 0, INDEX_BLOCK_LENGTH, pos, (err) => { + + if (err) { + return callBack(err, null); + } + + sip = _getLong(indexBuffer, 0); + + if (ip < sip) { + high = mid - 1; + setImmediate(_innerAsyncWhile); + } else { + sip = _getLong(indexBuffer, 4); + if (ip > sip) { + low = mid + 1; + setImmediate(_innerAsyncWhile); + } else { + sip = _getLong(indexBuffer, 8); + _self[ReadData](sip, (err, result) => { + callBack(err, result); + }); + } + } + }); + } + } + + // Call this immediately + _innerAsyncWhile(); + + } + + /** + * Sync of btreeSearch. + * @param {string} ip The IP address to search for. + * @return {Function} A result something like `{ city: 2163, region: '中国|0|广东省|深圳市|阿里云' }` */ btreeSearchSync(ip) { - ip = ip2long(ip); + + ip = _ip2long(ip); // first search (in header index) let low = 0; @@ -221,84 +352,143 @@ class IP2Region { let p = 0; let sip = 0; - let eip = 0; - let dataPtr = 0; while (low <= high) { mid = (low + high) >> 1; p = mid * INDEX_BLOCK_LENGTH; - sip = getLong(blockBuffer, p); + sip = _getLong(blockBuffer, p); if (ip < sip) { high = mid - 1; } else { - eip = getLong(blockBuffer, p + 4); - if (ip > eip) { + sip = _getLong(blockBuffer, p + 4); + if (ip > sip) { low = mid + 1; } else { - dataPtr = getLong(blockBuffer, p + 8); + sip = _getLong(blockBuffer, p + 8); break; } } } - return this.readData(dataPtr); + return this[ReadDataSync](sip); } /** - * @private - */ - calcTotalBlocks() { - const superBlock = new Buffer(8); - fs.readSync(this.dbFd, superBlock, 0, 8, 0); - this.firstIndexPtr = getLong(superBlock, 0); - this.lastIndexPtr = getLong(superBlock, 4); - this.totalBlocks = - (this.lastIndexPtr - this.firstIndexPtr) / INDEX_BLOCK_LENGTH + 1; - } + * Async of btreeSearch. + * @param {string} ip The IP address to search for. + * @param {Function} callBack The callBack function with two parameters, if successful, + * err is null and result is `{ city: 2163, region: '中国|0|广东省|深圳市|阿里云' }` + */ + btreeSearch(ip, callBack) { + ip = _ip2long(ip); - /** - * @private - */ - prepareHeader() { - fs.readSync( - this.dbFd, - this.headerIndexBuffer, - 0, - TOTAL_HEADER_LENGTH, - 8 - ); + // first search (in header index) + let low = 0; + let mid = 0; + let high = this.headerLen; + let sptr = 0; + let eptr = 0; - for (let i = 0; i < TOTAL_HEADER_LENGTH; i += 8) { - const startIp = getLong(this.headerIndexBuffer, i); - const dataPtr = getLong(this.headerIndexBuffer, i + 4); - if (dataPtr == 0) break; + while (low <= high) { + mid = (low + high) >> 1; - this.headerSip.push(startIp); - this.headerPtr.push(dataPtr); - this.headerLen++; // header index size count + if (ip == this.headerSip[mid]) { + if (mid > 0) { + sptr = this.headerPtr[mid - 1]; + eptr = this.headerPtr[mid]; + } else { + sptr = this.headerPtr[mid]; + eptr = this.headerPtr[mid + 1]; + } + break; + } + + if (ip < this.headerSip[mid]) { + if (mid == 0) { + sptr = this.headerPtr[mid]; + eptr = this.headerPtr[mid + 1]; + break; + } else if (ip > this.headerSip[mid - 1]) { + sptr = this.headerPtr[mid - 1]; + eptr = this.headerPtr[mid]; + break; + } + high = mid - 1; + } else { + if (mid == this.headerLen - 1) { + sptr = this.headerPtr[mid - 1]; + eptr = this.headerPtr[mid]; + break; + } else if (ip <= this.headerSip[mid + 1]) { + sptr = this.headerPtr[mid]; + eptr = this.headerPtr[mid + 1]; + break; + } + low = mid + 1; + } } - } - /** - * @private - * @param {number} dataPos - * @return {SearchResult} - */ - readData(dataPos) { - if (dataPos == 0) return null; - const dataLen = (dataPos >> 24) & 0xff; - dataPos = dataPos & 0x00ffffff; - const dataBuffer = new Buffer(dataLen); + // match nothing + if (sptr == 0) return callBack(null, null); - fs.readSync(this.dbFd, dataBuffer, 0, dataLen, dataPos); + let p = 0; + let sip = 0; - const city = getLong(dataBuffer, 0); - const region = dataBuffer.toString('utf8', 4, dataLen); + // second search (in index) + const blockLen = eptr - sptr; + const blockBuffer = new Buffer(blockLen + INDEX_BLOCK_LENGTH); + low = 0; + high = blockLen / INDEX_BLOCK_LENGTH; - return { city, region }; + const _self = this; + + function _innerAsyncWhile() { + + if (low <= high) { + + mid = (low + high) >> 1; + p = mid * INDEX_BLOCK_LENGTH; + + // Use this to call the method itself as + // an asynchronize step + fs.read(_self.dbFd, blockBuffer, + 0, + blockLen + INDEX_BLOCK_LENGTH, + sptr, (err) => { + + if (err) { + return callBack(err, null); + } + + sip = _getLong(blockBuffer, p); + + if (ip < sip) { + high = mid - 1; + setImmediate(_innerAsyncWhile); + } else { + sip = _getLong(blockBuffer, p + 4); + if (ip > sip) { + low = mid + 1; + setImmediate(_innerAsyncWhile); + } else { + sip = _getLong(blockBuffer, p + 8); + _self[ReadData](sip, (err, result) => { + callBack(err, result); + }); + } + } + }); + } + else { + // If we found nothing, return null + return callBack(null, null); + } + } + + _innerAsyncWhile(); } -} -IP2Region._instances = new Map(); + //#endregion +} module.exports = IP2Region; diff --git a/binding/nodejs/ip2region.spec.js b/binding/nodejs/ip2region.spec.js deleted file mode 100644 index 0982fd79..00000000 --- a/binding/nodejs/ip2region.spec.js +++ /dev/null @@ -1,24 +0,0 @@ -const path = require('path'); -const IP2Region = require('./ip2region'); - -describe('ip2region', () => { - let instance; - - beforeAll(() => { - instance = IP2Region.create(path.join(__dirname, '../../data/ip2region.db')); - }); - - afterAll(() => { - instance.destroy(); - }); - - test('should query', () => { - expect(instance.btreeSearchSync('120.24.78.68')).toMatchSnapshot(); - expect(instance.btreeSearchSync('10.10.10.10')).toMatchSnapshot(); - }); - - test('binarySearch', () => { - expect(instance.binarySearchSync('120.24.78.68')).toMatchSnapshot(); - expect(instance.binarySearchSync('10.10.10.10')).toMatchSnapshot(); - }); -}); diff --git a/binding/nodejs/package.json b/binding/nodejs/package.json index 9accef06..e579b54b 100644 --- a/binding/nodejs/package.json +++ b/binding/nodejs/package.json @@ -4,7 +4,8 @@ "description": "ip database ", "main": "ip2region.js", "scripts": { - "test": "jest" + "test": "jest", + "coverage":"npm run test && jest --coverage" }, "repository": { "type": "git", diff --git a/binding/nodejs/tests/constructorTest.spec.js b/binding/nodejs/tests/constructorTest.spec.js new file mode 100644 index 00000000..9e01bf3d --- /dev/null +++ b/binding/nodejs/tests/constructorTest.spec.js @@ -0,0 +1,108 @@ +// This test is used for tesing of a static function `create` of IP2Region +const IP2Region = require('../ip2region'); +const testIps = require('./utils/testData'); +const asyncFor = require('./utils/asyncFor'); + +describe('Constructor Test', () => { + let instance; + + beforeAll(() => { + instance = new IP2Region({ dbPath: '../../data/ip2region.db' }); + }); + + afterAll(() => { + IP2Region.destroy(); + }); + + test('btreeSearchSync query', () => { + for (const ip of testIps) { + expect(instance.btreeSearchSync(ip)).toMatchSnapshot(); + } + }); + + test('binarySearchSync query', () => { + for (const ip of testIps) { + expect(instance.binarySearchSync(ip)).toMatchSnapshot(); + } + }); + + + //#region callBack + test('binarySearch query', (done) => { + asyncFor(testIps, + (value, continueCallBack) => { + instance.binarySearch(value, (err, result) => { + expect(err).toBe(null); + expect(result).toMatchSnapshot(); + continueCallBack(); + }); + }, + () => { done() }); + }); + + test('btreeSearch query', (done) => { + asyncFor(testIps, + (value, continueCallBack) => { + instance.btreeSearch(value, (err, result) => { + expect(err).toBe(null); + expect(result).toMatchSnapshot(); + continueCallBack(); + }); + }, + () => { done() }); + }); + + //#endregion + + //#region Async Promisify test + const node_ver = require('./utils/fetchMainVersion'); + + // If we have Nodejs >= 8, we now support `async` and `await` + if (node_ver >= 8) { + + const asyncBinarySearch = async (ip) => { + + return new Promise((resolve, reject) => { + instance.binarySearch(ip, (err, result) => { + if (err) { + reject(err); + } + else { + resolve(result); + } + }); + }); + + }; + + const asyncBtreeSearch = async (ip) => { + + return new Promise((resolve, reject) => { + instance.btreeSearch(ip, (err, result) => { + if (err) { + reject(err); + } + else { + resolve(result); + } + }); + }); + + }; + + test('async binarySearch query', async () => { + for (let i = 0; i < testIps.length; ++i) { + const result = await asyncBinarySearch(testIps[i]); + expect(result).toMatchSnapshot(); + } + }); + + test('async btreeSearch query', async () => { + for (let i = 0; i < testIps.length; ++i) { + const result = await asyncBtreeSearch(testIps[i]); + expect(result).toMatchSnapshot(); + } + }); + } + //#endregion +}); diff --git a/binding/nodejs/tests/createTest.spec.js b/binding/nodejs/tests/createTest.spec.js new file mode 100644 index 00000000..ca4302ab --- /dev/null +++ b/binding/nodejs/tests/createTest.spec.js @@ -0,0 +1,108 @@ +// This test is used for tesing of a static function `create` of IP2Region +const IP2Region = require('../ip2region'); +const testIps = require('./utils/testData'); +const asyncFor = require('./utils/asyncFor'); + +describe('Create Test', () => { + let instance; + + beforeAll(() => { + instance = IP2Region.create('../../data/ip2region.db'); + }); + + afterAll(() => { + IP2Region.destroy(); + }); + + test('btreeSearchSync query', () => { + for (const ip of testIps) { + expect(instance.btreeSearchSync(ip)).toMatchSnapshot(); + } + }); + + test('binarySearchSync query', () => { + for (const ip of testIps) { + expect(instance.binarySearchSync(ip)).toMatchSnapshot(); + } + }); + + + //#region callBack + test('binarySearch query', (done) => { + asyncFor(testIps, + (value, continueCallBack) => { + instance.binarySearch(value, (err, result) => { + expect(err).toBe(null); + expect(result).toMatchSnapshot(); + continueCallBack(); + }); + }, + () => { done() }); + }); + + test('btreeSearch query', (done) => { + asyncFor(testIps, + (value, continueCallBack) => { + instance.btreeSearch(value, (err, result) => { + expect(err).toBe(null); + expect(result).toMatchSnapshot(); + continueCallBack(); + }); + }, + () => { done() }); + }); + + //#endregion + + //#region Async Promisify test + const node_ver = require('./utils/fetchMainVersion'); + + // If we have Nodejs >= 8, we now support `async` and `await` + if (node_ver >= 8) { + + const asyncBinarySearch = async (ip) => { + + return new Promise((resolve, reject) => { + instance.binarySearch(ip, (err, result) => { + if (err) { + reject(err); + } + else { + resolve(result); + } + }); + }); + + }; + + const asyncBtreeSearch = async (ip) => { + + return new Promise((resolve, reject) => { + instance.btreeSearch(ip, (err, result) => { + if (err) { + reject(err); + } + else { + resolve(result); + } + }); + }); + + }; + + test('async binarySearch query', async () => { + for (let i = 0; i < testIps.length; ++i) { + const result = await asyncBinarySearch(testIps[i]); + expect(result).toMatchSnapshot(); + } + }); + + test('async btreeSearch query', async () => { + for (let i = 0; i < testIps.length; ++i) { + const result = await asyncBtreeSearch(testIps[i]); + expect(result).toMatchSnapshot(); + } + }); + } + //#endregion +}); diff --git a/binding/nodejs/tests/exceptionTest.spec.js b/binding/nodejs/tests/exceptionTest.spec.js new file mode 100644 index 00000000..f3205330 --- /dev/null +++ b/binding/nodejs/tests/exceptionTest.spec.js @@ -0,0 +1,28 @@ +// This test is used for tesing of exceptions + +const IP2Region = require('../ip2region'); + +describe('Constructor Test', () => { + let instance; + + beforeAll(() => { + instance = new IP2Region({ dbPath: '../../data/ip2region.db' }) + }); + + afterAll(() => { + instance.destroy(); + }); + + test('IP invalid test', () => { + const invalidIps = ['255.234.233', '255.255.-1.255', null, undefined, '', 'x.255.y.200']; + for (const ip of invalidIps) { + expect(() => instance.btreeSearchSync(ip)).toThrow(); + expect(() => instance.binarySearchSync(ip)).toThrow(); + } + }); + + test('File Not Found test', () => { + expect(() => new IP2Region({ dbPath: 'A Bad File or Path Here' })).toThrow(); + }); + +}); diff --git a/binding/nodejs/tests/utils/asyncFor.js b/binding/nodejs/tests/utils/asyncFor.js new file mode 100644 index 00000000..a574984d --- /dev/null +++ b/binding/nodejs/tests/utils/asyncFor.js @@ -0,0 +1,23 @@ +/** + * Async For + * @param {Array} groupArray + * @param {Function} exeCallBack + * @param {Function} finalCallBack + */ +function asyncFor(groupArray, exeCallBack, finalCallBack) { + + let i = 0; + + function _innerAsyncLoop() { + if (i < groupArray.length) { + exeCallBack(groupArray[i++], _innerAsyncLoop); + } + else { + finalCallBack(); + } + } + + _innerAsyncLoop(); +} + +module.exports = asyncFor; \ No newline at end of file diff --git a/binding/nodejs/tests/utils/fetchMainVersion.js b/binding/nodejs/tests/utils/fetchMainVersion.js new file mode 100644 index 00000000..ed79119c --- /dev/null +++ b/binding/nodejs/tests/utils/fetchMainVersion.js @@ -0,0 +1,9 @@ +let node_ver = process.version +// Because nodejs's version is something like `v8.11.3`. So we should ignore `v` first +node_ver = node_ver.substr(1); +// Splitted by `.` +node_ver = node_ver.split('.'); +// Take the main version number +node_ver = parseInt(node_ver[0]); + +module.exports = node_ver; diff --git a/binding/nodejs/tests/utils/testData.js b/binding/nodejs/tests/utils/testData.js new file mode 100644 index 00000000..315983c7 --- /dev/null +++ b/binding/nodejs/tests/utils/testData.js @@ -0,0 +1,16 @@ +module.exports = [ + '0.0.0.0', + '10.10.10.10', + '210.109.255.230', + '192.168.0.1', + '255.255.255.255', + '77.49.66.88', + '210.248.255.231', + '35.193.251.120', + '197.84.60.202', + '183.196.233.159', + '20.108.91.101', + '120.196.148.137', + '249.255.250.200', + '112.65.1.130' +] \ No newline at end of file