这是一份非常详细、实用、通俗易懂、权威且全面的 Java 正向代理和反向代理指南。内容由浅入深,包含原理、最佳实践代码和完整系统案例。
目录
- 网络代理基础概念
- 1.1 什么是网络代理?
- 1.2 代理的作用
- 1.3 代理的类型概览 (正向 vs 反向)
- 正向代理详解
- 2.1 正向代理原理剖析
- 2.2 正向代理的核心功能与典型应用场景
- 2.3 Java 实现正向代理的最佳实践
- 2.3.1 使用
HttpClient设置正向代理 - 2.3.2 使用
ProxySelector实现动态代理选择 - 2.3.3 使用第三方库 (如
ProxyServlet)
- 2.3.1 使用
- 反向代理详解
- 3.1 反向代理原理剖析
- 3.2 反向代理的核心功能与典型应用场景
- 3.3 Java 实现反向代理的最佳实践
- 3.3.1 使用
Nginx(非 Java,但常见搭配) - 3.3.2 使用
Spring Cloud Gateway(Java) - 3.3.3 使用
Netty构建高性能反向代理 (Java)
- 3.3.1 使用
- 核心差异与对比
- 4.1 正向代理 vs 反向代理:目的、位置、配置方、服务对象
- Java 实现完整系统案例
- 5.1 案例一:Java HTTP 正向代理服务器 (简易版)
- 5.2 案例二:使用 Spring Cloud Gateway 构建 API 网关 (反向代理)
- 5.3 案例三:使用 Netty 实现带简单负载均衡的反向代理
- 总结与进阶方向
1. 网络代理基础概念
1.1 什么是网络代理?
想象一下你要去一个地方(目标服务器),但中间隔着一堵墙(网络限制)或者你需要一个助手帮你跑腿。网络代理(Proxy)就是这个"中间人"或"助手"。它位于客户端(你)和服务器(目的地)之间,代替客户端向服务器发送请求,并代替服务器将响应返回给客户端。
对于客户端来说,它似乎是在直接和代理服务器通信;对于目标服务器来说,它看到的请求是来自代理服务器,而不是真正的客户端。代理在两者之间起到了"中转"和"隔离"的作用。
1.2 代理的作用
代理服务器扮演着多种重要角色:
- 访问控制与过滤: 限制用户访问特定网站或内容(正向代理常见)。
- 缓存: 存储常用资源,加速后续访问(正向/反向代理均可)。
- 负载均衡: 将客户端请求分发到多个后端服务器,提高性能和可靠性(反向代理核心功能)。
- 安全: 隐藏后端服务器真实 IP 地址,提供一层防护(反向代理常见);提供 SSL 卸载/终止。
- 日志记录与审计: 记录所有经过代理的请求和响应。
- 突破访问限制: 访问被地理位置或其他策略限制的资源(正向代理常见)。
1.3 代理的类型概览 (正向 vs 反向)
根据代理服务器的位置、配置方和服务对象的不同,主要分为两类:
- 正向代理 (Forward Proxy): 通常由客户端(或客户端所在网络)配置。代表客户端去访问外部资源。客户端知道代理的存在,并主动使用它。典型场景:公司内网用户通过代理访问互联网。
- 反向代理 (Reverse Proxy): 通常由服务器端(或服务提供方)配置。代表服务器接收来自互联网的请求。客户端通常不知道代理的存在,以为自己在直接访问目标服务器。典型场景:网站入口、负载均衡器、API 网关。
2. 正向代理详解
2.1 正向代理原理剖析
+--------+ +-----------+ +-----------+
| 客户端 | ------> | 正向代理服务器 | ------> | 目标服务器 |
+--------+ <------ +-----------+ <------ +-----------+
(请求) (请求) (响应)
(响应) (响应)
- 客户端配置: 客户端(浏览器、Java 程序)被显式配置或通过策略(如 PAC 文件)知道要使用哪个代理服务器(IP 和端口)。
- 请求发送: 当客户端需要访问外部资源(如
http://example.com)时,它不会直接向example.com发送请求,而是将请求发送给配置好的正向代理服务器。 - 代理转发: 正向代理服务器收到请求后,解析出客户端真正想要访问的目标地址(
example.com),然后以自己的身份向该目标服务器发起新的请求。 - 响应返回: 目标服务器处理请求,将响应返回给正向代理服务器。
- 代理转交: 正向代理服务器再将收到的响应返回给原始客户端。
关键点:
- 客户端知道并主动使用代理。
- 代理代表客户端。
- 目标服务器看到的是代理的请求,不知道真正的客户端是谁(除非代理传递了原始信息)。
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?query,ProxyServlet会将其转发到http://backend-server:8080/path?query,并将响应返回给客户端。
3. 反向代理详解
3.1 反向代理原理剖析
+--------+ +-------------+ +-----------------+
| 客户端 | ------> | 反向代理服务器 | ------> | 后端服务器集群 |
+--------+ <------ +-------------+ <------ +-----------------+
(请求) (请求) (响应)
(响应) (响应)
- 客户端请求: 客户端向公开的域名或 IP 地址(反向代理服务器)发送请求(如
http://api.example.com)。 - 代理接收: 反向代理服务器接收来自客户端的请求。
- 路由决策: 反向代理根据预定义的规则(如 URL 路径、主机头、负载均衡算法)决定将请求转发到哪个后端服务器(如
http://backend-server-1:8080或http://backend-server-2:8080)。 - 转发请求: 反向代理将客户端的请求(可能稍作修改)转发给选定的后端服务器。
- 后端处理: 后端服务器处理请求并生成响应。
- 返回响应: 后端服务器将响应返回给反向代理。
- 代理响应: 反向代理(可能对响应进行修改,如添加/删除头部)将响应返回给原始客户端。
关键点:
- 客户端通常不知道代理的存在,以为自己在直接访问目标服务。
- 代理代表后端服务器。
- 后端服务器集群对客户端是隐藏的。
- 客户端请求的是代理的地址。
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();
}
}
}
使用说明:
- 运行此 Java 程序。
- 配置你的浏览器或其他 HTTP 客户端,将 HTTP 代理设置为
localhost:8888。 - 尝试访问
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.";
}
}
- 运行与测试:
- 启动 Eureka 服务注册中心(如果使用)。
- 启动
USER-SERVICE服务(监听端口如 8081,注册到 Eureka)。 - 启动
GatewayApplication。 - 访问
http://localhost:8080/api/users/1-> 被路由到USER-SERVICE的/1。 - 访问
http://localhost:8080/api/orders/123-> 被路由到http://localhost:9090/123。 - 访问其他路径
http://localhost:8080/foo-> 被路由到https://example.com/foo。 - 停止
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;
}
}
}
使用说明:
- 准备两个或多个简单的 HTTP 服务器(如使用
python -m http.server 8081和python -m http.server 8082在不同目录)。 - 运行此 Netty 反向代理程序。
- 使用浏览器或
curl访问http://localhost:8888。 - 观察代理日志和两个后端服务器的访问日志,请求应该被轮询分发到不同的后端服务器 (
localhost:8081和localhost: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 网关全面指南。关注我,更多干货分享给你,你的支持就是我创作的最大动力!