socket实现HTTP请求,参考HttpURLConnection源码解析

背景

有台服务器,网卡绑定有2个ip地址,分别为:
A:192.168.111.201
B:192.168.111.202
在这台服务器请求目标地址
C:192.168.111.203
时必须使用B作为源地址才能访问目标地址C,在这台服务器默认又是使用A地址作为源地址。

1、curl解决办法

#指定源ip
curl -X POST -H "Content-Type:application/json"  --interface 192.168.111.202 http://192.168.111.203:8080/v1 -d '{"model":"x"}'

2、使用nginx解决办法 

        #转发接口
        location ^~ /v1 {
            root html;
            limit_rate 2048k;
            proxy_set_header Host $http_host;
            proxy_set_header X-Real-IP $remote_addr;
            proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
            client_max_body_size 100m;
            client_body_buffer_size 128m;
            proxy_connect_timeout 120s;
            proxy_send_timeout 120s;
            proxy_read_timeout 120s;
            proxy_bind 192.168.111.202;  # 指定源IP
            proxy_pass http://192.168.111.203:8080;
        }

3、使用socket实现HTTP请求

由于原生HttpURLConnection不支持设置源ip地址,而socket支持设置源ip地址,所以使用socket实现http请求就可以了。

HttpURLConnection 示例 

 /**
     * 发送POST请求
     * @param url         请求地址
     * @param params      请求参数
     * @param contentType ContentType请求头类型
     * @param timeout     读超时,单位:秒
     * @author lhs
     * @date 2024/12/2 15:35
     */
    public static String sendPost(String url, String params, String contentType, Integer timeout) {
        InputStream inputStream = null;
        OutputStream outputStream = null;
        HttpURLConnection connection = null;
        int responseCode = 0;
        try {
            connection = (HttpURLConnection) new URL(url).openConnection();
            connection.setRequestMethod("POST");
            connection.setDoInput(true);
            connection.setDoOutput(true);
            connection.setUseCaches(false);
            connection.setConnectTimeout(10000);// 连接超时(单位:毫秒)
            if (timeout == null || timeout == 0) {
                connection.setReadTimeout(15000);// 读超时(单位:毫秒)
            } else {
                connection.setReadTimeout(timeout * 1000);// 读超时(单位:毫秒)
            }
            if (contentType == null || contentType.length() == 0) {
                connection.setRequestProperty("Content-Type", APPLICATION_FORM_URLENCODED);
            } else {
                connection.setRequestProperty("Content-Type", contentType);
            }
            if (params != null && params.length() > 0) {
                outputStream = connection.getOutputStream();
                outputStream.write(params.getBytes(StandardCharsets.UTF_8));
                outputStream.flush();
            }
            int len;
            byte[] buf = new byte[4096];
            responseCode = connection.getResponseCode();
            inputStream = connection.getInputStream();
            ByteArrayOutputStream baos = new ByteArrayOutputStream();
            while ((len = inputStream.read(buf)) != -1) {
                baos.write(buf, 0, len);
                baos.flush();
            }
            String result = baos.toString("UTF-8");
            baos.close();
            return result;
        } catch (Exception e) {
            String cause = e.getCause() == null ? "" : e.getCause().getMessage();
            return "Exception:" + responseCode + ":" + cause + e.getMessage();
        } finally {
            try {
                if (inputStream != null) {
                    inputStream.close();
                }
                if (outputStream != null) {
                    outputStream.close();
                }
                if (connection != null) {
                    connection.disconnect();
                }
            } catch (IOException e) {
                log.error(e.getMessage(), e);
            }
        }
    }

HttpURLConnection源码分析过程

入口:connection.getInputStream()
 

情况一:

当不能预先确定报文体的长度时,不可能在头中包含Content-Length域来指明报文体长度,此时就需要通过Transfer-Encoding域来确定报文体长度。
 

情况二: 

响应头有 Content-Length

 

 

 socket实现HTTP请求

socket实现http请求很简单,抓包看下报文就知道了,比较麻烦的是解析响应报文。
根据分析HttpURLConnection 源码可以看出响应报文解析需要区分响应头有Transfer-Encoding和响应头有 Content-Length 两种情况。

若需要指定源IP,打开“指定源IP方式”后面的注释代码,注释“不需要指定源IP方式”后面两行代码。

package com.study;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import sun.net.www.MeteredStream;
import sun.net.www.http.ChunkedInputStream;

import java.io.*;
import java.net.InetSocketAddress;
import java.net.Socket;
import java.net.URL;
import java.nio.charset.StandardCharsets;
import java.util.HashMap;
import java.util.Map;

public class HttpClientUtil {
    private static final Logger log = LoggerFactory.getLogger(HttpClientUtil.class);
    /*ContentType请求头类型*/
    public final static String APPLICATION_FORM_URLENCODED = "application/x-www-form-urlencoded;charset=utf-8";
    public final static String APPLICATION_JSON = "application/json;charset=utf-8";
    public final static String APPLICATION_SOAP_XML = "application/soap+xml;charset=utf-8";
    public final static String MULTIPART_FORM_DATA = "multipart/form-data;charset=utf-8";
    public final static String APPLICATION_XML = "application/xml;charset=utf-8";
    public final static String TEXT_HTML = "text/html;charset=utf-8";
    public final static String TEXT_XML = "text/xml;charset=utf-8";

    public static void main(String[] args) throws Exception {
        String url = "http://www.7timer.info/bin/astro.php";
        String params = "lon=104.06&lat=30.65&ac=0&lang=en&unit=metric&output=json&tzshift=0";
        String result = sendPost(url, params, HttpClientUtil.APPLICATION_FORM_URLENCODED, 20);
        log.info("响应报文:" + result);
    }

    /**
     * 发送POST请求
     * @param url         请求地址
     * @param params      请求参数
     * @param contentType ContentType请求头类型
     * @param soTimeout   读超时,单位:秒
     * @author lhs
     * @date 2024/12/2 15:35
     */
    public static String sendPost(String url, String params, String contentType, Integer soTimeout) throws Exception {
        URL u = new URL(url);
        String path = u.getFile();
        if (path != null && !path.isEmpty()) {
            if (path.charAt(0) == '?') {
                path = "/" + path;
            }
        } else {
            path = "/";
        }
        // 要连接的服务端IP地址和端口
        int port = u.getPort();
        String host = u.getHost();
        String authority = host;
        if (port != -1 && port != u.getDefaultPort()) {
            authority = host + ":" + port;
        }
        if (port == -1) {
            port = u.getDefaultPort();
        }
        // 设置连接超时时间
        int connectTimeout = 10 * 1000;

        // 不需要指定源IP方式
        Socket socket = new Socket();
        socket.connect(new InetSocketAddress(host, port), connectTimeout);

        // 指定源IP方式
        // SocketAddress localAddress = new InetSocketAddress("192.168.111.202", 0);// 0表示让系统自动选择一个端口
        // socket.bind(localAddress); // 绑定本地 IP 地址和端口
        // SocketAddress remoteAddress = new InetSocketAddress(host, port);
        // socket.connect(remoteAddress, connectTimeout); // 连接到远程服务器

        OutputStream outputStream = socket.getOutputStream();
        PrintStream serverOutput = new PrintStream(new BufferedOutputStream(outputStream), false, "UTF-8");
        socket.setTcpNoDelay(true);
        socket.setSoTimeout(soTimeout * 1000);

        // 请求参数body部分
        byte[] body = params.getBytes(StandardCharsets.UTF_8);
        // // 请求参数header部分
        String header = getHttpHeader(path, authority, contentType, body.length);
        log.info("请求报文:" + header + params);
        serverOutput.print(header);//请求参数header部分
        serverOutput.flush();
        serverOutput.write(body);//请求参数body部分
        serverOutput.flush();

        InputStream inputStream = new BufferedInputStream(socket.getInputStream());

        int len = 0;
        byte[] buf = new byte[8];
        // readlimit被设置为10,意味着从标记位置开始,你可以读取最多10个字节的数据,然后仍然可以通过调用reset()方法回到这个标记位置。
        inputStream.mark(10);
        while (len < 8) {
            int read = inputStream.read(buf, len, 8 - len);
            if (read < 0) {
                break;
            }
            len += read;
        }
        String scheme = new String(buf, StandardCharsets.UTF_8);
        inputStream.reset();
        if ("HTTP/1.1".equals(scheme)) {
            Map<String, String> headerMap = parseHeader(inputStream);
            try {
                //第一行响应内容
                String firstLineHeader = headerMap.get(null);
                int index;
                for (index = firstLineHeader.indexOf(32); firstLineHeader.charAt(index) == ' '; ++index) {
                }
                //响应码
                int responseCode = Integer.parseInt(firstLineHeader.substring(index, index + 3));
                log.info("响应码:" + responseCode);

                // 当不能预先确定报文体的长度时,不可能在头中包含Content-Length域来指明报文体长度,此时就需要通过Transfer-Encoding域来确定报文体长度。
                String transferEncoding = headerMap.get("Transfer-Encoding");
                if ("chunked".equalsIgnoreCase(transferEncoding)) {
                    inputStream = new ChunkedInputStream(inputStream, sun.net.www.http.HttpClient.New(u), null);
                }

                //响应body长度
                String contentLength = headerMap.get("Content-Length");
                if (contentLength != null) {
                    long bodyLength = Long.parseLong(contentLength);
                    inputStream = new MeteredStream(inputStream, null, bodyLength);
                }

                buf = new byte[4096];
                ByteArrayOutputStream baos = new ByteArrayOutputStream();
                //只有当客户端关闭它的输出流的时候,服务端才能取得结尾的-1
                while ((len = inputStream.read(buf)) != -1) {
                    baos.write(buf, 0, len);
                }
                String result = baos.toString("UTF-8");
                return result;
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
        return null;
    }

    /**
     * 该方法参考:sun.net.www.MessageHeader#mergeHeader(java.io.InputStream)源码
     * @author lhs
     * @date 2025/1/11 10:53
     */
    private static Map<String, String> parseHeader(InputStream var1) throws IOException {
        Map<String, String> headerMap = new HashMap<>();
        if (var1 != null) {
            char[] var2 = new char[10];

            String var9;
            String var10;
            for (int var3 = var1.read(); var3 != 10 && var3 != 13 && var3 >= 0; headerMap.put(var10, var9)) {
                int var4 = 0;
                int var5 = -1;
                boolean var7 = var3 > 32;
                var2[var4++] = (char) var3;

                label104:
                while (true) {
                    int var6;
                    if ((var6 = var1.read()) < 0) {
                        var3 = -1;
                        break;
                    }

                    switch (var6) {
                        case 9:
                            var6 = 32;
                        case 32:
                            var7 = false;
                            break;
                        case 10:
                        case 13:
                            var3 = var1.read();
                            if (var6 == 13 && var3 == 10) {
                                var3 = var1.read();
                                if (var3 == 13) {
                                    var3 = var1.read();
                                }
                            }

                            if (var3 == 10 || var3 == 13 || var3 > 32) {
                                break label104;
                            }

                            var6 = 32;
                            break;
                        case 58:
                            if (var7 && var4 > 0) {
                                var5 = var4;
                            }

                            var7 = false;
                    }

                    if (var4 >= var2.length) {
                        char[] var8 = new char[var2.length * 2];
                        System.arraycopy(var2, 0, var8, 0, var4);
                        var2 = var8;
                    }

                    var2[var4++] = (char) var6;
                }

                while (var4 > 0 && var2[var4 - 1] <= ' ') {
                    --var4;
                }

                if (var5 <= 0) {
                    var10 = null;
                    var5 = 0;
                } else {
                    var10 = String.copyValueOf(var2, 0, var5);
                    if (var5 < var4 && var2[var5] == ':') {
                        ++var5;
                    }

                    while (var5 < var4 && var2[var5] <= ' ') {
                        ++var5;
                    }
                }

                if (var5 >= var4) {
                    var9 = new String();
                } else {
                    var9 = String.copyValueOf(var2, var5, var4 - var5);
                }
            }

        }
        return headerMap;
    }

    /**
     * 拼接http请求头报文
     * @author lhs
     * @date 2023/3/31 17:47
     */
    private static String getHttpHeader(String path, String authority, String contentType, int length) throws Exception {
        StringBuilder header = new StringBuilder();
        header.append("POST " + path + " HTTP/1.1\r\n");
        // header.append("Content-Type: application/json;charset=UTF-8\r\n");
        header.append("Content-Type: " + contentType + "\r\n");
        header.append("Host: " + authority + "\r\n");
        header.append("Content-Length: " + length + "\r\n");
        header.append("\r\n");
        return header.toString();
    }


}

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/962697.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

漏洞扫描工具之xray

下载地址&#xff1a;https://github.com/chaitin/xray/releases 1.9.11 使用文档&#xff1a;https://docs.xray.cool/tools/xray/Scanning 与burpsuite联动&#xff1a; https://xz.aliyun.com/news/7563 参考&#xff1a;https://blog.csdn.net/lza20001103/article/details…

正月初三特殊的一天

在我们河南豫东地区&#xff0c;初三这一天一般情况下可以在家休息&#xff0c;不需要串门走亲戚&#xff0c;给亲戚的长辈或比自己辈份长的拜年。 特殊的正月初三 还有两种情况&#xff0c;正月初三这一天必须去走亲戚。一种是有去世的亲戚没有过三周年&#xff0c;正月初三这…

强化学习笔记——4策略迭代、值迭代、TD算法

基于策略迭代的贝尔曼方程和基于值迭代的贝尔曼方程&#xff0c;关系还是不太理解 首先梳理一下&#xff1a; 通过贝尔曼方程将强化学习转化为值迭代和策略迭代两种问题 求解上述两种贝尔曼方程有三种方法&#xff1a;DP&#xff08;有模型&#xff09;&#xff0c;MC&#xff…

HTTP协议和静态web服务器

一、HTTP协议 1 HTTP协议的定义 网络协议 网络协议是指计算机通信网络中两台计算机之间进行通信所必须共同遵守的规定或规则。HTTP协议 HTTP协议(超文本传输协议)是一种网络通信协议,它允许将超文本标记语言(HTML)文档从Web服务器传送到客户端的浏览器。默认端口:80HTTPS协…

智能汽车网络安全威胁报告

近年来随着智能汽车技术的快速发展&#xff0c;针对智能汽车的攻击也逐渐从传统的针对单一车辆控制器的攻击转变为针对整车智能化服务的攻击&#xff0c;包括但不限于对远程控制应用程序的操控、云服务的渗透、智能座舱系统的破解以及对第三方应用和智能服务的攻击。随着WP.29 …

在虚拟机里运行frida-server以实现对虚拟机目标软件的监测和修改参数(一)(android Google Api 35高版本版)

frida-server下载路径 我这里选择较高版本的frida-server-16.6.6-android-x86_64 以root身份启动adb 或 直接在android studio中打开 adb root 如果使用android studio打开的话&#xff0c;最好选择google api的虚拟机&#xff0c;默认以root模式开启 跳转到下载的frida-se…

Node.js——body-parser、防盗链、路由模块化、express-generator应用生成器

个人简介 &#x1f440;个人主页&#xff1a; 前端杂货铺 &#x1f64b;‍♂️学习方向&#xff1a; 主攻前端方向&#xff0c;正逐渐往全干发展 &#x1f4c3;个人状态&#xff1a; 研发工程师&#xff0c;现效力于中国工业软件事业 &#x1f680;人生格言&#xff1a; 积跬步…

【2024年华为OD机试】(C卷,200分)- 启动多任务排序 (JavaScriptJava PythonC/C++)

一、问题描述 题目解析 本题是一个典型的拓扑排序问题。拓扑排序用于解决有向无环图(DAG)中的节点排序问题,使得对于图中的每一条有向边 (u, v),u 在排序中总是位于 v 的前面。在本题中,任务之间的依赖关系可以看作是有向图中的边,而任务的执行顺序就是拓扑排序的结果。…

【NLP251】NLP RNN 系列网络

NLP251 系列主要记录从NLP基础网络结构到知识图谱的学习 &#xff11;.原理及网络结构 &#xff11;.&#xff11;&#xff32;&#xff2e;&#xff2e; 在Yoshua Bengio论文中( http://proceedings.mlr.press/v28/pascanu13.pdf )证明了梯度求导的一部分环节是一个指数模型…

Privacy Eraser,电脑隐私的终极清除者

Privacy Eraser 是一款专为保护用户隐私而设计的全能型软件&#xff0c;它不仅能够深度清理计算机中的各类隐私数据&#xff0c;还提供了多种系统优化工具&#xff0c;帮助用户提升设备的整体性能。通过这款软件&#xff0c;用户可以轻松清除浏览器历史记录、缓存文件、Cookie、…

数据分析常用的AI工具

数据分析领域中常用的AI工具种类繁多&#xff0c;涵盖了从数据处理、分析到可视化和预测的各个环节。以下是一些常见且广泛应用的AI数据分析工具及其特点&#xff1a; 1. 数据处理与清洗工具 Python库&#xff1a;如PandasAI&#xff0c;集成了生成式AI能力&#xff0c;支持自…

npm常见报错整理

npm install时报UNMET PEER DEPENDENCY 现象 npm install时报UNMET PEER DEPENDENCY,且执行npm install好几遍仍报这个。 原因 不是真的缺少某个包,而是安装的依赖版本不对,警告你应该安装某一个版本。 真的缺少某个包。 解决 看了下package.json文件,我的react是有的…

ARM内核:嵌入式时代的核心引擎

引言 在当今智能设备无处不在的时代&#xff0c;ARM&#xff08;Advanced RISC Machines&#xff09;处理器凭借其高性能、低功耗的特性&#xff0c;成为智能手机、物联网设备、汽车电子等领域的核心引擎。作为精简指令集&#xff08;RISC&#xff09;的典范&#xff0c;ARM核…

https数字签名手动验签

以bing.com 为例 1. CA 层级的基本概念 CA 层级是一种树状结构&#xff0c;由多个层级的 CA 组成。每个 CA 负责为其下一层级的实体&#xff08;如子 CA 或终端实体&#xff09;颁发证书。层级结构的顶端是 根 CA&#xff08;Root CA&#xff09;&#xff0c;它是整个 PKI 体…

如何获取当前的位置信息

文章目录 1 概念介绍2 使用方法3 示例代码3 体验分享 我们在上一章回中介绍了如何实现滑动菜单相关的内容&#xff0c;本章回中将介绍如何获取位置信息.闲话休提&#xff0c;让我们一起Talk Flutter吧。 1 概念介绍 我们在这里说的获取位置信息本质上是获取当前手机所在位置的…

c++在线音乐播放器项目开发记录(2)

前言 因为放寒假了&#xff0c;时间比较短&#xff0c;想找实习也不好找&#xff0c;干脆在家加强一下技术栈&#xff0c;首先从c学起&#xff0c;最适合练手的就是qt的项目了&#xff0c;我是根据B站视频【5个C/C硬核简历项目实战&#xff0c;可直接写入简历&#xff0c;包含…

PyQt6医疗多模态大语言模型(MLLM)实用系统框架构建初探(下.代码部分)

医疗 MLLM 框架编程实现 本医疗 MLLM 框架结合 Python 与 PyQt6 构建,旨在实现多模态医疗数据融合分析并提供可视化界面。下面从数据预处理、模型构建与训练、可视化界面开发、模型 - 界面通信与部署这几个关键部分详细介绍编程实现。 6.1 数据预处理 在医疗 MLLM 框架中,多…

【项目初始化】

项目初始化 使用脚手架创建项目Vite创建项目推荐拓展 使用脚手架创建项目 Vite Vite 是一个现代的前端构建工具&#xff0c;它提供了极速的更新和开发体验&#xff0c;支持多种前端框架&#xff0c;如 Vue、React 等创建项目 pnpm create vuelatest推荐拓展

云原生(五十二) | DataGrip软件使用

文章目录 DataGrip软件使用 一、DataGrip基本使用 二、软件界面介绍 三、附件文件夹到项目中 四、DataGrip设置 五、SQL执行快捷键 DataGrip软件使用 一、DataGrip基本使用 1. 软件界面介绍 2. 附加文件夹到项目中【重要】 3. DataGrip配置 快捷键使用&#xff1a;C…

爬虫基础(二)Web网页的基本原理

一、网页的组成 网页由三部分构成&#xff1a;HTML、JavaScript、CSS。 &#xff08;1&#xff09;HTML HTML 相当于网页的骨架&#xff0c;它通过使用标签来定义网页内容的结构。 举个例子&#xff1a; 它把图片标签为img、把视频标签为video&#xff0c;然后组合到一个界面…