使用Java实现HTTP和HTTPS代理服务详解

作者:

文章目录
  • 简单来说就是其他的服务器代替帮你访问HTTP或者HTTPS,当你的网络受限无法访问某些网站时,但是你的代理服务器可以访问某些网站,这时候你就设置代理,通过代理服务器去访问某些网站。例如:你使用某个ip频繁去爬网站信息时,可能你的ip就会被封,这时候你可能就需要代理去切换ip了。
  • 首先我们简单用Java搭一个Socket服务端,打印一下HTTP代理请求报文。 public static void main(String[] args) { try { ServerSocket serverSocket = new ServerSocket(8888); System.out.println(“Server started, waiting for client…”); while (true) { Socket socket = serverSocket.accept(); System.out.println(“Client connected: ” + socket.getInetAddress().getHostAddress()); new Thread(()->{ try(InputStream inputStream = socket.getInputStream()) { BufferedReader reader = new BufferedReader(new InputStreamReader(inputStream)); while (reader.ready()){ System.out.println(reader.readLine()); } } catch (IOException e) { e.printStackTrace(); } }).start(); } } catch (IOException e) { e.printStackTrace(); } } 使用curl -x 127.0.0.1:8888 http://www.baidu.com 设置本地代理8888端口,代理访问百度。 然后看一下Java的控制台,可以看到代理请求过来的HTTP报文。 GET http://www.baidu.com/ HTTP/1.1Host: www.baidu.comUser-Agent: curl/8.4.0Accept: */*Proxy-Connection: Keep-Alive 下面是通火狐浏览器拿到的请求头原始报文,可以发现代理HTTP报文和正常HTTP报文不同处: 第一行中间一个是http://www.baidu.com/,另一个是/。 一个是Proxy-Connection,另一个是Connection。 GET / HTTP/1.1Host: www.baidu.comUser-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:125.0) Gecko/20100101 Firefox/125.0Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,*/*;q=0.8Accept-Language: zh-CN,zh;q=0.8,zh-TW;q=0.7,zh-HK;q=0.5,en-US;q=0.3,en;q=0.2Accept-Encoding: gzip, deflateConnection: keep-aliveCookie: BIDUPSID=52C8D6333DE11AAB7F6B193EE8925F3E; PSTM=1700558835; H_PS_PSSID=40300_40446_40080_60138_40463_60175; BAIDUID=52C8D6333DE11AAB7F6B193EE8925F3E:FG=1; BD_UPN=13314752; COOKIE_SESSION=8189645_0_5_4_2_3_1_0_4_3_31_1_41_0_56_0_1713792557_0_1713792501%7C5%230_0_1713792501%7C1; baikeVisitId=3cf47ba3-c45a-4e5c-bc50-1cef890ac1f1; BA_HECTOR=a02g018h000l2l250k2k8k043iu9l41j2u8gd1t; BDORZ=B490B5EBF6F3CD402E515D22BCDA1598Upgrade-Insecure-Requests: 1 根据上面的不同,我们只需要将代理的报文转成正常的HTTP报文,在Java中添加下面两个方法替换一下内容。 //替换域名部分,将http://www.baidu.com 变成 / private static String replaceDomain(String line) { String reqUrl = line.split(“s+”)[1]; //http://www.baidu.com/ // 提取域名部分 int endIndex = reqUrl.indexOf(“/”, 8); // 从第 8 个字符开始搜索斜杠 String domain = endIndex != -1 ? reqUrl.substring(0, endIndex + 1) : reqUrl; // 替换 return line.replaceFirst(domain, “/”); } //Connection部分,将Proxy-Connection 变成 Connection private static String replaceConnection(String line) { String prefix = “Proxy-Connection: “; return line.replaceFirst(prefix, “Connection: “); } 报文修改后,还得知道这个报文往那个地方发送,可以通Host字段提出域名和端口,代码如下可以拿到Host。 private static Object[] getHeader(String str) { String hostPrefix = “Host: “; String contentLengthPrefix = “Content-Length: “; String host = null; Integer contentLength = null; String[] line = str.split(“rn”); for (String l : line) { if (l.startsWith(hostPrefix)) { host = l.substring(hostPrefix.length()); } else if (l.startsWith(contentLengthPrefix)) { contentLength = Integer.parseInt(l.substring(contentLengthPrefix.length())); } } return new Object[]{host, contentLength}; } 有了Host我可以从里面拿到域名和端口,然后进行Socket连接,然后把修改好的报文写入Socket中。 //获取host String address = (String) getHeader(sb.toString())[0]; //分割host和端口 String[] split = address.split(“:”); String host = split[0]; int port = split.length > 1 ? Integer.parseInt(split[1]) : 80; System.out.println(sb.toString()); //初始化socket连接 socket = new Socket(host, port); //写入第一次请求的数据 socket.getOutputStream().write(sb.toString().getBytes()); 之后创建一个线程去读Socket返回的数据,也就是百度那边返回的数据,拿到数据后再写回给代理的clientSocket。 //创建新线程执行socket读取 Socket finalSocket = socket; new Thread(() -> { try { InputStream socketInputStream = finalSocket.getInputStream(); byte[] socketBt = new byte[1024]; int socketlen = -1; while ((socketlen = socketInputStream.read(socketBt)) != -1) { System.out.println(new String(socketBt, 0, socketlen)); clientSocket.getOutputStream().write(socketBt, 0, socketlen); } socketInputStream.close(); } catch (Exception e) { e.printStackTrace(); } finally { try { finalSocket.close(); } catch (IOException e) { e.printStackTrace(); } } }).start(); 这样就完成了一半,比较麻烦的是在每次连接读取的第一个代理报文,你得判断结束,GET请求可以通过rnrn,POST就得通过Content-Length去判断了,总数据长度 = Content-Length+ 数据中的rnrn位置后。下面代码可能长些,就是判断每次连接的第一个代理报文结束,结束标志要么用Content-Length或者rnrn,拿到第一个代理报文就可以创建Socket连接,之后的数据写入这个Socket就行了。 InputStream inputStream = clientSocket.getInputStream(); StringBuilder sb = new StringBuilder(); byte[] bt = new byte[1024]; int len = -1; boolean isFirstLine = true; int contentLength = -1; while ((len = inputStream.read(bt)) != -1) { //第一次请求 if (socket == null) { if (isFirstLine) { isFirstLine = false; String replace = replaceDomain(bt, len); //跳过HTTPS连接 if (replace.startsWith(“CONNECT”)) { len = -1; break; } sb.append(replace); } else { sb.append(new String(bt, 0, len)); } if (contentLength == -1) { Integer length = (Integer) getHeader(new String(bt, 0, len))[1]; if (length != null) { contentLength = length; } int crlfcrlf = findCRLFCRLF(bt, len) + 4; contentLength -= (len – crlfcrlf); } //长度到达结束 if (contentLength == 0) { break; } if (sb.toString().endsWith(“rnrn”)) { break; } } else { //首行需要修改 if (isFirstLine) { String replace = replaceDomain(bt, len); socket.getOutputStream().write(replace.getBytes()); } else { socket.getOutputStream().write(bt, 0, len); } } } 使用curl -x 127.0.0.1:8888 http://www.baidu.com 测试一下,发现正常响应。 上面是简单的测试,还得用浏览器去测试,话说找一个http的网站,也太难,我这里就用python的文件下载管理页面。 if __name__ == ‘__main__’: # 定义服务器的端口 PORT = 8000 # 创建请求处理程序 Handler = http.server.SimpleHTTPRequestHandler # 设置工作目录 os.chdir(“C:WindowsMedia”) # 创建服务器 with socketserver.TCPServer((“”, PORT), Handler) as httpd: print(f”服务启动在端口 {PORT}”) httpd.serve_forever() 使用火狐浏览设置代理。 搞定,浏览器和控制台显示正常,注意浏览器的地址不要写127.0.0.1,他是不走代理的。
  • HTTPS代理比HTTP代理简单多了,也是通过Socket服务端,使用curl -x 127.0.0.1:8888 https://www.baidu.com 控制台可以拿到下面HTTPS的代理报文。 CONNECT www.baidu.com:443 HTTP/1.1Host: www.baidu.com:443User-Agent: curl/8.4.0Proxy-Connection: Keep-Alive 我简单画一下大致流程,浏览器通过代理把上面CONNECT报文发送Java应用,Java应用拿到报文,从报文中获取到域名和端口,通过域名和端口连接百度服务器,连接成功后,回应浏览器HTTP/1.1 200 Connection estabished告诉他这边已经连接好了,可以发送数据了,最后浏览器和百度服务器双方交换数据。 浏览器Java应用百度服务器CONNECT报文连接HTTP/1.1 200 Connection estabished发送数据写入数据浏览器Java应用百度服务器 逻辑和HTTP一样的,只是他不用把CONNECT报文发送到百度服务器上,就不用修改报文,而且结束只用通过rnrn判断,和百度服务器连接上就回应HTTP/1.1 200 Connection estabished,简单多了。 以上就是使用Java实现HTTP和HTTPS代理服务详解的详细内容,更多关于Java实现HTTP和HTTPS代理的资料请关注风君子博客其它相关文章! 您可能感兴趣的文章: java实现轻量型http代理服务器示例 java实现http的Post、Get、代理访问请求 java httpclient设置超时时间和代理的方法 JAVA HTTP反向代理实现过程详解 详解Java是如何通过接口来创建代理并进行http请求 java http请求设置代理Proxy的两种常见方法
  • 目录
    • HTTP代理和HTTPS代理介绍
    • HTTP代理
    • HTTPS代理

    简单来说就是其他的服务器代替帮你访问HTTP或者HTTPS,当你的网络受限无法访问某些网站时,但是你的代理服务器可以访问某些网站,这时候你就设置代理,通过代理服务器去访问某些网站。例如:你使用某个ip频繁去爬网站信息时,可能你的ip就会被封,这时候你可能就需要代理去切换ip了。

    首先我们简单用Java搭一个Socket服务端,打印一下HTTP代理请求报文。

    public static void main(String[] args) {
        try {
            ServerSocket serverSocket = new ServerSocket(8888);
            System.out.println("Server started, waiting for client...");
            while (true) {
                Socket socket = serverSocket.accept();
                System.out.println("Client connected: " + socket.getInetAddress().getHostAddress());
                new Thread(()->{
                    try(InputStream inputStream = socket.getInputStream()) {
                        BufferedReader reader = new BufferedReader(new InputStreamReader(inputStream));
                        while (reader.ready()){
                            System.out.println(reader.readLine());
                        }
                    } catch (IOException e) {
                        e.printStackTrace();
                    }
                }).start();
            }
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
    

    使用curl -x 127.0.0.1:8888 http://www.baidu.com 设置本地代理8888端口,代理访问百度。

    然后看一下Java的控制台,可以看到代理请求过来的HTTP报文。

    GET http://www.baidu.com/ HTTP/1.1
    Host: www.baidu.com
    User-Agent: curl/8.4.0
    Accept: */*
    Proxy-Connection: Keep-Alive

    下面是通火狐浏览器拿到的请求头原始报文,可以发现代理HTTP报文和正常HTTP报文不同处:

    第一行中间一个是http://www.baidu.com/,另一个是/

    一个是Proxy-Connection,另一个是Connection

    GET / HTTP/1.1
    Host: www.baidu.com
    User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:125.0) Gecko/20100101 Firefox/125.0
    Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,*/*;q=0.8
    Accept-Language: zh-CN,zh;q=0.8,zh-TW;q=0.7,zh-HK;q=0.5,en-US;q=0.3,en;q=0.2
    Accept-Encoding: gzip, deflate
    Connection: keep-alive
    Cookie: BIDUPSID=52C8D6333DE11AAB7F6B193EE8925F3E; PSTM=1700558835; H_PS_PSSID=40300_40446_40080_60138_40463_60175; BAIDUID=52C8D6333DE11AAB7F6B193EE8925F3E:FG=1; BD_UPN=13314752; COOKIE_SESSION=8189645_0_5_4_2_3_1_0_4_3_31_1_41_0_56_0_1713792557_0_1713792501%7C5%230_0_1713792501%7C1; baikeVisitId=3cf47ba3-c45a-4e5c-bc50-1cef890ac1f1; BA_HECTOR=a02g018h000l2l250k2k8k043iu9l41j2u8gd1t; BDORZ=B490B5EBF6F3CD402E515D22BCDA1598
    Upgrade-Insecure-Requests: 1

    根据上面的不同,我们只需要将代理的报文转成正常的HTTP报文,在Java中添加下面两个方法替换一下内容。

    //替换域名部分,将http://www.baidu.com 变成 /
    private static String replaceDomain(String line) {
        String reqUrl = line.split("s+")[1];
        //http://www.baidu.com/
        // 提取域名部分
        int endIndex = reqUrl.indexOf("/", 8); // 从第 8 个字符开始搜索斜杠
        String domain = endIndex != -1 ? reqUrl.substring(0, endIndex + 1) : reqUrl;
        // 替换
        return line.replaceFirst(domain, "/");
    }
    //Connection部分,将Proxy-Connection 变成 Connection
    private static String replaceConnection(String line) {
        String prefix = "Proxy-Connection: ";
        return line.replaceFirst(prefix, "Connection: ");
    }

    报文修改后,还得知道这个报文往那个地方发送,可以通Host字段提出域名和端口,代码如下可以拿到Host。

    private static Object[] getHeader(String str) {
        String hostPrefix = "Host: ";
        String contentLengthPrefix = "Content-Length: ";
        String host = null;
        Integer contentLength = null;
        String[] line = str.split("rn");
        for (String l : line) {
            if (l.startsWith(hostPrefix)) {
                host = l.substring(hostPrefix.length());
            } else if (l.startsWith(contentLengthPrefix)) {
                contentLength = Integer.parseInt(l.substring(contentLengthPrefix.length()));
            }
        }
        return new Object[]{host, contentLength};
    }
    

    有了Host我可以从里面拿到域名和端口,然后进行Socket连接,然后把修改好的报文写入Socket中。

    //获取host
    String address = (String) getHeader(sb.toString())[0];
    //分割host和端口
    String[] split = address.split(":");
    String host = split[0];
    int port = split.length > 1 ? Integer.parseInt(split[1]) : 80;
    System.out.println(sb.toString());
    //初始化socket连接
    socket = new Socket(host, port);
    //写入第一次请求的数据
    socket.getOutputStream().write(sb.toString().getBytes());
    

    之后创建一个线程去读Socket返回的数据,也就是百度那边返回的数据,拿到数据后再写回给代理的clientSocket

    //创建新线程执行socket读取
    Socket finalSocket = socket;
    new Thread(() -> {
        try {
            InputStream socketInputStream = finalSocket.getInputStream();
    
            byte[] socketBt = new byte[1024];
            int socketlen = -1;
            while ((socketlen = socketInputStream.read(socketBt)) != -1) {
                System.out.println(new String(socketBt, 0, socketlen));
                clientSocket.getOutputStream().write(socketBt, 0, socketlen);
            }
            socketInputStream.close();
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            try {
                finalSocket.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }).start();
    

    这样就完成了一半,比较麻烦的是在每次连接读取的第一个代理报文,你得判断结束,GET请求可以通过rnrn,POST就得通过Content-Length去判断了,总数据长度 = Content-Length+ 数据中的rnrn位置后。下面代码可能长些,就是判断每次连接的第一个代理报文结束,结束标志要么用Content-Length或者rnrn,拿到第一个代理报文就可以创建Socket连接,之后的数据写入这个Socket就行了。

    InputStream inputStream = clientSocket.getInputStream();
    StringBuilder sb = new StringBuilder();
    byte[] bt = new byte[1024];
    int len = -1;
    boolean isFirstLine = true;
    int contentLength = -1;
    while ((len = inputStream.read(bt)) != -1) {
        
        //第一次请求
        if (socket == null) {
    
            if (isFirstLine) {
                isFirstLine = false;
    
                String replace = replaceDomain(bt, len);
                
                //跳过HTTPS连接
                if (replace.startsWith("CONNECT")) {
                    len = -1;
                    break;
                }
                sb.append(replace);
            } else {
                sb.append(new String(bt, 0, len));
            }
            if (contentLength == -1) {
                Integer length = (Integer) getHeader(new String(bt, 0, len))[1];
                if (length != null) {
                    contentLength = length;
                }
                int crlfcrlf = findCRLFCRLF(bt, len) + 4;
                contentLength -= (len - crlfcrlf);
            }
            //长度到达结束
            if (contentLength == 0) {
                break;
            }
            if (sb.toString().endsWith("rnrn")) {
                break;
            }
        } else {
            //首行需要修改
            if (isFirstLine) {
                String replace = replaceDomain(bt, len);
                socket.getOutputStream().write(replace.getBytes());
            } else {
                socket.getOutputStream().write(bt, 0, len);
            }
        }
    }
    

    使用curl -x 127.0.0.1:8888 http://www.baidu.com 测试一下,发现正常响应。

    上面是简单的测试,还得用浏览器去测试,话说找一个http的网站,也太难,我这里就用python的文件下载管理页面。

    if __name__ == '__main__':
        # 定义服务器的端口
        PORT = 8000
        # 创建请求处理程序
        Handler = http.server.SimpleHTTPRequestHandler
        # 设置工作目录
        os.chdir("C:WindowsMedia")
        # 创建服务器
        with socketserver.TCPServer(("", PORT), Handler) as httpd:
            print(f"服务启动在端口 {PORT}")
            httpd.serve_forever()
    

    使用火狐浏览设置代理。

    搞定,浏览器和控制台显示正常,注意浏览器的地址不要写127.0.0.1,他是不走代理的。

    HTTPS代理比HTTP代理简单多了,也是通过Socket服务端,使用curl -x 127.0.0.1:8888 https://www.baidu.com 控制台可以拿到下面HTTPS的代理报文。

    CONNECT www.baidu.com:443 HTTP/1.1
    Host: www.baidu.com:443
    User-Agent: curl/8.4.0
    Proxy-Connection: Keep-Alive

    我简单画一下大致流程,浏览器通过代理把上面CONNECT报文发送Java应用,Java应用拿到报文,从报文中获取到域名和端口,通过域名和端口连接百度服务器,连接成功后,回应浏览器HTTP/1.1 200 Connection estabished告诉他这边已经连接好了,可以发送数据了,最后浏览器和百度服务器双方交换数据。

    浏览器Java应用百度服务器CONNECT报文连接HTTP/1.1 200 Connection estabished发送数据写入数据浏览器Java应用百度服务器

    逻辑和HTTP一样的,只是他不用把CONNECT报文发送到百度服务器上,就不用修改报文,而且结束只用通过rnrn判断,和百度服务器连接上就回应HTTP/1.1 200 Connection estabished,简单多了。

    以上就是使用Java实现HTTP和HTTPS代理服务详解的详细内容,更多关于Java实现HTTP和HTTPS代理的资料请关注风君子博客其它相关文章!

    您可能感兴趣的文章:

    • java实现轻量型http代理服务器示例
    • java实现http的Post、Get、代理访问请求
    • java httpclient设置超时时间和代理的方法
    • JAVA HTTP反向代理实现过程详解
    • 详解Java是如何通过接口来创建代理并进行http请求
    • java http请求设置代理Proxy的两种常见方法

    站内搜索