核心原理
一个 HTTP 代理服务器的工作流程如下:

- 监听: 代理服务器在指定端口(8080)上监听来自客户端(如浏览器)的连接请求。
- 接收请求: 客户端通过
CONNECT方法(用于 HTTPS)或直接发送 HTTP 请求(用于 HTTP)到代理服务器。 - 解析请求: 代理服务器解析客户端的请求,获取目标服务器的地址(主机名和端口)以及请求的详细信息(如方法、路径、头信息等)。
- 转发请求: 代理服务器作为客户端,向目标服务器发起一个新的请求,将原始请求的内容转发过去。
- 接收响应: 目标服务器处理请求后,将响应返回给代理服务器。
- 转发响应: 代理服务器将收到的响应原封不动地转发给最初的客户端。
- 连接关闭: 客户端和代理服务器之间的连接关闭。
对于 HTTPS,过程稍有不同:
- 客户端首先向代理服务器发送一个
CONNECT example.com:443 HTTP/1.1请求。 - 代理服务器接收到后,不解析具体内容,而是直接与目标服务器(
example.com的 443 端口)建立一条 纯 TCP 隧道。 - 建立成功后,代理服务器返回
HTTP/1.1 200 Connection established。 - 之后,客户端和目标服务器之间的所有通信都通过这个隧道进行,代理服务器只是透明地转发数据包,无法解密 HTTPS 内容(除非配置了 SSL 证书进行中间人攻击)。
使用 Java 标准库 (Java 1.4+)
Java 的标准库提供了 java.net.ServerSocket 和 java.net.Socket,我们可以用它们来构建一个基础的、功能单一的 HTTP 代理,这个实现不会处理 HTTPS 的 CONNECT 方法,但能很好地理解代理的核心逻辑。
代码示例 (仅支持 HTTP):
import java.io.*;
import java.net.*;
public class SimpleHttpProxy {
public static void main(String[] args) throws IOException {
int port = 8080;
try (ServerSocket serverSocket = new ServerSocket(port)) {
System.out.println("代理服务器启动,监听端口: " + port);
while (true) {
// 1. 接受客户端连接
Socket clientSocket = serverSocket.accept();
System.out.println("新的客户端连接: " + clientSocket.getInetAddress());
// 2. 为每个客户端连接创建一个新线程处理
new Thread(new ProxyHandler(clientSocket)).start();
}
}
}
static class ProxyHandler implements Runnable {
private final Socket clientSocket;
public ProxyHandler(Socket clientSocket) {
this.clientSocket = clientSocket;
}
@Override
public void run() {
try (
// 客户端输入流 (来自浏览器的请求)
BufferedReader clientReader = new BufferedReader(new InputStreamReader(clientSocket.getInputStream()));
// 客户端输出流 (发送给浏览器的响应)
OutputStream clientOutputStream = clientSocket.getOutputStream();
// 目标服务器的 Socket
Socket targetSocket = null;
// 目标服务器的输入流 (来自目标服务器的响应)
InputStream targetInputStream = null;
// 目标服务器的输出流 (发送给目标服务器的请求)
OutputStream targetOutputStream = null;
) {
// 3. 解析客户端请求的第一行,获取目标主机和端口
String requestLine = clientReader.readLine();
if (requestLine == null) return;
System.out.println("收到请求: " + requestLine);
String[] parts = requestLine.split(" ");
if (parts.length < 3) return;
String method = parts[0];
String url = parts[1];
// 解析主机和端口
String host;
int port = 80; // 默认 HTTP 端口
if (url.startsWith("http://")) {
url = url.substring(7);
int slashIndex = url.indexOf('/');
if (slashIndex != -1) {
host = url.substring(0, slashIndex);
url = url.substring(slashIndex);
} else {
host = url;
url = "/";
}
} else {
// 处理相对路径,需要从 Host 头中获取主机名
// 这是一个简化版,实际代理需要解析 Host 头
host = "example.com"; // 示例,实际应从请求头中获取
}
if (host.contains(":")) {
String[] hostParts = host.split(":");
host = hostParts[0];
port = Integer.parseInt(hostParts[1]);
}
System.out.println("目标主机: " + host + ", 端口: " + port);
// 4. 连接到目标服务器
targetSocket = new Socket(host, port);
targetInputStream = targetSocket.getInputStream();
targetOutputStream = targetSocket.getOutputStream();
// 5. 转发请求头和请求体
// 先转发请求行
targetOutputStream.write((method + " " + url + " HTTP/1.1\r\n").getBytes());
// 转发其余请求头
String line;
while (!(line = clientReader.readLine()).isEmpty()) {
// 跳过 Host 头,因为我们已经在 URL 中指定了
if (line.toLowerCase().startsWith("host:")) {
targetOutputStream.write(("Host: " + host + "\r\n").getBytes());
} else {
targetOutputStream.write((line + "\r\n").getBytes());
}
}
targetOutputStream.write("\r\n".getBytes()); // 请求头结束
targetOutputStream.flush();
// 6. 转发响应
byte[] buffer = new byte[4096];
int bytesRead;
while ((bytesRead = targetInputStream.read(buffer)) != -1) {
clientOutputStream.write(buffer, 0, bytesRead);
}
clientOutputStream.flush();
} catch (IOException e) {
System.err.println("处理请求时出错: " + e.getMessage());
} finally {
try {
clientSocket.close();
} catch (IOException e) {
// ignore
}
}
}
}
}
如何测试:

- 运行上述 Java 程序。
- 在浏览器或操作系统的网络设置中,将代理服务器设置为
localhost:8080。 - 访问一个 HTTP 网站(如
http://httpbin.org/get),你会在控制台看到代理服务器的日志输出。
使用第三方库 (推荐)
手动实现一个稳定、功能完善的代理服务器非常复杂,需要处理各种 HTTP/HTTPS 规范、连接池、性能优化、编码问题等,在生产环境中,强烈推荐使用成熟的第三方库。
推荐库:Netty
Netty 是一个高性能的异步事件驱动的网络应用框架,非常适合用来构建网络服务,包括代理服务器,它能极大地简化 NIO 编程。
添加 Maven 依赖:
<dependency>
<groupId>io.netty</groupId>
<artifactId>netty-all</artifactId>
<version>4.1.90.Final</version> <!-- 使用最新稳定版本 -->
</dependency>
代码示例 (支持 HTTP 和 HTTPS):

这个例子使用 Netty 的 HttpProxyHandler,它内部已经处理了大部分复杂的逻辑。
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.logging.LogLevel;
import io.netty.handler.logging.LoggingHandler;
import io.netty.handler.proxy.HttpProxyHandler;
import io.netty.handler.proxy.ProxyHandler;
public class NettyHttpProxyServer {
private static final int PORT = 8080;
public static void main(String[] args) throws Exception {
// 1. 创建 EventLoopGroup
EventLoopGroup bossGroup = new NioEventLoopGroup(1);
EventLoopGroup workerGroup = new NioEventLoopGroup();
try {
// 2. 创建 ServerBootstrap
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) throws Exception {
// Netty 的 HttpProxyHandler 会自动处理 CONNECT 请求并建立隧道
// 它也会处理普通的 HTTP 请求转发
ch.pipeline().addLast(new HttpProxyHandler(null)); // null 表示不进行认证
}
});
// 3. 绑定端口并启动
ChannelFuture f = b.bind(PORT).sync();
System.out.println("Netty HTTP 代理服务器已启动,监听端口: " + PORT);
// 4. 等待服务器关闭
f.channel().closeFuture().sync();
} finally {
// 5. 优雅关闭
workerGroup.shutdownGracefully();
bossGroup.shutdownGracefully();
}
}
}
为什么 Netty 更好?
- 异步非阻塞: 性能极高,能处理大量并发连接。
- 功能强大: 内置了对 HTTP/HTTPS 代理、WebSocket 等协议的支持。
- 代码简洁: 相比于手动处理 Socket 和流,Netty 的 API 更清晰、更健壮。
- 生态成熟: 社区活跃,文档丰富,问题容易解决。
完整实战:一个功能更丰富的代理服务器
让我们结合标准库和第三方库的思想,构建一个稍微复杂一点的代理服务器,它可以打印出请求和响应的摘要信息。
这个版本仍然使用标准库,因为它能更好地展示数据流转的过程。
目标功能:
- 支持基本的 HTTP 和
CONNECT请求(HTTPS 隧道)。 - 打印出请求的 URL 和响应的状态码。
import java.io.*;
import java.net.*;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
public class AdvancedHttpProxy {
private static final int PORT = 8080;
private static final ExecutorService threadPool = Executors.newCachedThreadPool();
public static void main(String[] args) throws IOException {
try (ServerSocket serverSocket = new ServerSocket(PORT)) {
System.out.println("高级代理服务器启动,监听端口: " + PORT);
while (true) {
Socket clientSocket = serverSocket.accept();
threadPool.execute(new ProxyTask(clientSocket));
}
}
}
static class ProxyTask implements Runnable {
private final Socket clientSocket;
public ProxyTask(Socket clientSocket) {
this.clientSocket = clientSocket;
}
@Override
public void run() {
try (
BufferedReader clientReader = new BufferedReader(new InputStreamReader(clientSocket.getInputStream()));
OutputStream clientOutputStream = clientSocket.getOutputStream();
) {
// 1. 读取请求行
String requestLine = clientReader.readLine();
if (requestLine == null) return;
System.out.println("\n--- 新请求 ---");
System.out.println("请求行: " + requestLine);
String[] parts = requestLine.split(" ");
String method = parts[0];
String url = parts[1];
// 2. 处理 CONNECT 方法 (HTTPS)
if ("CONNECT".equalsIgnoreCase(method)) {
handleHttpsTunnel(clientReader, clientOutputStream, url);
return;
}
// 3. 处理 HTTP 请求
String host = extractHostFromHeaders(clientReader);
if (host == null) {
sendError(clientOutputStream, 400, "Bad Request: Host header missing");
return;
}
System.out.println("目标主机: " + host);
String targetUrl = "http://" + host + url;
forwardHttpRequest(targetUrl, clientReader, clientOutputStream);
} catch (IOException e) {
System.err.println("处理任务时出错: " + e.getMessage());
} finally {
try {
clientSocket.close();
} catch (IOException e) {
// ignore
}
}
}
private String extractHostFromHeaders(BufferedReader reader) throws IOException {
String line;
while ((line = reader.readLine()) != null && !line.isEmpty()) {
if (line.toLowerCase().startsWith("host:")) {
return line.substring(5).trim();
}
}
return null;
}
private void handleHttpsTunnel(BufferedReader clientReader, OutputStream clientOutputStream, String hostAndPort) throws IOException {
System.out.println("建立 HTTPS 隧道到: " + hostAndPort);
String[] parts = hostAndPort.split(":");
String host = parts[0];
int port = parts.length > 1 ? Integer.parseInt(parts[1]) : 443;
try (Socket targetSocket = new Socket(host, port);
InputStream targetInputStream = targetSocket.getInputStream();
OutputStream targetOutputStream = targetSocket.getOutputStream()) {
// 告诉客户端隧道建立成功
clientOutputStream.write("HTTP/1.1 200 Connection established\r\n\r\n".getBytes());
clientOutputStream.flush();
// 双向转发数据
new Thread(() -> pipeStreams(clientInputStream, targetOutputStream)).start();
pipeStreams(targetInputStream, clientOutputStream);
} catch (IOException e) {
System.err.println("HTTPS 隧道建立失败: " + e.getMessage());
sendError(clientOutputStream, 502, "Bad Gateway");
}
}
private void pipeStreams(InputStream in, OutputStream out) {
byte[] buffer = new byte[4096];
int bytesRead;
try {
while ((bytesRead = in.read(buffer)) != -1) {
out.write(buffer, 0, bytesRead);
out.flush();
}
} catch (IOException e) {
// 连接关闭是正常情况
}
}
private void forwardHttpRequest(String targetUrl, BufferedReader clientReader, OutputStream clientOutputStream) throws IOException {
URL url = new URL(targetUrl);
try (InputStream targetInputStream = url.openStream()) {
// 获取响应头
HttpURLConnection connection = (HttpURLConnection) url.openConnection();
int statusCode = connection.getResponseCode();
System.out.println("响应状态码: " + statusCode);
// 转发响应头
String responseHeader = "HTTP/1.1 " + statusCode + " " + connection.getResponseMessage() + "\r\n";
clientOutputStream.write(responseHeader.getBytes());
for (String key : connection.getHeaderFields().keySet()) {
if (key != null) {
String value = connection.getHeaderField(key);
clientOutputStream.write((key + ": " + value + "\r\n").getBytes());
}
}
clientOutputStream.write("\r\n".getBytes()); // 响应头结束
clientOutputStream.flush();
// 转发响应体
byte[] buffer = new byte[4096];
int bytesRead;
while ((bytesRead = targetInputStream.read(buffer)) != -1) {
clientOutputStream.write(buffer, 0, bytesRead);
}
clientOutputStream.flush();
} catch (IOException e) {
System.err.println("转发 HTTP 请求失败: " + e.getMessage());
sendError(clientOutputStream, 502, "Bad Gateway");
}
}
private void sendError(OutputStream out, int code, String message) throws IOException {
String errorResponse = "HTTP/1.1 " + code + " " + message + "\r\n" +
"Content-Type: text/plain\r\n" +
"Connection: close\r\n" +
"\r\n" +
"Proxy Error: " + message;
out.write(errorResponse.getBytes());
}
}
}
| 方法 | 优点 | 缺点 | 适用场景 |
|---|---|---|---|
| 标准库 | - 无需依赖 - 原理清晰,适合学习 |
- 代码复杂,易出错 - 性能不高(同步阻塞) - 功能有限 |
学习网络编程、快速原型验证 |
| 第三方库 (Netty) | - 性能极高(异步NIO) - 功能强大,稳定可靠 - 代码简洁,易于维护 |
- 需要引入外部依赖 - 学习曲线稍陡 |
生产环境、高性能需求、复杂网络服务 |
建议:
- 如果你是初学者,想了解代理服务器的工作原理,可以从 标准库 的例子开始。
- 如果你要构建一个真正可用的、高性能的代理服务器,请直接使用 Netty 这样的成熟框架,它能让你站在巨人的肩膀上,避免重复造轮子和处理棘手的底层问题。
