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

news2025/2/5 5:15:00

背景

有台服务器,网卡绑定有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();
    }


}

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

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

3、C#基于.net framework的应用开发实战编程 - 实现(三、三) - 编程手把手系列文章...

三、 实现&#xff1b; 三&#xff0e;三、编写应用程序&#xff1b; 此文主要是实现应用的主要编码工作。 1、 分层&#xff1b; 此例子主要分为UI、Helper、DAL等层。UI负责便签的界面显示&#xff1b;Helper主要是链接UI和数据库操作的中间层&#xff1b;DAL为对数据库的操…

Java BIO详解

一、简介 1.1 BIO概述 BIO&#xff08;Blocking I/O&#xff09;&#xff0c;即同步阻塞IO&#xff08;传统IO&#xff09;。 BIO 全称是 Blocking IO&#xff0c;同步阻塞式IO&#xff0c;是JDK1.4之前的传统IO模型&#xff0c;就是传统的 java.io 包下面的代码实现。 服务…

Haproxy+keepalived高可用集群,haproxy宕机的解决方案

Haproxykeepalived高可用集群&#xff0c;允许keepalived宕机&#xff0c;允许后端真实服务器宕机&#xff0c;但是不允许haproxy宕机&#xff0c; 所以下面就是解决方案 keepalived配置高可用检测脚本 &#xff0c;master和backup都要添加 配置脚本 # vim /etc/keepalived…

98,【6】 buuctf web [ISITDTU 2019]EasyPHP

进入靶场 代码 <?php // 高亮显示当前 PHP 文件的源代码&#xff0c;通常用于调试或展示代码&#xff0c;方便用户查看代码逻辑 highlight_file(__FILE__);// 从 GET 请求中获取名为 _ 的参数值&#xff0c;并赋值给变量 $_ // 符号用于抑制可能出现的错误信息&#xff…

九. Redis 持久化-RDB(详细讲解说明,一个配置一个说明分析,步步讲解到位)

九. Redis 持久化-RDB(详细讲解说明&#xff0c;一个配置一个说明分析&#xff0c;步步讲解到位) 文章目录 九. Redis 持久化-RDB(详细讲解说明&#xff0c;一个配置一个说明分析&#xff0c;步步讲解到位)1. RDB 概述2. RDB 持久化执行流程3. RDB 的详细配置4. RDB 备份&恢…

小程序越来越智能化,作为设计师要如何进行创新设计

一、用户体验至上 &#xff08;一&#xff09;简洁高效的界面设计 小程序的特点之一是轻便快捷&#xff0c;用户期望能够在最短的时间内找到所需功能并完成操作。因此&#xff0c;设计师应致力于打造简洁高效的界面。避免过多的装饰元素和复杂的布局&#xff0c;采用清晰的导航…

(done) MIT6.S081 2023 学习笔记 (Day7: LAB6 Multithreading)

网页&#xff1a;https://pdos.csail.mit.edu/6.S081/2023/labs/thread.html (任务1教会了你如何用 C 语言调用汇编&#xff0c;编译后链接即可) 任务1&#xff1a;Uthread: switching between threads (完成) 在这个练习中&#xff0c;你将设计一个用户级线程系统中的上下文切…

JVM执行流程与架构(对应不同版本JDK)

直接上图&#xff08;对应JDK8以及以后的HotSpot&#xff09; 这里主要区分说明一下 方法区于 字符串常量池 的位置更迭&#xff1a; 方法区 JDK7 以及之前的版本将方法区存放在堆区域中的 永久代空间&#xff0c;堆的大小由虚拟机参数来控制。 JDK8 以及之后的版本将方法…

基于开源AI智能名片2 + 1链动模式S2B2C商城小程序视角下的个人IP人设构建研究

摘要&#xff1a;本文深入探讨在开源AI智能名片2 1链动模式S2B2C商城小程序的应用场景下&#xff0c;个人IP人设构建的理论与实践。通过剖析个人IP人设定义中的“诉求”“特质”“可感知”三要素&#xff0c;结合该小程序特点&#xff0c;阐述其对个人IP打造的影响与推动作用&…

刷题汇总一览

文章目录 贪心动态规划数据结构滑动窗口与双指针前缀和动态规划 本题单设计力扣、牛客等多个刷题网站 贪心 贪心后悔 徒步旅行中的补给问题 LCP 30.魔塔游戏 题目使用到的思想解题分析徒步旅行中的补给问题每次我们都加入当前补给点的k个选择&#xff0c;同时进行升序排序&am…

Jupyter Lab的使用

Lab与Notebook的区别: Jupyter Lab和Jupyter notebook有什么区别&#xff0c;这里找到一篇博客不过我没细看&#xff0c; Jupyter Lab和Jupyter Notebook的区别 - codersgl - 博客园 使用起来Lab就是一个更齐全、功能更高级的notebook&#xff0c; 启用滚动输出: 有时候一个…

SpringBoot中关于knife4j 中的一些相关注解

1、效果图 对比可以明显的看到加了注解与没有加注解所表现出来的效果不同&#xff08;加了注解的更加明了清晰&#xff09; 2、实现效果 Tag注解‌用于为测试方法或测试类添加标签&#xff0c;以便在执行测试时根据标签进行过滤。使用Tag注解可以更灵活地控制测试的执行&#…

知识管理系统助力企业信息共享与创新思维的全面提升研究

内容概要 知识管理系统的引入极大地改变了企业内部的信息流程与创新机制。通过有效整合与管理组织内的知识资源&#xff0c;这些系统不仅降低了信息孤岛的现象&#xff0c;还提升了员工之间的协作能力。企业在信息共享方面&#xff0c;通过知识管理系统构建了一个透明、高效的…

LLM - 基于LM Studio本地部署DeepSeek-R1的蒸馏量化模型

文章目录 前言开发环境快速开始LM Studio简单设置模型下载开始对话 模型选择常见错误最后 前言 目前&#xff0c;受限于设备性能&#xff0c;在本地部署的基本都是DeepSeek-R1的蒸馏量化模型&#xff0c;这些蒸馏量化模型的表现可能并没有你想象的那么好。绝大部分人并不需要本…

本地部署 DeepSeek-R1:简单易上手,AI 随时可用!

&#x1f3af; 先看看本地部署的运行效果 为了测试本地部署的 DeepSeek-R1 是否真的够强&#xff0c;我们随便问了一道经典的“鸡兔同笼”问题&#xff0c;考察它的推理能力。 &#x1f4cc; 问题示例&#xff1a; 笼子里有鸡和兔&#xff0c;总共有 35 只头&#xff0c;94 只…

对象的实例化、内存布局与访问定位

一、创建对象的方式 二、创建对象的步骤: 一、判断对象对应的类是否加载、链接、初始化: 虚拟机遇到一条new指令&#xff0c;首先去检查这个指令的参数能否在Metaspace的常量池中定位到一个类的符号引用&#xff0c;并且检查这个符号引用代表的类是否已经被加载、解析和初始化…

OpenAI推出Deep Research带给我们怎样的启示

OpenAI 又发新产品了&#xff0c;这次是面向深度研究领域的智能体产品 ——「Deep Research」&#xff0c;貌似被逼无奈的节奏… 在技术方面&#xff0c;Deep Research搭载了优化后o3模型并通过端到端强化学习在多个领域的复杂浏览和推理任务上进行了训练。因没有更多的技术暴露…

K8S学习笔记-------1.安装部署K8S集群环境

1.修改为root权限 #sudo su 2.修改主机名 #hostnamectl set-hostname k8s-master01 3.查看网络地址 sudo nano /etc/netplan/01-netcfg.yaml4.使网络配置修改生效 sudo netplan apply5.修改UUID&#xff08;某些虚拟机系统&#xff0c;需要设置才能生成UUID&#xff09;#…

【基于SprintBoot+Mybatis+Mysql】电脑商城项目之用户登录

&#x1f9f8;安清h&#xff1a;个人主页 &#x1f3a5;个人专栏&#xff1a;【Spring篇】【计算机网络】【Mybatis篇】 &#x1f6a6;作者简介&#xff1a;一个有趣爱睡觉的intp&#xff0c;期待和更多人分享自己所学知识的真诚大学生。 目录 &#x1f3af;1.登录-持久层 &…

【Deep Seek本地化部署】模型实测:规划求解python代码

目录 前言 一、实测 1、整数规划问题 2、非线性规划问题 二、代码正确性验证 1、整数规划问题代码验证 2、非线性规划问题代码验证 三、结果正确性验证 1、整数规划问题结果正确性验证 2、非线性规划问题正确性验证 四、整数规划问题示例 后记 前言 模型&#xff…