Ip2region Framework Overview
The ip2region v2.0 library serves as an offline IP address定位 framework and data management solution. It achieves microsecond-level query performance and provides xdb data generation and query client implementations for various mainstream programming languages.
Core Capabilities
Standardized Data Structure
The region information for every IP data segment adheres to a fixed format: Country|Region|Province|City|ISP. Data for China is predominantly precise to the city level, whereas data for other nations may only resolve to the country level, with subsequent fields defaulting to zero.
Data Optimization
The xdb generation process automatically handles data deduplication and compression. For the default global IP dataset, the resulting ip2region.xdb file size is approximately 11 MiB. This size may vary depending on the level of detail in the source data.
Query Acceleration
Querying solely based on the xdb file yields response times in the ten-microsecond range. Performance can be further optimized using two memory-caching strategies:
- vIndex Caching: Allocates a fixed 512 KiB memory buffer to cache vector index data. This reduces disk I/O operations by one and stabilizes query efficiency between 10 to 20 microseconds.
- Full File Caching: Loads the entire xdb file into memory. While memory consumption equals the file size, this eliminates all disk I/O, maintaining microsecond-level query speeds.
Extensible Data Management
The v2.0 xdb format supports billions of IP data segments. Region information is fully customizable, allowing the integration of specific business data such as GPS coordinates, UN geocodes, or postal codes. This flexibility allows developers to use ip2region as a framework for managing proprietary IP geolocation data.
Spring Boot Integration
1. Resource Acquisition
Clone the repository and locate the database file.
git clone https://github.com/lionsoul2014/ip2region.git
Copy the ip2region.xdb file from the data/ directory into your project's src/main/resources folder.
2. Maven Dependencies
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-lang3</artifactId>
<version>3.12.0</version>
</dependency>
<!-- IP Geolocation Library -->
<dependency>
<groupId>org.lionsoul</groupId>
<artifactId>ip2region</artifactId>
<version>2.7.0</version>
</dependency>
<!-- User Agent Parsing -->
<dependency>
<groupId>eu.bitwalker</groupId>
<artifactId>UserAgentUtils</artifactId>
<version>1.21</version>
</dependency>
3. Implementation Logic
The following utility class handles database initialization via a static block, ensuring the data is loaded into memory to resolve issues with accessing files within JAR packages. It also includes logic to extract the real client IP from proxy headers.
package com.example.geo.util;
import eu.bitwalker.useragentutils.UserAgent;
import org.apache.commons.lang3.StringUtils;
import org.lionsoul.ip2region.xdb.Searcher;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.util.FileCopyUtils;
import javax.servlet.http.HttpServletRequest;
import java.io.IOException;
import java.io.InputStream;
import java.net.InetAddress;
import java.net.UnknownHostException;
public class NetworkGeoResolver {
private static final Logger logger = LoggerFactory.getLogger(NetworkGeoResolver.class);
private static final String LOCAL_IPV4 = "127.0.0.1";
private static Searcher locationSearcher;
static {
try {
// Load the xdb file into memory to avoid IO issues in JAR packaging
InputStream dbStream = NetworkGeoResolver.class.getResourceAsStream("/ip2region/ip2region.xdb");
byte[] binaryData = FileCopyUtils.copyToByteArray(dbStream);
locationSearcher = Searcher.newWithBuffer(binaryData);
logger.info("IP2Region database loaded into memory successfully.");
} catch (IOException e) {
logger.error("Failed to initialize IP searcher", e);
throw new RuntimeException("Initialization failed", e);
}
}
public static String determineIpAddress(HttpServletRequest request) {
String clientIp = null;
// Check Kubernetes or specific proxy headers first
clientIp = checkHeader(request, "X-Original-Forwarded-For");
if (clientIp != null) return sanitizeIp(clientIp);
// Standard proxy headers
clientIp = checkHeader(request, "X-Forwarded-For");
if (clientIp != null) return sanitizeIp(clientIp);
clientIp = checkHeader(request, "Proxy-Client-IP");
if (clientIp != null) return sanitizeIp(clientIp);
clientIp = checkHeader(request, "WL-Proxy-Client-IP");
if (clientIp != null) return sanitizeIp(clientIp);
// Fallback to remote address
clientIp = request.getRemoteAddr();
if (LOCAL_IPV4.equals(clientIp)) {
try {
clientIp = InetAddress.getLocalHost().getHostAddress();
} catch (UnknownHostException e) {
logger.error("Error resolving local host", e);
}
}
// Handle IPv6 loopback mapping
return "0:0:0:0:0:0:0:1".equals(clientIp) ? LOCAL_IPV4 : clientIp;
}
private static String checkHeader(HttpServletRequest request, String headerName) {
String value = request.getHeader(headerName);
return (StringUtils.isEmpty(value) || "unknown".equalsIgnoreCase(value)) ? null : value;
}
private static String sanitizeIp(String ipList) {
if (ipList != null && ipList.length() > 15 && ipList.indexOf(",") > 0) {
return ipList.substring(0, ipList.indexOf(","));
}
return ipList;
}
public static UserAgent parseUserAgent(HttpServletRequest request) {
return UserAgent.parseUserAgentString(request.getHeader("User-Agent"));
}
public static String fetchLocationData(String ipAddress) {
if (locationSearcher == null) {
logger.error("Searcher not initialized");
return null;
}
try {
String rawInfo = locationSearcher.search(ipAddress);
if (StringUtils.isNotBlank(rawInfo)) {
// Clean up null placeholders (0)
return rawInfo.replace("|0", "").replace("0|", "");
}
} catch (Exception e) {
logger.error("Failed to search location for IP: {}", ipAddress, e);
}
return null;
}
public static String getHostname() {
try {
return InetAddress.getLocalHost().getHostName();
} catch (UnknownHostException e) {
return "Unknown Host";
}
}
}
4. Verification
The following test case demonstrates how to retrieve geolocation data for a specific IP address.
package com.example.geo;
import com.example.geo.util.NetworkGeoResolver;
import org.junit.jupiter.api.Test;
import org.springframework.boot.test.context.SpringBootTest;
@SpringBootTest
public class GeoLocationTests {
@Test
public void verifyIpLookup() {
// Replace with a valid public IP for testing
String location = NetworkGeoResolver.fetchLocationData("8.8.8.8");
System.out.println("Resolved Location: " + location);
}
}