实现逻辑就是转发请求和响应。
核心代码
-
- // 启动代理服务器
- 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源码
我已打包,打包地址: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请求流程图:
