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 近期在彭博直播中表示,代币化股票和债券将逐步融入链上生态,将进一…...

深度学习在微纳光子学中的应用
深度学习在微纳光子学中的主要应用方向 深度学习与微纳光子学的结合主要集中在以下几个方向: 逆向设计 通过神经网络快速预测微纳结构的光学响应,替代传统耗时的数值模拟方法。例如设计超表面、光子晶体等结构。 特征提取与优化 从复杂的光学数据中自…...
React 第五十五节 Router 中 useAsyncError的使用详解
前言 useAsyncError 是 React Router v6.4 引入的一个钩子,用于处理异步操作(如数据加载)中的错误。下面我将详细解释其用途并提供代码示例。 一、useAsyncError 用途 处理异步错误:捕获在 loader 或 action 中发生的异步错误替…...
树莓派超全系列教程文档--(62)使用rpicam-app通过网络流式传输视频
使用rpicam-app通过网络流式传输视频 使用 rpicam-app 通过网络流式传输视频UDPTCPRTSPlibavGStreamerRTPlibcamerasrc GStreamer 元素 文章来源: http://raspberry.dns8844.cn/documentation 原文网址 使用 rpicam-app 通过网络流式传输视频 本节介绍来自 rpica…...

Cilium动手实验室: 精通之旅---20.Isovalent Enterprise for Cilium: Zero Trust Visibility
Cilium动手实验室: 精通之旅---20.Isovalent Enterprise for Cilium: Zero Trust Visibility 1. 实验室环境1.1 实验室环境1.2 小测试 2. The Endor System2.1 部署应用2.2 检查现有策略 3. Cilium 策略实体3.1 创建 allow-all 网络策略3.2 在 Hubble CLI 中验证网络策略源3.3 …...
基础测试工具使用经验
背景 vtune,perf, nsight system等基础测试工具,都是用过的,但是没有记录,都逐渐忘了。所以写这篇博客总结记录一下,只要以后发现新的用法,就记得来编辑补充一下 perf 比较基础的用法: 先改这…...

高危文件识别的常用算法:原理、应用与企业场景
高危文件识别的常用算法:原理、应用与企业场景 高危文件识别旨在检测可能导致安全威胁的文件,如包含恶意代码、敏感数据或欺诈内容的文档,在企业协同办公环境中(如Teams、Google Workspace)尤为重要。结合大模型技术&…...
【git】把本地更改提交远程新分支feature_g
创建并切换新分支 git checkout -b feature_g 添加并提交更改 git add . git commit -m “实现图片上传功能” 推送到远程 git push -u origin feature_g...

c#开发AI模型对话
AI模型 前面已经介绍了一般AI模型本地部署,直接调用现成的模型数据。这里主要讲述讲接口集成到我们自己的程序中使用方式。 微软提供了ML.NET来开发和使用AI模型,但是目前国内可能使用不多,至少实践例子很少看见。开发训练模型就不介绍了&am…...

vue3+vite项目中使用.env文件环境变量方法
vue3vite项目中使用.env文件环境变量方法 .env文件作用命名规则常用的配置项示例使用方法注意事项在vite.config.js文件中读取环境变量方法 .env文件作用 .env 文件用于定义环境变量,这些变量可以在项目中通过 import.meta.env 进行访问。Vite 会自动加载这些环境变…...
在web-view 加载的本地及远程HTML中调用uniapp的API及网页和vue页面是如何通讯的?
uni-app 中 Web-view 与 Vue 页面的通讯机制详解 一、Web-view 简介 Web-view 是 uni-app 提供的一个重要组件,用于在原生应用中加载 HTML 页面: 支持加载本地 HTML 文件支持加载远程 HTML 页面实现 Web 与原生的双向通讯可用于嵌入第三方网页或 H5 应…...