Java正向代理与反向代理实战指南

这是一份非常详细、实用、通俗易懂、权威且全面的 Java 正向代理和反向代理指南。内容由浅入深,包含原理、最佳实践代码和完整系统案例。


目录

  1. 网络代理基础概念
    • 1.1 什么是网络代理?
    • 1.2 代理的作用
    • 1.3 代理的类型概览 (正向 vs 反向)
  2. 正向代理详解
    • 2.1 正向代理原理剖析
    • 2.2 正向代理的核心功能与典型应用场景
    • 2.3 Java 实现正向代理的最佳实践
      • 2.3.1 使用 HttpClient 设置正向代理
      • 2.3.2 使用 ProxySelector 实现动态代理选择
      • 2.3.3 使用第三方库 (如 ProxyServlet)
  3. 反向代理详解
    • 3.1 反向代理原理剖析
    • 3.2 反向代理的核心功能与典型应用场景
    • 3.3 Java 实现反向代理的最佳实践
      • 3.3.1 使用 Nginx (非 Java,但常见搭配)
      • 3.3.2 使用 Spring Cloud Gateway (Java)
      • 3.3.3 使用 Netty 构建高性能反向代理 (Java)
  4. 核心差异与对比
    • 4.1 正向代理 vs 反向代理:目的、位置、配置方、服务对象
  5. Java 实现完整系统案例
    • 5.1 案例一:Java HTTP 正向代理服务器 (简易版)
    • 5.2 案例二:使用 Spring Cloud Gateway 构建 API 网关 (反向代理)
    • 5.3 案例三:使用 Netty 实现带简单负载均衡的反向代理
  6. 总结与进阶方向

1. 网络代理基础概念

1.1 什么是网络代理?

想象一下你要去一个地方(目标服务器),但中间隔着一堵墙(网络限制)或者你需要一个助手帮你跑腿。网络代理(Proxy)就是这个"中间人"或"助手"。它位于客户端(你)和服务器(目的地)之间,代替客户端向服务器发送请求,并代替服务器将响应返回给客户端。

对于客户端来说,它似乎是在直接和代理服务器通信;对于目标服务器来说,它看到的请求是来自代理服务器,而不是真正的客户端。代理在两者之间起到了"中转"和"隔离"的作用。

1.2 代理的作用

代理服务器扮演着多种重要角色:

  • 访问控制与过滤: 限制用户访问特定网站或内容(正向代理常见)。
  • 缓存: 存储常用资源,加速后续访问(正向/反向代理均可)。
  • 负载均衡: 将客户端请求分发到多个后端服务器,提高性能和可靠性(反向代理核心功能)。
  • 安全: 隐藏后端服务器真实 IP 地址,提供一层防护(反向代理常见);提供 SSL 卸载/终止。
  • 日志记录与审计: 记录所有经过代理的请求和响应。
  • 突破访问限制: 访问被地理位置或其他策略限制的资源(正向代理常见)。

1.3 代理的类型概览 (正向 vs 反向)

根据代理服务器的位置、配置方和服务对象的不同,主要分为两类:

  • 正向代理 (Forward Proxy): 通常由客户端(或客户端所在网络)配置。代表客户端去访问外部资源。客户端知道代理的存在,并主动使用它。典型场景:公司内网用户通过代理访问互联网。
  • 反向代理 (Reverse Proxy): 通常由服务器端(或服务提供方)配置。代表服务器接收来自互联网的请求。客户端通常不知道代理的存在,以为自己在直接访问目标服务器。典型场景:网站入口、负载均衡器、API 网关。

2. 正向代理详解

2.1 正向代理原理剖析

复制代码
+--------+          +-----------+          +-----------+
| 客户端 |  ------> | 正向代理服务器 | ------> | 目标服务器 |
+--------+ <------  +-----------+ <------ +-----------+
      (请求)               (请求)             (响应)
      (响应)               (响应)
  1. 客户端配置: 客户端(浏览器、Java 程序)被显式配置或通过策略(如 PAC 文件)知道要使用哪个代理服务器(IP 和端口)。
  2. 请求发送: 当客户端需要访问外部资源(如 http://example.com)时,它不会直接向 example.com 发送请求,而是将请求发送给配置好的正向代理服务器。
  3. 代理转发: 正向代理服务器收到请求后,解析出客户端真正想要访问的目标地址(example.com),然后以自己的身份向该目标服务器发起新的请求。
  4. 响应返回: 目标服务器处理请求,将响应返回给正向代理服务器。
  5. 代理转交: 正向代理服务器再将收到的响应返回给原始客户端。

关键点:

  • 客户端知道并主动使用代理。
  • 代理代表客户端
  • 目标服务器看到的是代理的请求,不知道真正的客户端是谁(除非代理传递了原始信息)。

2.2 正向代理的核心功能与典型应用场景

  • 功能:
    • 访问控制: 限制内部用户访问非法或不安全网站。
    • 内容过滤: 过滤广告、恶意软件等。
    • 缓存加速: 缓存常用网站资源,减少带宽消耗,加快访问速度。
    • 日志审计: 记录用户上网行为。
    • 突破限制: 访问被防火墙或地理位置屏蔽的资源(需使用位于可访问区域的代理)。
    • 匿名性: 对目标服务器隐藏客户端真实 IP(但代理本身知道)。
  • 场景:
    • 企业/学校内网访问外网。
    • 个人用户使用代理访问特定资源。
    • 爬虫程序通过代理轮换 IP 避免被封禁。

2.3 Java 实现正向代理的最佳实践

在 Java 应用中,作为客户端使用正向代理访问外部资源非常常见。

2.3.1 使用 HttpClient (Apache HttpClient 或 JDK 11+ HttpClient) 设置正向代理

  • Apache HttpClient (常用):
java 复制代码
import org.apache.http.HttpHost;
import org.apache.http.client.config.RequestConfig;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.impl.client.HttpClients;

public class HttpClientProxyExample {

    public static void main(String[] args) throws Exception {
        // 1. 定义代理服务器地址和端口 (例如: 192.168.1.100:8080)
        HttpHost proxy = new HttpHost("192.168.1.100", 8080);

        // 2. 创建代理配置
        RequestConfig config = RequestConfig.custom()
                .setProxy(proxy)
                .build();

        // 3. 创建 HttpClient 实例,并设置代理配置
        try (CloseableHttpClient httpClient = HttpClients.custom()
                .setDefaultRequestConfig(config)
                .build()) {

            // 4. 创建请求 (目标URL是客户端真正想访问的)
            HttpGet request = new HttpGet("http://example.com");

            // 5. 执行请求 (通过代理访问 example.com)
            httpClient.execute(request, response -> {
                System.out.println("Response Code: " + response.getStatusLine().getStatusCode());
                // ... 处理响应内容 ...
                return null;
            });
        }
    }
}
  • JDK 11+ HttpClient:
java 复制代码
import java.net.URI;
import java.net.http.HttpClient;
import java.net.http.HttpRequest;
import java.net.http.HttpResponse;
import java.net.InetSocketAddress;
import java.net.Proxy;

public class JdkHttpClientProxyExample {

    public static void main(String[] args) throws Exception {
        // 1. 定义代理地址和端口
        Proxy proxy = new Proxy(Proxy.Type.HTTP, new InetSocketAddress("192.168.1.100", 8080));

        // 2. 创建 HttpClient 实例,设置代理
        HttpClient client = HttpClient.newBuilder()
                .proxy(ProxySelector.of(proxy.address()))
                .build();

        // 3. 创建请求 (目标URL是客户端真正想访问的)
        HttpRequest request = HttpRequest.newBuilder()
                .uri(URI.create("http://example.com"))
                .build();

        // 4. 发送请求 (通过代理访问 example.com)
        HttpResponse<String> response = client.send(request, HttpResponse.BodyHandlers.ofString());

        // 5. 输出响应
        System.out.println("Status Code: " + response.statusCode());
        System.out.println("Body: " + response.body());
    }
}

2.3.2 使用 ProxySelector 实现动态代理选择

java.net.ProxySelector 允许你自定义代理选择逻辑。系统默认有一个实现,但你可以覆盖它。

java 复制代码
import java.net.*;
import java.util.List;

public class CustomProxySelector extends ProxySelector {

    private final List<Proxy> proxies;

    public CustomProxySelector(List<Proxy> proxies) {
        this.proxies = proxies;
    }

    @Override
    public List<Proxy> select(URI uri) {
        // 根据 URI (协议、主机等) 动态选择合适的代理
        // 这里简单返回配置好的代理列表
        return proxies;
    }

    @Override
    public void connectFailed(URI uri, SocketAddress sa, IOException ioe) {
        // 处理连接代理失败的情况 (例如:记录日志、移除无效代理)
        System.err.println("Connection to proxy failed: " + sa);
    }

    public static void main(String[] args) throws Exception {
        // 1. 创建代理列表 (可以包含多个代理,如 DIRECT, HTTP, SOCKS)
        List<Proxy> proxyList = List.of(
                new Proxy(Proxy.Type.HTTP, new InetSocketAddress("proxy1.example.com", 8080)),
                new Proxy(Proxy.Type.HTTP, new InetSocketAddress("proxy2.example.com", 8080)) // 备用
        );

        // 2. 设置全局 ProxySelector
        ProxySelector.setDefault(new CustomProxySelector(proxyList));

        // 3. 使用 URLConnection 或 HttpClient (JDK) 等会自动使用配置的代理选择器
        URL url = new URL("http://example.com");
        URLConnection connection = url.openConnection();
        try (java.io.InputStream is = connection.getInputStream()) {
            // ... 读取数据 ...
        }
    }
}

2.3.3 使用第三方库 (如 ProxyServlet)

如果你需要在 Java Web 应用中提供 一个 HTTP 正向代理服务(例如,让浏览器或其他客户端通过你的 Java 应用作为代理访问外部资源),可以使用成熟的库,如 org.mitre.dsmiley.httpproxy:ProxyServlet

  • 依赖 (Maven):
xml 复制代码
<dependency>
    <groupId>org.mitre.dsmiley.httpproxy</groupId>
    <artifactId>smiley-http-proxy-servlet</artifactId>
    <version>1.12.1</version> <!-- 检查最新版本 -->
</dependency>
  • 配置 Servlet (在 web.xml 或 Spring Boot 中):
xml 复制代码
<!-- web.xml -->
<servlet>
    <servlet-name>proxyServlet</servlet-name>
    <servlet-class>org.mitre.dsmiley.httpproxy.ProxyServlet</servlet-class>
    <!-- 可选初始化参数,如 targetUri -->
    <init-param>
        <param-name>targetUri</param-name>
        <param-value>http://backend-server:8080</param-value>
    </init-param>
</servlet>
<servlet-mapping>
    <servlet-name>proxyServlet</servlet-name>
    <url-pattern>/proxy/*</url-pattern>
</servlet-mapping>
java 复制代码
// Spring Boot 配置 (使用 @Bean)
@Bean
public ServletRegistrationBean<ProxyServlet> proxyServletRegistration() {
    ServletRegistrationBean<ProxyServlet> registrationBean = new ServletRegistrationBean<>(new ProxyServlet());
    registrationBean.addUrlMappings("/proxy/*");
    registrationBean.addInitParameter("targetUri", "http://backend-server:8080"); // 可选,可动态计算
    return registrationBean;
}
  • 使用: 客户端将请求发送到 http://your-java-app/proxy/path?queryProxyServlet 会将其转发到 http://backend-server:8080/path?query,并将响应返回给客户端。

3. 反向代理详解

3.1 反向代理原理剖析

复制代码
+--------+          +-------------+          +-----------------+
| 客户端 |  ------> | 反向代理服务器 | ------> | 后端服务器集群 |
+--------+ <------  +-------------+ <------ +-----------------+
      (请求)               (请求)               (响应)
      (响应)               (响应)
  1. 客户端请求: 客户端向公开的域名或 IP 地址(反向代理服务器)发送请求(如 http://api.example.com)。
  2. 代理接收: 反向代理服务器接收来自客户端的请求。
  3. 路由决策: 反向代理根据预定义的规则(如 URL 路径、主机头、负载均衡算法)决定将请求转发到哪个后端服务器(如 http://backend-server-1:8080http://backend-server-2:8080)。
  4. 转发请求: 反向代理将客户端的请求(可能稍作修改)转发给选定的后端服务器。
  5. 后端处理: 后端服务器处理请求并生成响应。
  6. 返回响应: 后端服务器将响应返回给反向代理。
  7. 代理响应: 反向代理(可能对响应进行修改,如添加/删除头部)将响应返回给原始客户端。

关键点:

  • 客户端通常不知道代理的存在,以为自己在直接访问目标服务。
  • 代理代表后端服务器
  • 后端服务器集群对客户端是隐藏的。
  • 客户端请求的是代理的地址

3.2 反向代理的核心功能与典型应用场景

  • 功能:
    • 负载均衡: 将流量均匀分发到多个后端实例,提高吞吐量和容错性。
    • 高可用性: 在后端服务器故障时,将流量重定向到健康实例。
    • 安全: 隐藏后端服务器 IP 和拓扑结构;提供 WAF (Web 应用防火墙) 功能;SSL 终止(在代理处解密 HTTPS,减轻后端负担)。
    • 缓存: 缓存静态内容,加速响应。
    • 压缩: 压缩响应内容,节省带宽。
    • 路由: 基于 URL 路径、主机头、请求参数等将请求路由到不同的后端服务(API 网关的核心功能)。
    • A/B 测试、金丝雀发布: 控制流量流向不同版本的服务。
  • 场景:
    • 大型网站入口(如 www.example.com)。
    • 微服务架构的 API 网关(如 api.example.com)。
    • 负载均衡器处理 Web 服务器、应用服务器集群。
    • 统一入口点,简化客户端访问。

3.3 Java 实现反向代理的最佳实践

3.3.1 使用 Nginx (非 Java,但常见搭配)

虽然 Nginx 本身不是 Java 写的,但它是最流行、最高效的反向代理服务器之一,常与 Java 后端服务搭配使用。配置通常写在 nginx.conf 中:

nginx 复制代码
http {
    upstream backend_servers {
        # 定义后端服务器集群 (负载均衡)
        server backend1.example.com:8080;
        server backend2.example.com:8080;
        # server backend3.example.com:8080 backup; # 备用服务器
        # 可以配置负载均衡策略: least_conn, ip_hash 等
    }

    server {
        listen 80;
        server_name api.example.com; # 客户端访问的域名

        location / {
            # 将所有请求代理到后端集群
            proxy_pass http://backend_servers;
            # 设置必要的头部,让后端知道原始客户端信息
            proxy_set_header Host $host;
            proxy_set_header X-Real-IP $remote_addr;
            proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
            proxy_set_header X-Forwarded-Proto $scheme;
        }
    }
}

3.3.2 使用 Spring Cloud Gateway (Java)

Spring Cloud Gateway 是 Spring Cloud 生态系统提供的基于 Spring 5、Project Reactor 和 Spring Boot 2 构建的 API 网关。它是实现反向代理(特别是微服务场景)的强大工具。

  • 依赖 (Maven - Spring Boot 2.x):
xml 复制代码
<dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-starter-gateway</artifactId>
</dependency>
  • 基础配置示例 (application.yml):
yaml 复制代码
spring:
  cloud:
    gateway:
      routes:
        - id: service_route # 路由ID
          uri: http://backend-service:8080 # 目标服务地址 (可以是 LB 地址)
          predicates:
            - Path=/service/** # 匹配路径规则
          filters:
            - StripPrefix=1 # 去掉路径前缀 (例如 /service/foo -> /foo)
        - id: fallback_route
          uri: https://example.com
          predicates:
            - Path=/**
  • Java DSL 配置示例 (更灵活):
java 复制代码
@Bean
public RouteLocator customRouteLocator(RouteLocatorBuilder builder) {
    return builder.routes()
        .route("service_route", r -> r.path("/service/**")
            .filters(f -> f.stripPrefix(1))
            .uri("http://backend-service:8080")) // 或 lb://service-id (服务发现)
        .route("fallback_route", r -> r.path("/**")
            .uri("https://example.com"))
        .build();
}
  • 功能: Spring Cloud Gateway 提供了强大的路由、断言(Predicate)、过滤器(Filter)机制,支持负载均衡(集成 Ribbon 或 LoadBalancer)、熔断、限流、重写路径、添加/删除请求头等。

3.3.3 使用 Netty 构建高性能反向代理 (Java)

Netty 是一个高性能、异步事件驱动的网络框架。你可以用它构建自定义的、极高性能的反向代理服务器。

java 复制代码
import io.netty.bootstrap.ServerBootstrap;
import io.netty.channel.*;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.SocketChannel;
import io.netty.channel.socket.nio.NioServerSocketChannel;
import io.netty.handler.codec.http.*;
import io.netty.handler.logging.LogLevel;
import io.netty.handler.logging.LoggingHandler;

import java.net.URI;

public class NettyReverseProxy {

    public static void main(String[] args) throws Exception {
        EventLoopGroup bossGroup = new NioEventLoopGroup(1); // 接收连接
        EventLoopGroup workerGroup = new NioEventLoopGroup(); // 处理连接

        try {
            ServerBootstrap b = new ServerBootstrap();
            b.group(bossGroup, workerGroup)
             .channel(NioServerSocketChannel.class)
             .handler(new LoggingHandler(LogLevel.INFO)) // 添加日志
             .childHandler(new ChannelInitializer<SocketChannel>() {
                 @Override
                 protected void initChannel(SocketChannel ch) {
                     ChannelPipeline p = ch.pipeline();
                     p.addLast(new HttpServerCodec()); // HTTP 编解码器
                     p.addLast(new SimpleChannelInboundHandler<FullHttpRequest>() {
                         @Override
                         protected void channelRead0(ChannelHandlerContext ctx, FullHttpRequest request) {
                             // 1. 解析目标地址 (这里简化为固定地址,实际需根据路由规则确定)
                             String targetUriStr = "http://backend-server:8080" + request.uri();
                             URI targetUri = URI.create(targetUriStr);

                             // 2. 创建连接到后端服务器的 Bootstrap (简化版,应复用)
                             Bootstrap backendBootstrap = new Bootstrap()
                                     .channel(ch.channel().getClass())
                                     .group(ch.eventLoop())
                                     .handler(new ChannelInitializer<Channel>() {
                                         @Override
                                         protected void initChannel(Channel ch) {
                                             ch.pipeline().addLast(
                                                     new HttpClientCodec(),
                                                     new HttpObjectAggregator(65536), // 聚合完整请求/响应
                                                     new SimpleChannelInboundHandler<FullHttpResponse>() {
                                                         @Override
                                                         protected void channelRead0(ChannelHandlerContext ctx, FullHttpResponse backendResponse) {
                                                             // 4. 将后端响应写回给客户端
                                                             ctx.channel().writeAndFlush(backendResponse.retainedDuplicate())
                                                                     .addListener(ChannelFutureListener.CLOSE);
                                                         }
                                                     });
                                         }
                                     });

                             // 3. 连接后端服务器并转发请求
                             ChannelFuture f = backendBootstrap.connect(targetUri.getHost(), targetUri.getPort());
                             f.addListener((ChannelFuture future) -> {
                                 if (future.isSuccess()) {
                                     // 连接成功,发送原始请求 (可能需要修改Host头等)
                                     FullHttpRequest copy = request.copy();
                                     copy.headers().set(HttpHeaderNames.HOST, targetUri.getHost()); // 设置正确的Host
                                     future.channel().writeAndFlush(copy);
                                 } else {
                                     // 连接失败,返回错误响应给客户端
                                     ctx.channel().writeAndFlush(new DefaultFullHttpResponse(
                                             HttpVersion.HTTP_1_1, HttpResponseStatus.BAD_GATEWAY))
                                             .addListener(ChannelFutureListener.CLOSE);
                                 }
                             });
                         }
                     });
                 }
             });

            Channel ch = b.bind(8888).sync().channel(); // 监听端口
            ch.closeFuture().sync();
        } finally {
            bossGroup.shutdownGracefully();
            workerGroup.shutdownGracefully();
        }
    }
}

注意: 这是一个高度简化的示例,用于说明原理。生产级实现需要考虑:

  • 连接池管理 (复用 Bootstrap/Channel)
  • 正确处理 HTTP 流水线、Keep-Alive
  • 更复杂的路由规则
  • 头部修改、SSL/TLS、超时处理
  • 负载均衡集成
  • 错误处理和日志
  • 性能优化

4. 核心差异与对比

特性 正向代理 (Forward Proxy) 反向代理 (Reverse Proxy)
位置 靠近客户端 (通常在客户端网络) 靠近服务器端 (在服务提供方网络)
配置方 客户端 (或客户端网络管理员) 主动配置代理 服务器管理员 配置代理
服务对象 代表客户端 访问外部资源 代表后端服务器 接收客户端请求
客户端感知 知道代理存在并主动使用 通常不知道代理存在,以为直连目标服务器
目标服务器感知 知道请求来自代理 (不知道真实客户端) 知道请求来自代理 (但代理可能传递客户端信息)
主要目的 控制/加速/匿名客户端访问 负载均衡、安全、路由、加速服务访问
典型场景 公司内网代理、爬虫代理 网站入口、API 网关、负载均衡器

5. Java 实现完整系统案例

5.1 案例一:Java HTTP 正向代理服务器 (简易版)

这个案例展示一个简单的 Java HTTP 正向代理服务器。客户端配置使用此代理访问外部网站。

java 复制代码
import java.net.*;
import java.io.*;

public class SimpleForwardProxy {

    public static void main(String[] args) throws IOException {
        int proxyPort = 8888; // 代理服务器监听端口
        try (ServerSocket serverSocket = new ServerSocket(proxyPort)) {
            System.out.println("Forward Proxy listening on port " + proxyPort);

            while (true) {
                Socket clientSocket = serverSocket.accept();
                new Thread(() -> handleClient(clientSocket)).start();
            }
        }
    }

    private static void handleClient(Socket clientSocket) {
        try (Socket client = clientSocket;
             BufferedReader clientIn = new BufferedReader(new InputStreamReader(client.getInputStream()));
             OutputStream clientOut = client.getOutputStream()) {

            // 1. 读取客户端请求的第一行 (请求行)
            String requestLine = clientIn.readLine();
            if (requestLine == null || requestLine.isEmpty()) return;
            String[] parts = requestLine.split(" ");
            String method = parts[0];
            String urlStr = parts[1];

            // 2. 解析目标 URL
            URI uri = new URI(urlStr);
            String host = uri.getHost();
            int port = uri.getPort() != -1 ? uri.getPort() : 80; // 默认 HTTP 端口

            // 3. 建立到目标服务器的连接
            try (Socket targetSocket = new Socket(host, port);
                 OutputStream targetOut = targetSocket.getOutputStream();
                 InputStream targetIn = targetSocket.getInputStream()) {

                // 4. 将客户端的请求行转发给目标服务器
                targetOut.write((requestLine + "\r\n").getBytes());

                // 5. 转发剩余的请求头 (需要修正 Host 头)
                String headerLine;
                while (!(headerLine = clientIn.readLine()).isEmpty()) {
                    if (headerLine.startsWith("Host:")) {
                        headerLine = "Host: " + host; // 确保 Host 头正确指向目标服务器
                    }
                    targetOut.write((headerLine + "\r\n").getBytes());
                }
                targetOut.write("\r\n".getBytes()); // 空行结束头部
                targetOut.flush();

                // 6. 转发请求体 (如果有,这里简化处理,只处理 GET)
                // TODO: 处理 POST 等带请求体的方法

                // 7. 将目标服务器的响应转发回客户端
                byte[] buffer = new byte[4096];
                int bytesRead;
                while ((bytesRead = targetIn.read(buffer)) != -1) {
                    clientOut.write(buffer, 0, bytesRead);
                }
                clientOut.flush();
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

使用说明:

  1. 运行此 Java 程序。
  2. 配置你的浏览器或其他 HTTP 客户端,将 HTTP 代理设置为 localhost:8888
  3. 尝试访问 http://example.com。请求会通过此代理服务器转发。

注意: 这是一个非常基础的实现,仅用于演示原理。它不支持 HTTPS、CONNECT 方法(用于 HTTPS 隧道)、完整的 HTTP 头处理、连接复用、性能优化等。生产环境请使用成熟的代理软件或库。

5.2 案例二:使用 Spring Cloud Gateway 构建 API 网关 (反向代理)

这是一个完整的 Spring Boot 应用,使用 Spring Cloud Gateway 作为反向代理 API 网关。

  • 依赖 (pom.xml):
xml 复制代码
<parent>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-parent</artifactId>
    <version>2.7.12</version> <!-- 使用兼容版本 -->
    <relativePath/>
</parent>
<dependencies>
    <dependency>
        <groupId>org.springframework.cloud</groupId>
        <artifactId>spring-cloud-starter-gateway</artifactId>
    </dependency>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-actuator</artifactId>
    </dependency>
    <!-- 如果需要服务发现 (如 Eureka) -->
    <dependency>
        <groupId>org.springframework.cloud</groupId>
        <artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
    </dependency>
</dependencies>
<dependencyManagement>
    <dependencies>
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-dependencies</artifactId>
            <version>2021.0.5</version> <!-- 匹配 Spring Boot 版本 -->
            <type>pom</type>
            <scope>import</scope>
        </dependency>
    </dependencies>
</dependencyManagement>
  • 主应用类 (GatewayApplication.java):
java 复制代码
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;

@SpringBootApplication
public class GatewayApplication {
    public static void main(String[] args) {
        SpringApplication.run(GatewayApplication.class, args);
    }
}
  • 配置文件 (application.yml):
yaml 复制代码
server:
  port: 8080 # 网关监听端口

spring:
  application:
    name: api-gateway
  cloud:
    gateway:
      routes:
        - id: user-service
          uri: lb://USER-SERVICE # 使用 LoadBalancerClient 负载均衡到名为 USER-SERVICE 的服务 (需服务发现)
          predicates:
            - Path=/api/users/**
          filters:
            - StripPrefix=2 # /api/users/foo -> /foo
            - name: CircuitBreaker # 熔断器 (Hystrix 或 Resilience4j)
              args:
                name: userCircuitBreaker
                fallbackUri: forward:/fallback/user
        - id: order-service
          uri: http://localhost:9090 # 直接指向固定地址
          predicates:
            - Path=/api/orders/**
          filters:
            - StripPrefix=2
        - id: fallback-user
          uri: forward:/default-fallback
          predicates:
            - Path=/fallback/user
        - id: static-route
          uri: https://example.com
          predicates:
            - Path=/**
      default-filters: # 全局过滤器
        - AddRequestHeader=X-Request-Gateway, API-Gateway

# 如果使用 Eureka
eureka:
  client:
    service-url:
      defaultZone: http://localhost:8761/eureka
  instance:
    prefer-ip-address: true

# 熔断器配置 (示例用 Hystrix)
hystrix:
  command:
    userCircuitBreaker:
      execution:
        isolation:
          thread:
            timeoutInMilliseconds: 2000 # 超时时间
      circuitBreaker:
        requestVolumeThreshold: 5 # 触发熔断的最小请求数
        sleepWindowInMilliseconds: 5000 # 熔断后尝试恢复时间
  • 熔断 Fallback Controller (可选):
java 复制代码
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;

@RestController
public class FallbackController {

    @GetMapping("/default-fallback")
    public String defaultFallback() {
        return "Service is currently unavailable. Please try again later.";
    }
}
  • 运行与测试:
    1. 启动 Eureka 服务注册中心(如果使用)。
    2. 启动 USER-SERVICE 服务(监听端口如 8081,注册到 Eureka)。
    3. 启动 GatewayApplication
    4. 访问 http://localhost:8080/api/users/1 -> 被路由到 USER-SERVICE/1
    5. 访问 http://localhost:8080/api/orders/123 -> 被路由到 http://localhost:9090/123
    6. 访问其他路径 http://localhost:8080/foo -> 被路由到 https://example.com/foo
    7. 停止 USER-SERVICE,再次访问 /api/users/... 应看到熔断 fallback 信息。

5.3 案例三:使用 Netty 实现带简单负载均衡的反向代理

这个案例扩展了之前简化的 Netty 反向代理,加入简单的轮询负载均衡。

java 复制代码
import io.netty.bootstrap.Bootstrap;
import io.netty.bootstrap.ServerBootstrap;
import io.netty.channel.*;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.SocketChannel;
import io.netty.channel.socket.nio.NioServerSocketChannel;
import io.netty.handler.codec.http.*;
import io.netty.handler.logging.LogLevel;
import io.netty.handler.logging.LoggingHandler;

import java.net.URI;
import java.util.List;
import java.util.concurrent.CopyOnWriteArrayList;
import java.util.concurrent.atomic.AtomicInteger;

public class NettyLoadBalancingReverseProxy {

    // 模拟后端服务器列表 (实际应从配置或服务发现获取)
    private static final List<BackendServer> backendServers = new CopyOnWriteArrayList<>();
    static {
        backendServers.add(new BackendServer("localhost", 8081));
        backendServers.add(new BackendServer("localhost", 8082));
        // 可以添加更多
    }

    // 简单的轮询计数器
    private static final AtomicInteger counter = new AtomicInteger(0);

    public static void main(String[] args) throws Exception {
        EventLoopGroup bossGroup = new NioEventLoopGroup(1);
        EventLoopGroup workerGroup = new NioEventLoopGroup();

        try {
            ServerBootstrap b = new ServerBootstrap();
            b.group(bossGroup, workerGroup)
             .channel(NioServerSocketChannel.class)
             .handler(new LoggingHandler(LogLevel.INFO))
             .childHandler(new ChannelInitializer<SocketChannel>() {
                 @Override
                 protected void initChannel(SocketChannel ch) {
                     ChannelPipeline p = ch.pipeline();
                     p.addLast(new HttpServerCodec());
                     p.addLast(new HttpObjectAggregator(65536)); // 聚合完整请求
                     p.addLast(new ReverseProxyHandler());
                 }
             });

            Channel ch = b.bind(8888).sync().channel();
            ch.closeFuture().sync();
        } finally {
            bossGroup.shutdownGracefully();
            workerGroup.shutdownGracefully();
        }
    }

    private static class ReverseProxyHandler extends SimpleChannelInboundHandler<FullHttpRequest> {
        @Override
        protected void channelRead0(ChannelHandlerContext ctx, FullHttpRequest request) {
            // 1. 根据负载均衡算法选择一个后端服务器
            BackendServer backend = selectBackendServer();
            System.out.println("Routing request to: " + backend);

            // 2. 创建连接到选定后端的 Bootstrap
            Bootstrap backendBootstrap = new Bootstrap()
                    .channel(ctx.channel().getClass())
                    .group(ctx.channel().eventLoop())
                    .handler(new ChannelInitializer<Channel>() {
                        @Override
                        protected void initChannel(Channel ch) {
                            ch.pipeline().addLast(
                                    new HttpClientCodec(),
                                    new HttpObjectAggregator(65536),
                                    new BackendHandler(ctx, request));
                        }
                    });

            // 3. 连接到后端并转发请求
            ChannelFuture connectFuture = backendBootstrap.connect(backend.host, backend.port);
            connectFuture.addListener((ChannelFuture future) -> {
                if (future.isSuccess()) {
                    // 复制请求,修正 Host 头
                    FullHttpRequest copy = request.retainedDuplicate();
                    copy.headers().set(HttpHeaderNames.HOST, backend.host + ":" + backend.port);
                    future.channel().writeAndFlush(copy);
                } else {
                    // 连接失败,返回错误
                    sendErrorResponse(ctx, HttpResponseStatus.BAD_GATEWAY);
                    future.channel().close();
                }
            });
        }

        @Override
        public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) {
            cause.printStackTrace();
            ctx.close();
        }

        private BackendServer selectBackendServer() {
            // 简单轮询 (Round Robin)
            int index = counter.getAndIncrement() % backendServers.size();
            return backendServers.get(index);
            // 实际可扩展为随机、加权、最少连接等算法
        }

        private void sendErrorResponse(ChannelHandlerContext ctx, HttpResponseStatus status) {
            FullHttpResponse response = new DefaultFullHttpResponse(HttpVersion.HTTP_1_1, status);
            response.headers().set(HttpHeaderNames.CONTENT_LENGTH, 0);
            ctx.channel().writeAndFlush(response).addListener(ChannelFutureListener.CLOSE);
        }
    }

    private static class BackendHandler extends SimpleChannelInboundHandler<FullHttpResponse> {
        private final ChannelHandlerContext frontendCtx;
        private final FullHttpRequest originalRequest;

        public BackendHandler(ChannelHandlerContext frontendCtx, FullHttpRequest originalRequest) {
            this.frontendCtx = frontendCtx;
            this.originalRequest = originalRequest;
        }

        @Override
        protected void channelRead0(ChannelHandlerContext backendCtx, FullHttpResponse backendResponse) {
            // 4. 将后端响应写回给前端客户端
            frontendCtx.channel().writeAndFlush(backendResponse.retainedDuplicate())
                    .addListener(future -> {
                        if (!future.isSuccess()) {
                            future.cause().printStackTrace();
                        }
                        // 无论成功与否,都关闭连接 (简化处理)
                        backendCtx.channel().close();
                        frontendCtx.channel().close();
                    });
        }

        @Override
        public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) {
            cause.printStackTrace();
            ReverseProxyHandler.sendErrorResponse(frontendCtx, HttpResponseStatus.BAD_GATEWAY);
            ctx.channel().close();
            frontendCtx.channel().close();
        }
    }

    // 后端服务器信息类
    private static class BackendServer {
        final String host;
        final int port;

        BackendServer(String host, int port) {
            this.host = host;
            this.port = port;
        }

        @Override
        public String toString() {
            return host + ":" + port;
        }
    }
}

使用说明:

  1. 准备两个或多个简单的 HTTP 服务器(如使用 python -m http.server 8081python -m http.server 8082 在不同目录)。
  2. 运行此 Netty 反向代理程序。
  3. 使用浏览器或 curl 访问 http://localhost:8888
  4. 观察代理日志和两个后端服务器的访问日志,请求应该被轮询分发到不同的后端服务器 (localhost:8081localhost:8082)。

注意: 这仍然是基础实现。生产级需要考虑:

  • 健康检查: 定期检查后端服务器是否存活,移除故障节点。
  • 连接池: 复用与后端的连接,提高性能。
  • 更复杂的 LB 算法: 最少连接、加权轮询等。
  • 动态配置: 从外部源(如配置中心、服务注册中心)获取后端列表。
  • HTTPS 支持: 处理 SSL/TLS 终止或隧道。
  • 重试机制: 后端连接失败时重试其他节点。
  • 完善的错误处理和日志。

6. 总结与进阶方向

本指南详细阐述了 Java 正向代理和反向代理的原理、区别、应用场景、最佳实践代码以及完整的系统案例。

  • 总结:
    • 正向代理 是客户端的代理,用于访问外部资源,控制、加速或匿名化客户端请求。
    • 反向代理 是服务器的代理,用于接收客户端请求,提供负载均衡、安全、路由和加速服务访问。
    • Java 可以通过多种方式实现和使用代理:HttpClient/ProxySelector (客户端使用正向代理),Spring Cloud Gateway/Netty (构建反向代理服务器)。
  • 进阶方向:
    • 深入协议: 研究 HTTP/1.1, HTTP/2, WebSocket 在代理中的处理细节。
    • 性能优化: 连接池管理、零拷贝、高效缓冲区使用。
    • 安全性: 实现 WAF 功能、更严格的认证授权、防止 DDoS。
    • 动态配置与服务发现: 与 Consul、Zookeeper、Nacos、Kubernetes 集成,实现自动化服务注册发现和配置更新。
    • 可观测性: 集成 Metrics (Prometheus)、Tracing (Jaeger/Zipkin)、日志 (ELK) 监控代理性能和后端健康。
    • 高级负载均衡算法: 实现最少连接、响应时间加权、一致性哈希等。
    • 高可用与集群: 代理服务器自身的高可用部署方案。
    • 云原生: 在 Kubernetes 环境中部署和管理 API 网关 (如 Spring Cloud Gateway) 和反向代理。

通过掌握这些知识和技能,你将能够设计和实现高效、可靠、安全的 Java 代理解决方案,满足各种网络架构和应用场景的需求。

下期预告:反向代理之基于Spring Cloud Gateway 的微服务 API 网关全面指南。关注我,更多干货分享给你,你的支持就是我创作的最大动力!

相关推荐
2603_949462102 小时前
Flutter for OpenHarmony 社团管理App实战 - 资产管理实现
开发语言·javascript·flutter
naruto_lnq2 小时前
分布式日志系统实现
开发语言·c++·算法
郑州光合科技余经理2 小时前
可独立部署的Java同城O2O系统架构:技术落地
java·开发语言·前端·后端·小程序·系统架构·uni-app
啊我不会诶2 小时前
Codeforces Round 1071 (Div. 3) vp补题
开发语言·学习·算法
json{shen:"jing"}2 小时前
js收官总概述
开发语言·python
froginwe112 小时前
Java 文档注释
开发语言
Zsy_0510032 小时前
【C++】stack、queue、容器适配器
开发语言·c++
笨蛋不要掉眼泪2 小时前
Spring Boot + RedisTemplate 数据结构的基础操作
java·数据结构·spring boot·redis·wpf