java

关注公众号 jb51net

关闭
首页 > 软件编程 > java > Java获取客户端IP地址

Java获取客户端真实IP地址经典写法详解

作者:爱码少年 00fly.online

在Web开发中,获取客户端的真实IP地址是一个常见需求,本文将分别给出经典写法(基于工具类/条件判断)和Lambda写法(基于函数式接口 + 流式处理)的实现,并进行对比

在Web开发中,获取客户端的真实IP地址是一个常见需求。由于客户端可能经过代理、负载均衡或CDN,request.getRemoteAddr() 往往只能拿到代理服务器的IP。因此,需要从特定的HTTP头(如 X-Forwarded-ForProxy-Client-IPWL-Proxy-Client-IP)中解析出原始IP。

下面分别给出经典写法(基于工具类/条件判断)和Lambda写法(基于函数式接口 + 流式处理)的实现,并进行对比。

经典写法(传统工具类)

使用 if-else 链和循环逐层解析,逻辑清晰,易于理解和调试。

import javax.servlet.http.HttpServletRequest;
public class IpUtils {
    /**
     * 获取客户端真实IP地址
     * @param request HttpServletRequest
     * @return 真实IP,如果无法获取则返回 "unknown"
     */
    public static String getClientIp(HttpServletRequest request) {
        String ip = request.getHeader("X-Forwarded-For");
        if (ip == null || ip.isEmpty() || "unknown".equalsIgnoreCase(ip)) {
            ip = request.getHeader("Proxy-Client-IP");
        }
        if (ip == null || ip.isEmpty() || "unknown".equalsIgnoreCase(ip)) {
            ip = request.getHeader("WL-Proxy-Client-IP");
        }
        if (ip == null || ip.isEmpty() || "unknown".equalsIgnoreCase(ip)) {
            ip = request.getHeader("HTTP_CLIENT_IP");
        }
        if (ip == null || ip.isEmpty() || "unknown".equalsIgnoreCase(ip)) {
            ip = request.getHeader("HTTP_X_FORWARDED_FOR");
        }
        if (ip == null || ip.isEmpty() || "unknown".equalsIgnoreCase(ip)) {
            ip = request.getRemoteAddr();
        }
        // 对于通过多级代理的情况,取第一个非 unknown 的IP
        if (ip != null && ip.contains(",")) {
            ip = ip.split(",")[0].trim();
        }
        return ip;
    }
}

Lambda写法(Java 8+)

利用 Stream 和 Optional 对头名称列表进行链式处理,代码更紧凑、函数式风格更明显。

import javax.servlet.http.HttpServletRequest;
import java.util.Arrays;
import java.util.Optional;
public class IpUtilsLambda {
    private static final String[] HEADERS_TO_TRY = {
        "X-Forwarded-For",
        "Proxy-Client-IP",
        "WL-Proxy-Client-IP",
        "HTTP_CLIENT_IP",
        "HTTP_X_FORWARDED_FOR"
    };
    /**
     * 获取客户端真实IP地址(Lambda风格)
     * @param request HttpServletRequest
     * @return 真实IP,默认返回 request.getRemoteAddr()
     */
    public static String getClientIp(HttpServletRequest request) {
        String ip = Arrays.stream(HEADERS_TO_TRY)
                .map(request::getHeader)
                .filter(header -> header != null && !header.isEmpty() && !"unknown".equalsIgnoreCase(header))
                .findFirst()
                .orElseGet(request::getRemoteAddr);
        // 处理多级代理情况
        if (ip != null && ip.contains(",")) {
            ip = ip.split(",")[0].trim();
        }
        return ip;
    }
}

或者更进一步,将分割逻辑也融入流中:

public static String getClientIpAdvanced(HttpServletRequest request) {
    return Arrays.stream(HEADERS_TO_TRY)
            .map(request::getHeader)
            .filter(header -> header != null && !header.isEmpty() && !"unknown".equalsIgnoreCase(header))
            .map(header -> header.contains(",") ? header.split(",")[0].trim() : header)
            .findFirst()
            .orElseGet(request::getRemoteAddr);
}

两种写法对比

对比维度经典写法Lambda写法
可读性直观,每个条件分支清晰可见,适合所有Java开发者代码紧凑,但对不熟悉函数式编程的开发者有一定阅读门槛
代码行数较多(约20行)较少(约10行)
扩展性添加新头需要增加一个 if 块只需在 HEADERS_TO_TRY 数组中增加一个元素
性能几乎无差异,都是 O(n) 遍历,且只在请求生命周期内执行一次同样 O(n),但 Stream 会引入少量额外开销(微乎其微)
调试便利性可在任意 if 处打断点,逐行跟踪流内调试相对困难,需借助 peek() 或 IDE 的流调试插件
错误处理可针对每个头单独处理异常或日志需要额外在 map 或 filter 中处理,不够直观
团队接受度高,任何Java程序员都能立即理解依赖于团队对函数式编程的熟悉程度

注意事项与最佳实践

方法补充

下面对比了两种Java获取客户端IP的实现方式。经典写法使用for循环遍历IP头信息数组,逐个检查并返回第一个有效IP;Lambda写法则利用Stream API,通过链式操作完成相同的逻辑,代码更简洁。两种方法都考虑了多种代理头信息(X-Forwarded-For等)和IP格式处理(分割、去空、trim),最终回退到request.getRemoteAddr()。Lambda写法展现了函数式编程的优势,但两者在功能上完全等效。

经典写法

    @Autowired
    HttpServletRequest request;
    
    String[] IP_HEADERS = {"X-Forwarded-For", "X-Real-IP", "Proxy-Client-IP", "WL-Proxy-Client-IP", "HTTP_X_FORWARDED_FOR", "HTTP_X_FORWARDED", "HTTP_X_CLUSTER_CLIENT_IP", "HTTP_CLIENT_IP", "HTTP_FORWARDED_FOR", "HTTP_FORWARDED", "HTTP_VIA", "REMOTE_ADDR"};
  
    private String getClientIpClassic()
    {
        log.info("##### Classic");
        for (String header : IP_HEADERS)
        {
            String ip = request.getHeader(header);
            if (StringUtils.isNotBlank(ip) && !StringUtils.equalsIgnoreCase("unknown", ip))
            {
                log.info("##### ip header: {}", header);
                String[] ips = ip.split(",");
                return ips[0].trim();
            }
        }
        log.info("##### request.getRemoteAddr");
        return request.getRemoteAddr();
    }

Lambda写法

    private String getClientIpLambda()
    {
        log.info("##### Lambda");
        return Arrays.stream(IP_HEADERS)
            .peek(log::info)
            .map(request::getHeader)
            .filter(ips -> StringUtils.isNotBlank(ips) && !"unknown".equalsIgnoreCase(ips))
            .flatMap(ips -> Arrays.stream(ips.split(",")))
            .filter(StringUtils::isNotBlank)
            .map(String::trim)
            .findFirst()
            .orElse(request.getRemoteAddr());
    }

总结

实际项目中,两种写法均可正确工作。如果项目已经使用 Java 8+ 并普遍采用 Stream API,Lambda写法可以提升代码的表达力;否则,经典写法更加稳妥。

到此这篇关于Java获取客户端真实IP地址经典写法详解的文章就介绍到这了,更多相关Java获取客户端IP地址内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!

您可能感兴趣的文章:
阅读全文