Merge pull request #466 from lionsoul2014/opt_golang_search_api
Opt golang search api
This commit is contained in:
commit
4d391acac2
|
|
@ -39,8 +39,8 @@ if err != nil {
|
|||
}
|
||||
|
||||
// 4. Export the ip2region service for concurrent dual-version IP address queries, for example:
|
||||
v4Region, err := ip2region.SearchByStr("113.92.157.29") // Perform IPv4 query
|
||||
v6Region, err := ip2region.SearchByStr("240e:3b7:3272:d8d0:db09:c067:8d59:539e") // Perform IPv6 query
|
||||
v4Region, err := ip2region.Search("113.92.157.29") // Perform IPv4 query
|
||||
v6Region, err := ip2region.Search("240e:3b7:3272:d8d0:db09:c067:8d59:539e") // Perform IPv6 query
|
||||
|
||||
|
||||
// 5. When the parent service needs to be closed, close the ip2region query service as well
|
||||
|
|
@ -60,8 +60,7 @@ ip2region.Close()
|
|||
The location information query API prototypes are:
|
||||
|
||||
```go
|
||||
SearchByStr(string) (string, error)
|
||||
Search([]byte) (string, error)
|
||||
Search(string | []byte) (string, error)
|
||||
```
|
||||
|
||||
If a query fails, the `error` will contain specific error details. If successful, it returns the `region` string. If the specified IP cannot be found, it returns an empty string `""`.
|
||||
|
|
@ -121,9 +120,9 @@ func main() {
|
|||
var ip = "1.2.3.4" // IPv4
|
||||
// ip = "240e:3b7:3272:d8d0:db09:c067:8d59:539e" // IPv6
|
||||
var tStart = time.Now()
|
||||
region, err := searcher.SearchByStr(ip)
|
||||
region, err := searcher.Search(ip)
|
||||
if err != nil {
|
||||
fmt.Printf("failed to SearchIP(%s): %s\n", ip, err)
|
||||
fmt.Printf("failed to Search(%s): %s\n", ip, err)
|
||||
return
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -36,8 +36,8 @@ if err != nil {
|
|||
}
|
||||
|
||||
// 4,导出 ip2region 服务进行双版本的IP地址的并发查询,例如:
|
||||
v4Region, err := ip2region.SearchByStr("113.92.157.29") // 进行 IPv4 查询
|
||||
v6Region, err := ip2region.SearchByStr("240e:3b7:3272:d8d0:db09:c067:8d59:539e") // 进行 IPv6 查询
|
||||
v4Region, err := ip2region.Search("113.92.157.29") // 进行 IPv4 查询
|
||||
v6Region, err := ip2region.Search("240e:3b7:3272:d8d0:db09:c067:8d59:539e") // 进行 IPv6 查询
|
||||
|
||||
|
||||
// 5,在服务需要关闭的时候,同时关闭 ip2region 查询服务
|
||||
|
|
@ -55,8 +55,7 @@ ip2region.Close()
|
|||
### 关于查询 API
|
||||
定位信息查询 API 原型为:
|
||||
```go
|
||||
SearchByStr(string) (string, error)
|
||||
Search([]byte) (string, error)
|
||||
Search(string | []byte) (string, error)
|
||||
```
|
||||
查询出错则 error 会包含具体的错误信息,查询成功会返回字符串的 `region` 信息,如果指定的 IP 查询不到则会返回空字符串 `""`。
|
||||
|
||||
|
|
@ -111,9 +110,9 @@ func main() {
|
|||
var ip = "1.2.3.4" // IPv4
|
||||
// ip = "240e:3b7:3272:d8d0:db09:c067:8d59:539e" // IPv6
|
||||
var tStart = time.Now()
|
||||
region, err := searcher.SearchByStr(ip)
|
||||
region, err := searcher.Search(ip)
|
||||
if err != nil {
|
||||
fmt.Printf("failed to SearchIP(%s): %s\n", ip, err)
|
||||
fmt.Printf("failed to Search(%s): %s\n", ip, err)
|
||||
return
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
module github.com/lionsoul2014/ip2region/binding/golang
|
||||
|
||||
go 1.17
|
||||
go 1.18
|
||||
|
||||
require github.com/mitchellh/go-homedir v1.1.0
|
||||
require github.com/mitchellh/go-homedir v1.1.0
|
||||
|
|
|
|||
|
|
@ -239,7 +239,7 @@ type 'quit' to exit
|
|||
}
|
||||
|
||||
tStart := time.Now()
|
||||
region, err := ip2region.SearchByStr(line)
|
||||
region, err := ip2region.Search(line)
|
||||
if err != nil {
|
||||
fmt.Printf("\x1b[0;31m{err: %s}\x1b[0m\n", err.Error())
|
||||
} else {
|
||||
|
|
@ -339,7 +339,13 @@ func testBench() {
|
|||
return
|
||||
}
|
||||
|
||||
for _, ip := range [][]byte{sip, eip} {
|
||||
mip, err := xdb.IPMiddle(sip, eip)
|
||||
if err != nil {
|
||||
fmt.Printf("IPMiddle(%s,%s): %s", xdb.IP2String(sip), xdb.IP2String(eip), err)
|
||||
return
|
||||
}
|
||||
|
||||
for _, ip := range [][]byte{sip, mip, eip} {
|
||||
sTime := time.Now()
|
||||
region, err := searcher.Search(ip)
|
||||
if err != nil {
|
||||
|
|
|
|||
|
|
@ -118,16 +118,21 @@ func NewIp2RegionWithPath(v4XdbPath string, v6XdbPath string) (*Ip2Region, error
|
|||
return NewIp2Region(v4Config, v6Config)
|
||||
}
|
||||
|
||||
func (ip2r *Ip2Region) SearchByStr(ipStr string) (string, error) {
|
||||
ipBytes, err := xdb.ParseIP(ipStr)
|
||||
if err != nil {
|
||||
return "", err
|
||||
func (ip2r *Ip2Region) Search(ip any) (string, error) {
|
||||
var err error
|
||||
var ipBytes []byte
|
||||
switch v := ip.(type) {
|
||||
case string:
|
||||
ipBytes, err = xdb.ParseIP(v)
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("parse ip %s: %w", v, err)
|
||||
}
|
||||
case []byte:
|
||||
ipBytes = v
|
||||
default:
|
||||
return "", fmt.Errorf("invalid ip value type %s", v)
|
||||
}
|
||||
|
||||
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 {
|
||||
|
|
|
|||
|
|
@ -23,7 +23,7 @@ func TestV4SearcherPool(t *testing.T) {
|
|||
ipString := "219.133.110.197"
|
||||
for i := 0; i < 20; i++ {
|
||||
searcher := searcherPool.BorrowSearcher()
|
||||
region, err := searcher.SearchByStr(ipString)
|
||||
region, err := searcher.Search(ipString)
|
||||
if err != nil {
|
||||
t.Fatalf("failed to search(%s): %s", ipString, err)
|
||||
}
|
||||
|
|
@ -53,7 +53,7 @@ func TestV6SearcherPool(t *testing.T) {
|
|||
ipString := "240e:3b7:3275:f090:d2a3:7d1a:dd90:c3b6"
|
||||
for i := 0; i < 20; i++ {
|
||||
searcher := searcherPool.BorrowSearcher()
|
||||
region, err := searcher.SearchByStr(ipString)
|
||||
region, err := searcher.Search(ipString)
|
||||
if err != nil {
|
||||
t.Fatalf("failed to search(%s): %s", ipString, err)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -89,20 +89,24 @@ func (s *Searcher) GetIOCount() int {
|
|||
return s.ioCount
|
||||
}
|
||||
|
||||
// SearchByStr find the region for the specified ip string
|
||||
func (s *Searcher) SearchByStr(str string) (string, error) {
|
||||
ip, err := ParseIP(str)
|
||||
if err != nil {
|
||||
return "", err
|
||||
// Search the region for the specified string or bytes ip address
|
||||
func (s *Searcher) Search(ip any) (string, error) {
|
||||
var err error
|
||||
var ipBytes []byte
|
||||
switch v := ip.(type) {
|
||||
case string:
|
||||
ipBytes, err = ParseIP(v)
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("parse ip %s: %w", v, err)
|
||||
}
|
||||
case []byte:
|
||||
ipBytes = v
|
||||
default:
|
||||
return "", fmt.Errorf("invalid ip value type %s", v)
|
||||
}
|
||||
|
||||
return s.Search(ip)
|
||||
}
|
||||
|
||||
// Search find the region for the specified long ip
|
||||
func (s *Searcher) Search(ip []byte) (string, error) {
|
||||
// ip version check
|
||||
if len(ip) != s.version.Bytes {
|
||||
if len(ipBytes) != s.version.Bytes {
|
||||
return "", fmt.Errorf("invalid ip address(%s expected)", s.version.Name)
|
||||
}
|
||||
|
||||
|
|
@ -110,7 +114,7 @@ func (s *Searcher) Search(ip []byte) (string, error) {
|
|||
s.ioCount = 0
|
||||
|
||||
// locate the segment index block based on the vector index
|
||||
var il0, il1 = int(ip[0]), int(ip[1])
|
||||
var il0, il1 = int(ipBytes[0]), int(ipBytes[1])
|
||||
var idx = il0*VectorIndexCols*VectorIndexSize + il1*VectorIndexSize
|
||||
var sPtr, ePtr = uint32(0), uint32(0)
|
||||
if s.vectorIndex != nil {
|
||||
|
|
@ -139,7 +143,7 @@ func (s *Searcher) Search(ip []byte) (string, error) {
|
|||
}
|
||||
|
||||
// binary search the segment index to get the region
|
||||
var bytes, dBytes = len(ip), len(ip) << 1
|
||||
var bytes, dBytes = len(ipBytes), len(ipBytes) << 1
|
||||
var segIndexSize = uint32(s.version.SegmentIndexSize)
|
||||
var dataLen, dataPtr = 0, uint32(0)
|
||||
var buff = make([]byte, segIndexSize)
|
||||
|
|
@ -153,9 +157,9 @@ func (s *Searcher) Search(ip []byte) (string, error) {
|
|||
}
|
||||
|
||||
// decode the data step by step to reduce the unnecessary operations
|
||||
if s.version.IPCompare(ip, buff[0:bytes]) < 0 {
|
||||
if s.version.IPCompare(ipBytes, buff[0:bytes]) < 0 {
|
||||
h = m - 1
|
||||
} else if s.version.IPCompare(ip, buff[bytes:dBytes]) > 0 {
|
||||
} else if s.version.IPCompare(ipBytes, buff[bytes:dBytes]) > 0 {
|
||||
l = m + 1
|
||||
} else {
|
||||
dataLen = int(binary.LittleEndian.Uint16(buff[dBytes:]))
|
||||
|
|
@ -171,7 +175,7 @@ func (s *Searcher) Search(ip []byte) (string, error) {
|
|||
|
||||
// load and return the region data
|
||||
var regionBuff = make([]byte, dataLen)
|
||||
err := s.read(int64(dataPtr), regionBuff)
|
||||
err = s.read(int64(dataPtr), regionBuff)
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("read region at %d: %w", dataPtr, err)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -82,6 +82,59 @@ func IPSubOne(ip []byte) []byte {
|
|||
return r
|
||||
}
|
||||
|
||||
// IPSub Sub the spcecified two byte ip
|
||||
func IPSub(sip, eip []byte) ([]byte, error) {
|
||||
if len(sip) != len(eip) {
|
||||
return []byte{}, fmt.Errorf("length of the two ips are not the same")
|
||||
}
|
||||
|
||||
var carry uint16 = 0
|
||||
var result = make([]byte, len(sip)+1)
|
||||
|
||||
for i := len(sip) - 1; i >= 0; i-- {
|
||||
sum := uint16(sip[i]) + uint16(eip[i]) + carry
|
||||
result[i+1] = byte(sum) // Store standard 8-bit result
|
||||
carry = sum >> 8 // Extract the 1-bit carry for the next byte
|
||||
}
|
||||
|
||||
// check and append the carry
|
||||
if carry > 0 {
|
||||
result[0] = byte(carry)
|
||||
return result, nil
|
||||
} else {
|
||||
return result[1:], nil
|
||||
}
|
||||
}
|
||||
|
||||
// IPHalf get the half value of an input byte ip
|
||||
func IPHalf(ip []byte) []byte {
|
||||
var length = len(ip)
|
||||
var result = make([]byte, length)
|
||||
// Tracks the bit falling off from the previous byte
|
||||
var carry byte = 0
|
||||
|
||||
for i := 0; i < length; i++ {
|
||||
// 1. Shift current byte right by 1
|
||||
// 2. Or (|) with the carry from the previous byte (shifted to the MSB position)
|
||||
result[i] = (ip[i] >> 1) | (carry << 7)
|
||||
|
||||
// 3. Capture the Least Significant Bit (LSB) to use as carry for the next byte
|
||||
carry = ip[i] & 1
|
||||
}
|
||||
|
||||
return result
|
||||
}
|
||||
|
||||
// IPMiddle get the middle value of two input ip address
|
||||
func IPMiddle(sip, eip []byte) ([]byte, error) {
|
||||
buf, err := IPSub(sip, eip)
|
||||
if err != nil {
|
||||
return []byte{}, fmt.Errorf("IPSub(%s, %s): %w", IP2String(sip), IP2String(eip), err)
|
||||
}
|
||||
|
||||
return IPHalf(buf), nil
|
||||
}
|
||||
|
||||
// Verify if the current Searcher could be used to search the specified xdb file.
|
||||
// Why do we need this check ?
|
||||
// The future features of the xdb impl may cause the current searcher not able to work properly.
|
||||
|
|
|
|||
|
|
@ -9,6 +9,7 @@
|
|||
package xdb
|
||||
|
||||
import (
|
||||
"encoding/binary"
|
||||
"fmt"
|
||||
"testing"
|
||||
"time"
|
||||
|
|
@ -41,6 +42,102 @@ func TestIPCompare(t *testing.T) {
|
|||
}
|
||||
}
|
||||
|
||||
func TestIPSub(t *testing.T) {
|
||||
var strToSub = "1.2.3.4"
|
||||
bytesToSub, err := ParseIP(strToSub)
|
||||
if err != nil {
|
||||
t.Fatalf("failed to parse ip %s", strToSub)
|
||||
}
|
||||
var intToSub = int(binary.BigEndian.Uint32(bytesToSub))
|
||||
t.Logf("to sub ip: %d -> %s", intToSub, strToSub)
|
||||
|
||||
counter := 0
|
||||
buf := make([]byte, 4)
|
||||
for i := 0; i < 0x2FFFFFFF; i++ {
|
||||
binary.BigEndian.PutUint32(buf, uint32(i))
|
||||
subVal, err := IPSub(buf, bytesToSub)
|
||||
if err != nil {
|
||||
t.Fatalf("failed to IPSub(%s,%s): %s", IP2String(buf), strToSub, err)
|
||||
}
|
||||
|
||||
// do it as two integers
|
||||
byteSub := int(binary.BigEndian.Uint32(subVal))
|
||||
intSub := i + intToSub
|
||||
if byteSub != intSub {
|
||||
t.Fatal("byte and int sub value are not the same")
|
||||
}
|
||||
|
||||
counter++
|
||||
}
|
||||
|
||||
t.Logf("test done with %d ips", counter)
|
||||
}
|
||||
|
||||
func TestIPHalf(t *testing.T) {
|
||||
var buf = make([]byte, 4)
|
||||
for i := 0; i < 0xFFFFFFFF; i++ {
|
||||
binary.BigEndian.PutUint32(buf, uint32(i))
|
||||
half := IPHalf(buf)
|
||||
|
||||
// do it as two integers
|
||||
byteMiddle := binary.BigEndian.Uint32(half)
|
||||
intMidle := i >> 1
|
||||
if byteMiddle != uint32(intMidle) {
|
||||
t.Fatal("byte middle and int middle are not the same")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestSubOverflow(t *testing.T) {
|
||||
var ip1Str = "255.255.255.250"
|
||||
ip1Bytes, err := ParseIP(ip1Str)
|
||||
if err != nil {
|
||||
t.Fatalf("failed to ParseIP(%s): %s", ip1Str, err)
|
||||
}
|
||||
|
||||
var buff = make([]byte, 4)
|
||||
for i := 0; i < 10; i++ {
|
||||
binary.BigEndian.PutUint32(buff, uint32(i))
|
||||
ipSub, err := IPSub(ip1Bytes, buff)
|
||||
if err != nil {
|
||||
t.Fatalf("failed to IPSub(%s, %s): %s", ip1Str, IP2String(buff), err)
|
||||
}
|
||||
|
||||
t.Logf("IPSub(%s, %s) = %+v", ip1Str, IP2String(buff), ipSub)
|
||||
}
|
||||
}
|
||||
|
||||
func TestIPMiddle(t *testing.T) {
|
||||
var sIPStr = "0.0.0.0"
|
||||
sBytes, err := ParseIP(sIPStr)
|
||||
if err != nil {
|
||||
t.Fatalf("failed to parse ip %s", sIPStr)
|
||||
}
|
||||
var sInt = int(binary.BigEndian.Uint32(sBytes))
|
||||
t.Logf("start ip: %d -> %s", sInt, sIPStr)
|
||||
|
||||
counter := 0
|
||||
buf := make([]byte, 4)
|
||||
for i := 0; i < 0x0FFFFFFF; i++ {
|
||||
binary.BigEndian.PutUint32(buf, uint32(i))
|
||||
midVal, err := IPMiddle(sBytes, buf)
|
||||
if err != nil {
|
||||
t.Fatalf("failed to IPMiddle(%s,%s): %s", sIPStr, IP2String(buf), err)
|
||||
}
|
||||
|
||||
// do it as two integers
|
||||
byteMid := int(binary.BigEndian.Uint32(midVal))
|
||||
intMid := (sInt + i) >> 1
|
||||
if byteMid != intMid {
|
||||
t.Fatal("byte and int middle value are not the same")
|
||||
}
|
||||
|
||||
counter++
|
||||
}
|
||||
|
||||
t.Logf("test done with %d ips", counter)
|
||||
}
|
||||
|
||||
func TestLoadVectorIndex(t *testing.T) {
|
||||
vIndex, err := LoadVectorIndexFromFile("../../../data/ip2region_v4.xdb")
|
||||
if err != nil {
|
||||
|
|
|
|||
Loading…
Reference in New Issue