ip2region search service & tests
This commit is contained in:
parent
620646e8e0
commit
1f8bf73a0f
|
|
@ -0,0 +1,182 @@
|
||||||
|
// Copyright 2022 The Ip2Region Authors. All rights reserved.
|
||||||
|
// Use of this source code is governed by a Apache2.0-style
|
||||||
|
// license that can be found in the LICENSE file.
|
||||||
|
|
||||||
|
package service
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/lionsoul2014/ip2region/binding/golang/xdb"
|
||||||
|
)
|
||||||
|
|
||||||
|
// ---
|
||||||
|
// Ip2Region service
|
||||||
|
// 1. Unified query interface to IPv4 and IPv6 address.
|
||||||
|
// 2. Concurrency search support.
|
||||||
|
//
|
||||||
|
// @Author Lion <chenxin619315@gmail.com>
|
||||||
|
// @Date 2025/12/05
|
||||||
|
|
||||||
|
type Ip2Region struct {
|
||||||
|
// v4 pool for cache policy vIndex or NoCache
|
||||||
|
v4Pool *SearcherPool
|
||||||
|
|
||||||
|
// v4 xdb searcher for full in-memeory search
|
||||||
|
v4InMemSearcher *xdb.Searcher
|
||||||
|
|
||||||
|
// v6 pool for cache policy vIndex or NoCache:w
|
||||||
|
v6Pool *SearcherPool
|
||||||
|
|
||||||
|
// v6 xdb searcher for full in-memeory search
|
||||||
|
v6InMemSearcher *xdb.Searcher
|
||||||
|
}
|
||||||
|
|
||||||
|
// create a new Ip2Region service with specified v4 and v6 config.
|
||||||
|
// set it to nil to disabled the specified search for the specified version.
|
||||||
|
func NewIp2Region(v4Config *Config, v6Config *Config) (*Ip2Region, error) {
|
||||||
|
var err error
|
||||||
|
|
||||||
|
// check and init the v4 pool or in-memory searcher
|
||||||
|
var v4Pool *SearcherPool
|
||||||
|
var v4InMemSearcher *xdb.Searcher
|
||||||
|
if v4Config == nil {
|
||||||
|
// with IPv4 disabled ?
|
||||||
|
v4Pool = nil
|
||||||
|
v4InMemSearcher = nil
|
||||||
|
} else if v4Config.cachePolicy == BufferCache {
|
||||||
|
v4Pool = nil
|
||||||
|
v4InMemSearcher, err = xdb.NewWithBuffer(v4Config.ipVersion, v4Config.cBuffer)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("failed to create v4 in-memory searcher: %w", err)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
v4InMemSearcher = nil
|
||||||
|
v4Pool, err = NewSearcherPool(v4Config)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("failed to create v4 searcher pool: %w", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// check and init the v6 pool or in-memory searcher
|
||||||
|
var v6Pool *SearcherPool
|
||||||
|
var v6InMemSearcher *xdb.Searcher
|
||||||
|
if v6Config == nil {
|
||||||
|
v6Pool = nil
|
||||||
|
v6InMemSearcher = nil
|
||||||
|
} else if v6Config.cachePolicy == BufferCache {
|
||||||
|
v6Pool = nil
|
||||||
|
v6InMemSearcher, err = xdb.NewWithBuffer(v6Config.ipVersion, v6Config.cBuffer)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("failed to create v6 in-memory searcher: %w", err)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
v6InMemSearcher = nil
|
||||||
|
v6Pool, err = NewSearcherPool(v6Config)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("failed to create v6 in-memeory searcher pool: %w", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return &Ip2Region{
|
||||||
|
v4Pool: v4Pool,
|
||||||
|
v4InMemSearcher: v4InMemSearcher,
|
||||||
|
|
||||||
|
v6Pool: v6Pool,
|
||||||
|
v6InMemSearcher: v6InMemSearcher,
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// create the ip2region search service with the specified v4 & v6 xdb path.
|
||||||
|
// with default cache policy VIndexCache and default searchers = 20
|
||||||
|
func NewIp2RegionWithPath(v4XdbPath string, v6XdbPath string) (*Ip2Region, error) {
|
||||||
|
var err error
|
||||||
|
|
||||||
|
// create v4 config with default config items
|
||||||
|
var v4Config *Config
|
||||||
|
if v4XdbPath == "" {
|
||||||
|
v4Config = nil
|
||||||
|
} else {
|
||||||
|
v4Config, err = NewV4Config(VIndexCache, v4XdbPath, 20)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("failed to create v4 config: %w", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// create v6 config with default config items
|
||||||
|
var v6Config *Config
|
||||||
|
if v6XdbPath == "" {
|
||||||
|
v6Config = nil
|
||||||
|
} else {
|
||||||
|
v6Config, err = NewV6Config(VIndexCache, v6XdbPath, 20)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("failed to create v6 config: %w", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return NewIp2Region(v4Config, v6Config)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (ip2r *Ip2Region) SearchByStr(ipStr string) (string, error) {
|
||||||
|
ipBytes, err := xdb.ParseIP(ipStr)
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
|
||||||
|
return ip2r.Search(ipBytes)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (ip2r *Ip2Region) Search(ipBytes []byte) (string, error) {
|
||||||
|
if l := len(ipBytes); l == 4 {
|
||||||
|
return ip2r.v4Search(ipBytes)
|
||||||
|
} else if l == 16 {
|
||||||
|
return ip2r.v6Search(ipBytes)
|
||||||
|
} else {
|
||||||
|
return "", fmt.Errorf("invalid byte ip address with len=%d", l)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (ip2r *Ip2Region) v4Search(ipBytes []byte) (string, error) {
|
||||||
|
if ip2r.v4InMemSearcher != nil {
|
||||||
|
return ip2r.v4InMemSearcher.Search(ipBytes)
|
||||||
|
}
|
||||||
|
|
||||||
|
// v4 search is disabled
|
||||||
|
if ip2r.v4Pool == nil {
|
||||||
|
return "", nil
|
||||||
|
}
|
||||||
|
|
||||||
|
v4Searcher := ip2r.v4Pool.BorrowSearcher()
|
||||||
|
defer ip2r.v4Pool.ReturnSearcher(v4Searcher)
|
||||||
|
return v4Searcher.Search(ipBytes)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (ip2r *Ip2Region) v6Search(ipBytes []byte) (string, error) {
|
||||||
|
if ip2r.v6InMemSearcher != nil {
|
||||||
|
return ip2r.v6InMemSearcher.Search(ipBytes)
|
||||||
|
}
|
||||||
|
|
||||||
|
// v6 search is disabled
|
||||||
|
if ip2r.v6Pool == nil {
|
||||||
|
return "", nil
|
||||||
|
}
|
||||||
|
|
||||||
|
v6Searcher := ip2r.v6Pool.BorrowSearcher()
|
||||||
|
defer ip2r.v6Pool.ReturnSearcher(v6Searcher)
|
||||||
|
return v6Searcher.Search(ipBytes)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (ip2r *Ip2Region) Close() {
|
||||||
|
ip2r.CloseTimeout(time.Second * 10)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (ip2r *Ip2Region) CloseTimeout(d time.Duration) {
|
||||||
|
if ip2r.v4Pool != nil {
|
||||||
|
ip2r.v4Pool.CloseTimeout(d)
|
||||||
|
}
|
||||||
|
|
||||||
|
if ip2r.v6Pool != nil {
|
||||||
|
ip2r.v6Pool.CloseTimeout(d)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,273 @@
|
||||||
|
// Copyright 2022 The Ip2Region Authors. All rights reserved.
|
||||||
|
// Use of this source code is governed by a Apache2.0-style
|
||||||
|
// license that can be found in the LICENSE file.
|
||||||
|
|
||||||
|
package service
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"sync"
|
||||||
|
"sync/atomic"
|
||||||
|
"testing"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/lionsoul2014/ip2region/binding/golang/xdb"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestConfigCreate(t *testing.T) {
|
||||||
|
v4Config, err := NewV4Config(VIndexCache, "../../../data/ip2region_v4.xdb", 10)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("failed to create v4 config: %s", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
v6Config, err := NewV6Config(VIndexCache, "../../../data/ip2region_v6.xdb", 10)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("failed to create v6 config: %s", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
ip2region, err := NewIp2Region(v4Config, v6Config)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("failed to create ip2region service: %s", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
v4Bytes, err := xdb.ParseIP("219.133.110.197")
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal("invalid ipv4 address")
|
||||||
|
}
|
||||||
|
|
||||||
|
v6Bytes, err := xdb.ParseIP("240e:3b7:3275:f090:d2a3:7d1a:dd90:c3b6")
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("invalid ipv6 address")
|
||||||
|
}
|
||||||
|
|
||||||
|
for i := 0; i < 20; i++ {
|
||||||
|
v4Bytes = xdb.IPAddOne(v4Bytes)
|
||||||
|
v6Bytes = xdb.IPAddOne(v6Bytes)
|
||||||
|
v4Region, err := ip2region.Search(v4Bytes)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("failed to search(%s): %s", xdb.IP2String(v4Bytes), err)
|
||||||
|
}
|
||||||
|
|
||||||
|
v6Region, err := ip2region.Search(v6Bytes)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("failed to search(%s): %s", xdb.IP2String(v6Bytes), err)
|
||||||
|
}
|
||||||
|
|
||||||
|
fmt.Printf(
|
||||||
|
"%2d->search(%s)=%s, search(%s)=%s\n",
|
||||||
|
i, xdb.IP2String(v4Bytes), v4Region, xdb.IP2String(v6Bytes), v6Region,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
ip2region.Close()
|
||||||
|
fmt.Print("ip2region closed gracefully")
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestPathCreate(t *testing.T) {
|
||||||
|
ip2region, err := NewIp2RegionWithPath("../../../data/ip2region_v4.xdb", "../../../data/ip2region_v6.xdb")
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("failed to create ip2region with path: %s", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
v4Bytes, err := xdb.ParseIP("219.133.110.197")
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal("invalid ipv4 address")
|
||||||
|
}
|
||||||
|
|
||||||
|
v6Bytes, err := xdb.ParseIP("240e:3b7:3275:f090:d2a3:7d1a:dd90:c3b6")
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("invalid ipv6 address")
|
||||||
|
}
|
||||||
|
|
||||||
|
for i := 0; i < 20; i++ {
|
||||||
|
v4Bytes = xdb.IPAddOne(v4Bytes)
|
||||||
|
v6Bytes = xdb.IPAddOne(v6Bytes)
|
||||||
|
v4Region, err := ip2region.Search(v4Bytes)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("failed to search(%s): %s", xdb.IP2String(v4Bytes), err)
|
||||||
|
}
|
||||||
|
|
||||||
|
v6Region, err := ip2region.Search(v6Bytes)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("failed to search(%s): %s", xdb.IP2String(v6Bytes), err)
|
||||||
|
}
|
||||||
|
|
||||||
|
fmt.Printf(
|
||||||
|
"%2d->search(%s)=%s, search(%s)=%s\n",
|
||||||
|
i, xdb.IP2String(v4Bytes), v4Region, xdb.IP2String(v6Bytes), v6Region,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
ip2region.Close()
|
||||||
|
fmt.Print("ip2region closed gracefully")
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestInMemSearch(t *testing.T) {
|
||||||
|
v4Config, err := NewV4Config(BufferCache, "../../../data/ip2region_v4.xdb", 10)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("failed to create v4 config: %s", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
v6Config, err := NewV6Config(BufferCache, "../../../data/ip2region_v6.xdb", 10)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("failed to create v6 config: %s", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
ip2region, err := NewIp2Region(v4Config, v6Config)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("failed to create ip2region service: %s", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
v4Bytes, err := xdb.ParseIP("219.133.110.197")
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal("invalid ipv4 address")
|
||||||
|
}
|
||||||
|
|
||||||
|
v6Bytes, err := xdb.ParseIP("240e:3b7:3275:f090:d2a3:7d1a:dd90:c3b6")
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("invalid ipv6 address")
|
||||||
|
}
|
||||||
|
|
||||||
|
for i := 0; i < 20; i++ {
|
||||||
|
v4Bytes = xdb.IPAddOne(v4Bytes)
|
||||||
|
v6Bytes = xdb.IPAddOne(v6Bytes)
|
||||||
|
v4Region, err := ip2region.Search(v4Bytes)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("failed to search(%s): %s", xdb.IP2String(v4Bytes), err)
|
||||||
|
}
|
||||||
|
|
||||||
|
v6Region, err := ip2region.Search(v6Bytes)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("failed to search(%s): %s", xdb.IP2String(v6Bytes), err)
|
||||||
|
}
|
||||||
|
|
||||||
|
fmt.Printf(
|
||||||
|
"%2d->search(%s)=%s, search(%s)=%s\n",
|
||||||
|
i, xdb.IP2String(v4Bytes), v4Region, xdb.IP2String(v6Bytes), v6Region,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
ip2region.Close()
|
||||||
|
fmt.Print("ip2region closed gracefully")
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestV4Only(t *testing.T) {
|
||||||
|
v4Config, err := NewV4Config(NoCache, "../../../data/ip2region_v4.xdb", 10)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("failed to create v4 config: %s", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
ip2region, err := NewIp2Region(v4Config, nil)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("failed to create ip2region service: %s", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
v4Bytes, err := xdb.ParseIP("219.133.110.197")
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal("invalid ipv4 address")
|
||||||
|
}
|
||||||
|
|
||||||
|
v6Bytes, err := xdb.ParseIP("240e:3b7:3275:f090:d2a3:7d1a:dd90:c3b6")
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("invalid ipv6 address")
|
||||||
|
}
|
||||||
|
|
||||||
|
for i := 0; i < 10; i++ {
|
||||||
|
v4Bytes = xdb.IPAddOne(v4Bytes)
|
||||||
|
v6Bytes = xdb.IPAddOne(v6Bytes)
|
||||||
|
v4Region, err := ip2region.Search(v4Bytes)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("failed to search(%s): %s", xdb.IP2String(v4Bytes), err)
|
||||||
|
}
|
||||||
|
|
||||||
|
v6Region, err := ip2region.Search(v6Bytes)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("failed to search(%s): %s", xdb.IP2String(v6Bytes), err)
|
||||||
|
}
|
||||||
|
|
||||||
|
fmt.Printf(
|
||||||
|
"%2d->search(%s)=%s, search(%s)=%s\n",
|
||||||
|
i, xdb.IP2String(v4Bytes), v4Region, xdb.IP2String(v6Bytes), v6Region,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
ip2region.Close()
|
||||||
|
fmt.Print("ip2region closed gracefully")
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestConcurrentCall(t *testing.T) {
|
||||||
|
v4Config, err := NewV4Config(VIndexCache, "../../../data/ip2region_v4.xdb", 15)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("failed to create v4 config: %s", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
v6Config, err := NewV6Config(VIndexCache, "../../../data/ip2region_v6.xdb", 15)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("failed to create v6 config: %s", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
v4Bytes, err := xdb.ParseIP("219.133.110.197")
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal("invalid ipv4 address")
|
||||||
|
}
|
||||||
|
|
||||||
|
v6Bytes, err := xdb.ParseIP("240e:3b7:3275:f090:d2a3:7d1a:dd90:c3b6")
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("invalid ipv6 address")
|
||||||
|
}
|
||||||
|
|
||||||
|
fmt.Printf("v4Config: %s\n", v4Config)
|
||||||
|
fmt.Printf("v6Config: %s\n", v6Config)
|
||||||
|
ip2region, err := NewIp2Region(v4Config, v6Config)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("failed to create ip2region service: %s", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
coroutines := 100
|
||||||
|
var wg sync.WaitGroup
|
||||||
|
var count int64 = 0
|
||||||
|
tStart := time.Now()
|
||||||
|
for i := 0; i < coroutines; i++ {
|
||||||
|
wg.Add(1)
|
||||||
|
go func() {
|
||||||
|
var ipBytes []byte
|
||||||
|
for i := 0; i < 5000; i++ {
|
||||||
|
if i%2 == 0 {
|
||||||
|
ipBytes = v4Bytes
|
||||||
|
} else {
|
||||||
|
ipBytes = v6Bytes
|
||||||
|
}
|
||||||
|
|
||||||
|
region, err := ip2region.Search(ipBytes)
|
||||||
|
if err != nil {
|
||||||
|
fmt.Printf("Error: failed to search(%s): %s", xdb.IP2String(ipBytes), err)
|
||||||
|
break
|
||||||
|
}
|
||||||
|
|
||||||
|
if l := len(ipBytes); l == 4 {
|
||||||
|
if region != "中国|广东省|深圳市|电信" {
|
||||||
|
fmt.Print("Error: region not equals")
|
||||||
|
break
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if region != "中国|广东省|深圳市|家庭宽带" {
|
||||||
|
fmt.Print("Error: region not equals")
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
atomic.AddInt64(&count, 1)
|
||||||
|
}
|
||||||
|
|
||||||
|
// mark all searches finished for this coroutine
|
||||||
|
wg.Done()
|
||||||
|
}()
|
||||||
|
}
|
||||||
|
|
||||||
|
// wait for all the searches to finished
|
||||||
|
wg.Wait()
|
||||||
|
costs := time.Since(tStart)
|
||||||
|
fmt.Printf("%d searches finished in %s, avg took: %s\n", count, costs, costs/time.Duration(count))
|
||||||
|
ip2region.Close()
|
||||||
|
fmt.Print("ip2region closed gracefully")
|
||||||
|
}
|
||||||
Loading…
Reference in New Issue