实现逻辑就是转发请求和响应。
核心代码
// 启动代理服务器
private void startProxyServer() {
new Thread(new ProxyServer()).start();
}
// 代理服务器
static class ProxyServer implements Runnable {
@Override
public void run() {
try {
// 监听指定的端口
int port = 8098; //一般使用49152到65535之间的端口
ServerSocket server = new ServerSocket(port);
// 当一个ServerSocket关闭并释放其绑定的端口后,操作系统通常会在几分钟内不允许其他Socket再次绑定到该端口。
// true:操作系统将允许其他Socket立即绑定到刚刚被释放的端口。
server.setReuseAddress(true);
// 使用线程池,防止过多线程耗尽资源
ExecutorService threadPool = Executors.newFixedThreadPool(50);
while (true) {
Socket socket = server.accept(); //会一直阻塞,直到有客户端连接进来
// new Thread 只是创建一个类的对象实例而已。而真正创建线程的是start()方法。
// 这里并没有直接调用start()方法,所以并没创建新线程,而是交给线程池去执行。
threadPool.submit(new ProxyClient(socket));
}
} catch (Exception e) {
Log.e("ProxyServer", e.getMessage(), e);
}
}
}
// 代理客户端
static class ProxyClient implements Runnable {
private final Socket proxySocket;//代理Socket
private Socket targetSocket = null;//目标Socket
public ProxyClient(Socket socket) {
this.proxySocket = socket;
}
@Override
public void run() {
try {
//客户端请求的报文
InputStream req = proxySocket.getInputStream();
int read;
int contentLength = 0;//body长度
String method = null;//请求方法
String url = null;//请求地址
String protocol = null;//请求协议
ByteArrayOutputStream os = new ByteArrayOutputStream();
ByteArrayOutputStream reqBack = new ByteArrayOutputStream();
//解析,提取请求报文
while ((read = req.read()) != -1) {
os.write(read);
reqBack.write(read);
if (read == '\n') {
//CONNECT www.xx.com:443/xx/yy HTTP/1.1
String line = os.toString("UTF-8");
os.reset();//重置,以便再次使用
if ("\r\n".equals(line)) {
//空行,请求头结束标志
break;
}
StringTokenizer stringTokenizer = new StringTokenizer(line, " ");
if (method == null) {
//八种请求方法:GET、POST、HEAD、OPTIONS、PUT、PATCH、DELETE、TRACE、CONNECT 方法
method = stringTokenizer.nextToken().toLowerCase();//connect
url = stringTokenizer.nextToken();//www.xx.com:443/xx/yy
protocol = stringTokenizer.nextToken().trim();//HTTP/1.1
} else {
String key = stringTokenizer.nextToken().toLowerCase();
if ("content-length:".equals(key)) {
String value = stringTokenizer.nextToken().trim();
contentLength = Integer.parseInt(value);
}
}
}
}
if (contentLength > 0) {
for (int i = 0; i < contentLength; i++) {
reqBack.write(req.read());
}
}
//完整请求报文
// String request = reqBack.toString("UTF-8");
// System.out.println("请求报文开始");
// System.out.print(request);
// System.out.println("\r\n请求报文结束");
//拼接完整url
if (url != null && !url.startsWith("http")) {
url = method.equals("connect") ? "https://" + url : "http://" + url;
}
URL u = new URL(url);
//目标ip
String targetHost = u.getHost();
//目标端口
int targetPort = u.getPort();
if (targetPort == -1) {
targetPort = 80;
}
//目标Socket
targetSocket = new Socket(targetHost, targetPort);
if ("connect".equals(method)) {//https
//HTTP/1.1 200 Connection established
//报文直接发送给代理Socket
OutputStream outputStream = proxySocket.getOutputStream();
outputStream.write((protocol + " 200 Connection established\r\n").getBytes(StandardCharsets.UTF_8));
outputStream.write("Proxy-agent: ProxyServer/1.0\r\n".getBytes(StandardCharsets.UTF_8));
outputStream.write("\r\n".getBytes(StandardCharsets.UTF_8));
outputStream.flush();
//前者转发给后者,代理Socket转发给目标Socket
Thread proxy2target = new Thread(new ForwardData(proxySocket, targetSocket));
proxy2target.start();
//前者转发给后者,目标Socket转发给代理Socket
Thread target2proxy = new Thread(new ForwardData(targetSocket, proxySocket));
target2proxy.start();
proxy2target.join();
} else {//http
//请求报文转发给目标Socket
OutputStream outputStream = targetSocket.getOutputStream();
outputStream.write(reqBack.toByteArray());
outputStream.flush();
//前者转发给后者,目标Socket转发给代理Socket
Thread thread = new Thread(new ForwardData(targetSocket, proxySocket));
thread.start();
thread.join();
}
} catch (Exception e) {
Log.e("ProxyClient", e.getMessage(), e);
} finally {
try {
if (targetSocket != null) {
targetSocket.close();
}
} catch (IOException e) {
Log.e("ProxyClient", e.getMessage(), e);
}
try {
if (proxySocket != null) {
proxySocket.close();
}
} catch (IOException e) {
Log.e("ProxyClient", e.getMessage(), e);
}
}
// Log.e("ProxyClient", "结束");
}
// 转发数据
static class ForwardData implements Runnable {
private final Socket inputSocket;
private final Socket outputSocket;
public ForwardData(Socket inputSocket, Socket outputSocket) {
this.inputSocket = inputSocket;
this.outputSocket = outputSocket;
}
@Override
public void run() {
try {
InputStream inputStream = inputSocket.getInputStream();
OutputStream outputStream = outputSocket.getOutputStream();
int read;
while ((read = inputStream.read()) != -1) {
outputStream.write(read);
}
} catch (Exception e) {
// Log.e("ForwardData", inputSocket + e.getMessage());
}
}
}
}
app源码
proxyserver: 代理服务器app
我已打包,打包地址:https://gitee.com/gloweds/proxyserver/raw/master/app/release/app-release.apk
有时会报错,但是这个错误不影响功能。
报错时间线如下:
2023-10-06 11:29:16.478 客户端请求结束(发起http请求)
2023-10-06 11:29:16.555 代理proxySocket的read()报错 Connection reset
2023-10-06 11:29:16.571 关闭两个Socket连接
2023-10-06 11:29:16.571 目标targetSocket的read()报错 Socket closed
上面报错的原因,是因为客户端请求发送报文没有完整发送结束标识-1,
如果客户端完整发送结束标识,上面的两个错误不会发生(Connection reset、Socket closed),但是这个错误不影响功能,可以不用处理。
代理proxySocket的read()报错 Connection reset,是因为客户端未完整发送结束标识-1,而客户端请求都结束了,也成功拿到了响应数据,这时关闭连接就导致代理proxySocket的read()报错 Connection reset。
目标targetSocket的read()报错 Socket closed,因为前面代理proxySocket的read()阻塞未正常发送结束标识,所以targetSocket的read()也阻塞了,关闭两个proxySocket和targetSocket连接后,targetSocket的read()自然报错Socket closed连接被强制关闭了。
https请求流程图: