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; # 指定源IPproxy_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();//只有当客户端关闭它的输出流的时候,服务端才能取得结尾的-1while ((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();}}
相关文章:

socket实现HTTP请求,参考HttpURLConnection源码解析
背景 有台服务器,网卡绑定有2个ip地址,分别为: A:192.168.111.201 B:192.168.111.202 在这台服务器请求目标地址 C:192.168.111.203 时必须使用B作为源地址才能访问目标地址C,在这台服务器默认…...

访问CMOS RAM
实验内容、程序清单及运行结果 访问CMOS RAM(课本实验14) 代码如下: assume cs:code data segment time db yy/mm/dd hh:mm:ss$ ;int 21h 显示字符串,要求以$结尾 table db 9,8,7,4,2,0 ;各时间量的存放单元 data ends cod…...

解决AnyConnect开机自启动问题
文章目录 一、问题描述二、解决方案 (Windows)1.开启-设置2.点击“应用”3.点击“启动”,选择“关” 三、参考文章 一、问题描述 学校指定的VPN总是开机自启动,然而 设置-Preferences 中却没有取消开机自启的选项。 似乎开机自启是必然的,我…...

芯片AI深度实战:进阶篇之vim内verilog实时自定义检视
【痛点】 传统Verilog开发中,工程师不断"编码→仿真→查错"的循环。本文整合AST解析与Vim编辑器,在编码阶段即实现: ✔️ 自动标记逻辑问题 ✔️ AI+ 发现涉及多模块逻辑错误 ✔️ 强制代码风格 【解决方案】 1️⃣ 基于AST的精准模式匹配 - 深度集成…...

数据结构实战之线性表(一)
一.线性表的定义和特点 线性表的定义 线性表是一种数据结构,它包含了一系列具有相同特性的数据元素,数据元素之间存在着顺序关系。例如,26个英文字母的字符表 ( (A, B, C, ....., Z) ) 就是一个线性表,其中每个字母就是一个数据…...

jdk8项目升级到jdk17——岁月云实战
由于很早之前就升级springboot版本到2.7.9,以前做好了铺垫,相对升级要容易一些。 1 项目打包成exe 1.1 jpackage打包jar C:\Users\39305\Desktop\数量核对>jpackage ^ More? --type exe ^ More? --name zp-server ^ More? --input C:\Use…...

商品列表及商品详情展示
前言 本文将展示一段结合 HTML、CSS 和 JavaScript 的代码,实现了一个简单的商品展示页面及商品详情,涵盖数据获取、渲染、搜索及排序等功能。 效果展示 点击不同的商品会展示对应的商品详情。 代码部分 代码总体实现 <!DOCTYPE html> <htm…...

使用where子句筛选记录
默认情况下,SearchCursor将返回一个表或要素类的所有行.然而在很多情况下,常常需要某些条件来限制返回行数. 操作方法: 1.打开IDLE,加载先前编写的SearchCursor.py脚本 2.添加where子句,更新SearchCursor()函数,查找记录中有<>文本的<>字段 with arcpy.da.Searc…...

SQL Server查询计划操作符(7.3)——查询计划相关操作符(5)
7.3. 查询计划相关操作符 38)Flow Distinct:该操作符扫描其输入并对其去重。该操作符从其输入得到每行数据时即将其返回(除非其为重复数据行,此时,该数据行会被抛弃),而Distinct操作符在产生任何输出前将消费所有输入。该操作符为逻辑操作符。该操作符具体如图7.2-38中…...

C++中常用的十大排序方法之4——希尔排序
成长路上不孤单😊😊😊😊😊😊 【😊///计算机爱好者😊///持续分享所学😊///如有需要欢迎收藏转发///😊】 今日分享关于C中常用的排序方法之4——希尔排序的相…...

扶摇计划--从失业的寒冬,慢慢的走出来
作为资深 Java 开发工程师,你有丰富的技术经验和解决问题的能力,即使暂时失业,也可以通过多种方式赚取收入。以下是结合你的技能和市场需求的具体建议,分阶段规划实现: 第一阶段:快速变现(短期,1-3个月) 自由职业与远程工作 平台接单:在 Upwork、Freelancer 或国内平…...

unity学习24:场景scene相关生成,加载,卸载,加载进度,异步加载场景等
目录 1 场景数量 SceneManager.sceneCount 2 直接代码生成新场景 SceneManager.CreateScene 3 场景的加载 3.1 用代码加载场景,仍然build setting里先加入配置 3.2 卸载场景 SceneManager.UnloadSceneAsync(); 3.3 同步加载场景 SceneManager.LoadScene 3.3.…...

[cg] 使用snapgragon 对UE5.3抓帧
最近想要抓opengl 的api,renderdoc在起应用时会闪退(具体原因还不知道),试了下snapgraon, 还是可以的 官网需要注册登录后下载,官网路径:Developer | Qualcomm 为了方便贴上已经下载好的exe安装包&#x…...

一元函数微积分的几何应用:二维平面光滑曲线的曲率公式
文章目录 前言曲率和曲率半径的定义曲率计算公式参数方程形式直角坐标显式方程形式极坐标形式向量形式 前言 本文将介绍二维平面光滑曲线的曲率定义以及不同形式的曲率及曲率半径公式的推导。 曲率和曲率半径的定义 (关于二维平面光滑曲线的定义以及弧长公式请参…...

ISBN 号码——蓝桥杯
1.题目描述 每一本正式出版的图书都有一个 ISBN 号码与之对应,ISBN 码包括 9 位数字、1 位识别码和 3 位分隔符,其规定格式如 “x-xxx-xxxxx-x”,其中符号“-”是分隔符(键盘上的减号),最后一位是识别码&a…...

Spring Boot - 数据库集成06 - 集成ElasticSearch
Spring boot 集成 ElasticSearch 文章目录 Spring boot 集成 ElasticSearch一:前置工作1:项目搭建和依赖导入2:客户端连接相关构建3:实体类相关注解配置说明 二:客户端client相关操作说明1:检索流程1.1&…...

51单片机CLD1602显示万年历+闹钟+农历+整点报时
1. 硬件设计 硬件是我自己设计的一个通用的51单片机开发平台,可以根据需要自行焊接模块,这是用立创EDA画的一个双层PCB板,所以模块都是插针式,不是表贴的。电路原理图在文末的链接里,PCB图暂时不选择开源。 B站上传的…...

C++ 中的类(class)和对象(object)
在 C 中,类(class)和对象(object)是面向对象编程(OOP)的核心概念。类是一种用户自定义的数据类型,它将数据(成员变量)和操作这些数据的函数(成员函…...

安卓通过网络获取位置的方法
一 方法介绍 1. 基本权限设置 首先需要在 AndroidManifest.xml 中添加必要权限: xml <uses-permission android:name"android.permission.INTERNET" /> <uses-permission android:name"android.permission.ACCESS_NETWORK_STATE" /&g…...

2025 年,链上固定收益领域迈向新时代
“基于期限的债券市场崛起与 Secured Finance 的坚定承诺” 2025年,传统资产——尤其是股票和债券——大规模涌入区块链的浪潮将创造历史。BlackRock 首席执行官 Larry Fink 近期在彭博直播中表示,代币化股票和债券将逐步融入链上生态,将进一…...

npm启动前端项目时报错(vue) error:0308010C:digital envelope routines::unsupported
vue 启动项目时,npm run serve 报下面的错: error:0308010C:digital envelope routines::unsupported at new Hash (node:internal/crypto/hash:67:19) at Object.createHash (node:crypto:133:10) at FSReqCallback.readFileAfterClose [as on…...

11.QT控件:输入类控件
1. Line Edit(单行输入框) QLineEdit表示单行输入框,用来输入一段文本,但是不能换行。 核心属性: 核心信号: 2. Text Edit(多行输入框) QTextEdit表示多行输入框,也是一个富文本 & markdown编辑器。并且能在内容超…...

deepseek核心技术:MLA架构-多头潜在注意力
deepseek核心技术:MLA架构-多头潜在注意力 MLA架构即Multi-Head Latent Attention(多头潜在注意力)架构,是一种优化后的注意力机制。以下是对其及相关示例的具体介绍: 工作原理 输入嵌入:将输入序列中的每个元素转换为向量表示,即嵌入向量。例如在处理文本时,将文本中…...

讯飞星火大模型API使用Python调用
本文仅仅为简单API调用,更多复杂使用方法请参见接口文档 先在科大讯飞开放平台注册账号,点击控制台,在我的应用中创建新应用,新应用的名称可以自定义,这里我写的是ai对话: 在这里我们使用的模型为Speak Ul…...

C#面试常考随笔7:什么是匿名⽅法?还有Lambda表达式?
匿名方法本质上是一种没有显式名称的方法,它可以作为参数传递给需要委托类型的方法,常用于事件处理、回调函数等场景,能够让代码更加简洁和紧凑。 使用场景 事件处理:在处理事件时,不需要为每个事件处理程序单独定义…...

Elasticsearch:如何搜索含有复合词的语言
作者:来自 Elastic Peter Straer 复合词在文本分析和标记过程中给搜索引擎带来挑战,因为它们会掩盖词语成分之间的有意义的联系。连字分解器标记过滤器等工具可以通过解构复合词来帮助解决这些问题。 德语以其长复合词而闻名:Rindfleischetik…...

【Numpy核心编程攻略:Python数据处理、分析详解与科学计算】1.25 视觉风暴:NumPy驱动数据可视化
1.25 视觉风暴:NumPy驱动数据可视化 目录 #mermaid-svg-i3nKPm64ZuQ9UcNI {font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}#mermaid-svg-i3nKPm64ZuQ9UcNI .error-icon{fill:#552222;}#mermaid-svg-i3nKPm64ZuQ9UcNI …...

idea maven本地有jar包,但还要从远程下载
idea 中,java 工程执行 maven reimport,报jar报无法下载。 我奇了个怪,我明明在本地仓库有啊,你非得从远程下载? 我从供应商那里拿来的,远程当然没有了。 这太奇葩了吧,折腾好久不行。 后来…...

C++编程语言:抽象机制:模板(Bjarne Stroustrup)
目录 23.1 引言和概观(Introduction and Overview) 23.2 一个简单的字符串模板(A Simple String Template) 23.2.1 模板的定义(Defining a Template) 23.2.2 模板实例化(Template Instantiation) 23.3 类型检查(Type Checking) 23.3.1 类型等价(Type Equivalence) …...

深入解析 Linux 内核中的页面错误处理机制
在现代操作系统中,页面错误(Page Fault)是内存管理的重要组成部分。当程序试图访问未映射到物理内存的虚拟内存地址时,CPU 会触发页面错误异常。Linux 内核通过一系列复杂的机制来处理这些异常,确保系统的稳定性和性能。本文将深入解析 Linux 内核中处理页面错误的核心代码…...