init commit
This commit is contained in:
+179
@@ -0,0 +1,179 @@
|
||||
package ip
|
||||
|
||||
import (
|
||||
"compress/gzip"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io"
|
||||
"net"
|
||||
"net/http"
|
||||
"strings"
|
||||
|
||||
"go.uber.org/zap"
|
||||
|
||||
"github.com/andybalholm/brotli"
|
||||
"github.com/klauspost/compress/zstd"
|
||||
"github.com/pkg/errors"
|
||||
)
|
||||
|
||||
// GetIP parses the input domain or IP address and returns the IP address.
|
||||
func GetIP(input string) ([]string, error) {
|
||||
// Check if the input is already a valid IP address.
|
||||
if net.ParseIP(input) != nil {
|
||||
return []string{input}, nil
|
||||
}
|
||||
|
||||
// Use net.LookupIP to resolve the domain name.
|
||||
ips, err := net.LookupIP(input)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// Convert IP addresses to string format.
|
||||
var result []string
|
||||
for _, ip := range ips {
|
||||
result = append(result, ip.String())
|
||||
}
|
||||
return result, nil
|
||||
}
|
||||
|
||||
const (
|
||||
ipapi = "ipapi.co"
|
||||
ipbase = "api.ipbase.com"
|
||||
ipwhois = "ipwhois.app"
|
||||
ipinfo = "ipinfo.io"
|
||||
)
|
||||
|
||||
var (
|
||||
queryUrls = map[string]bool{
|
||||
ipbase: true,
|
||||
ipapi: true,
|
||||
ipwhois: true,
|
||||
ipinfo: true,
|
||||
}
|
||||
)
|
||||
|
||||
// GetRegionByIp queries the geolocation of an IP address using supported services.
|
||||
func GetRegionByIp(ip string) (*GeoLocationResponse, error) {
|
||||
for service, enabled := range queryUrls {
|
||||
if enabled {
|
||||
response, err := fetchGeolocation(service, ip)
|
||||
if err != nil {
|
||||
zap.S().Errorf("Failed to fetch geolocation from %s: %v", service, err)
|
||||
continue
|
||||
}
|
||||
return response, nil
|
||||
}
|
||||
}
|
||||
return nil, errors.New("unable to fetch geolocation for the IP")
|
||||
}
|
||||
|
||||
// fetchGeolocation sends a request to the specified service to retrieve geolocation data.
|
||||
func fetchGeolocation(service, ip string) (*GeoLocationResponse, error) {
|
||||
var apiURL string
|
||||
|
||||
// Construct the API URL based on the service.
|
||||
switch service {
|
||||
case ipinfo:
|
||||
apiURL = fmt.Sprintf("https://ipinfo.io/%s/json", ip)
|
||||
case ipapi:
|
||||
apiURL = fmt.Sprintf("https://ipapi.co/%s/json", ip)
|
||||
case ipbase:
|
||||
apiURL = fmt.Sprintf("https://api.ipbase.com/v1/json/%s", ip)
|
||||
case ipwhois:
|
||||
apiURL = fmt.Sprintf("https://ipwhois.app/json/%s", ip)
|
||||
default:
|
||||
return nil, fmt.Errorf("unsupported service: %s", service)
|
||||
}
|
||||
|
||||
// Create the HTTP request.
|
||||
req, err := http.NewRequest("GET", apiURL, nil)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to create request: %v", err)
|
||||
}
|
||||
|
||||
setHeaders(req, service)
|
||||
|
||||
// Create the HTTP client and send the request.
|
||||
client := &http.Client{}
|
||||
resp, err := client.Do(req)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to send request: %v", err)
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
|
||||
// Decompress the response body based on Content-Encoding.
|
||||
body, err := decompressResponse(resp)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
// Parse the JSON response into GeoLocationResponse.
|
||||
var location GeoLocationResponse
|
||||
if err := json.Unmarshal(body, &location); err != nil {
|
||||
return nil, fmt.Errorf("failed to parse response: %v", err)
|
||||
}
|
||||
|
||||
// Ensure compatibility between country fields.
|
||||
if location.Country == "" {
|
||||
location.Country = location.CountryName
|
||||
}
|
||||
|
||||
if location.Loc != "" && strings.Contains(location.Loc, ",") {
|
||||
loc := strings.Split(location.Loc, ",")
|
||||
location.Latitude = loc[0]
|
||||
location.Longitude = loc[1]
|
||||
}
|
||||
|
||||
return &location, nil
|
||||
}
|
||||
|
||||
// setHeaders sets the necessary headers for the HTTP request.
|
||||
func setHeaders(req *http.Request, host string) {
|
||||
req.Header.Set("Host", host)
|
||||
req.Header.Set("User-Agent", "Mozilla/5.0 (X11; Linux x86_64; rv:134.0) Gecko/20100101 Firefox/134.0")
|
||||
req.Header.Set("Accept", "application/json, text/html, application/xhtml+xml, */*;q=0.8")
|
||||
req.Header.Set("Accept-Language", "en-US,en;q=0.5")
|
||||
req.Header.Set("Accept-Encoding", "gzip, deflate, br, zstd")
|
||||
req.Header.Set("Connection", "keep-alive")
|
||||
req.Header.Set("Upgrade-Insecure-Requests", "1")
|
||||
}
|
||||
|
||||
// decompressResponse decompresses the HTTP response body based on its Content-Encoding.
|
||||
func decompressResponse(resp *http.Response) ([]byte, error) {
|
||||
var reader io.ReadCloser
|
||||
var err error
|
||||
|
||||
switch resp.Header.Get("Content-Encoding") {
|
||||
case "gzip":
|
||||
reader, err = gzip.NewReader(resp.Body)
|
||||
case "br":
|
||||
reader = io.NopCloser(brotli.NewReader(resp.Body))
|
||||
case "zstd":
|
||||
decoder, zstdErr := zstd.NewReader(resp.Body)
|
||||
if zstdErr != nil {
|
||||
return nil, fmt.Errorf("failed to create zstd decoder: %v", zstdErr)
|
||||
}
|
||||
defer decoder.Close()
|
||||
return io.ReadAll(decoder)
|
||||
default:
|
||||
reader = resp.Body
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to create reader: %v", err)
|
||||
}
|
||||
defer reader.Close()
|
||||
|
||||
return io.ReadAll(reader)
|
||||
}
|
||||
|
||||
// GeoLocationResponse represents the geolocation data returned by the API.
|
||||
type GeoLocationResponse struct {
|
||||
Country string `json:"country"`
|
||||
CountryName string `json:"country_name"`
|
||||
Region string `json:"region"`
|
||||
City string `json:"city"`
|
||||
Latitude string `json:"latitude"`
|
||||
Longitude string `json:"longitude"`
|
||||
Loc string `json:"loc"`
|
||||
}
|
||||
@@ -0,0 +1,34 @@
|
||||
package ip
|
||||
|
||||
import (
|
||||
"testing"
|
||||
"time"
|
||||
)
|
||||
|
||||
func TestGetIPv4(t *testing.T) {
|
||||
t.Skip("skip TestGetIPv4")
|
||||
iPv4, err := GetIP("baidu.com")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
t.Log(iPv4)
|
||||
}
|
||||
|
||||
func TestGetRegionByIp(t *testing.T) {
|
||||
t.Skip("skip TestGetRegionByIp")
|
||||
ips, err := GetIP("122.14.229.128")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
for _, ip := range ips {
|
||||
t.Log(ip)
|
||||
resp, err := GetRegionByIp(ip)
|
||||
if err != nil {
|
||||
t.Fatalf("ip: %s,err: %v", ip, err)
|
||||
}
|
||||
t.Logf("country: %s,City: %s,latitude:%s, longitude:%s", resp.Country, resp.City, resp.Latitude, resp.Longitude)
|
||||
}
|
||||
time.Sleep(3 * time.Second)
|
||||
}
|
||||
Reference in New Issue
Block a user