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 近期在彭博直播中表示,代币化股票和债券将逐步融入链上生态,将进一…...
KubeSphere 容器平台高可用:环境搭建与可视化操作指南
Linux_k8s篇 欢迎来到Linux的世界,看笔记好好学多敲多打,每个人都是大神! 题目:KubeSphere 容器平台高可用:环境搭建与可视化操作指南 版本号: 1.0,0 作者: 老王要学习 日期: 2025.06.05 适用环境: Ubuntu22 文档说…...
Vue记事本应用实现教程
文章目录 1. 项目介绍2. 开发环境准备3. 设计应用界面4. 创建Vue实例和数据模型5. 实现记事本功能5.1 添加新记事项5.2 删除记事项5.3 清空所有记事 6. 添加样式7. 功能扩展:显示创建时间8. 功能扩展:记事项搜索9. 完整代码10. Vue知识点解析10.1 数据绑…...
TDengine 快速体验(Docker 镜像方式)
简介 TDengine 可以通过安装包、Docker 镜像 及云服务快速体验 TDengine 的功能,本节首先介绍如何通过 Docker 快速体验 TDengine,然后介绍如何在 Docker 环境下体验 TDengine 的写入和查询功能。如果你不熟悉 Docker,请使用 安装包的方式快…...
微软PowerBI考试 PL300-选择 Power BI 模型框架【附练习数据】
微软PowerBI考试 PL300-选择 Power BI 模型框架 20 多年来,Microsoft 持续对企业商业智能 (BI) 进行大量投资。 Azure Analysis Services (AAS) 和 SQL Server Analysis Services (SSAS) 基于无数企业使用的成熟的 BI 数据建模技术。 同样的技术也是 Power BI 数据…...
.Net框架,除了EF还有很多很多......
文章目录 1. 引言2. Dapper2.1 概述与设计原理2.2 核心功能与代码示例基本查询多映射查询存储过程调用 2.3 性能优化原理2.4 适用场景 3. NHibernate3.1 概述与架构设计3.2 映射配置示例Fluent映射XML映射 3.3 查询示例HQL查询Criteria APILINQ提供程序 3.4 高级特性3.5 适用场…...
vue3 定时器-定义全局方法 vue+ts
1.创建ts文件 路径:src/utils/timer.ts 完整代码: import { onUnmounted } from vuetype TimerCallback (...args: any[]) > voidexport function useGlobalTimer() {const timers: Map<number, NodeJS.Timeout> new Map()// 创建定时器con…...
AI编程--插件对比分析:CodeRider、GitHub Copilot及其他
AI编程插件对比分析:CodeRider、GitHub Copilot及其他 随着人工智能技术的快速发展,AI编程插件已成为提升开发者生产力的重要工具。CodeRider和GitHub Copilot作为市场上的领先者,分别以其独特的特性和生态系统吸引了大量开发者。本文将从功…...
【JavaWeb】Docker项目部署
引言 之前学习了Linux操作系统的常见命令,在Linux上安装软件,以及如何在Linux上部署一个单体项目,大多数同学都会有相同的感受,那就是麻烦。 核心体现在三点: 命令太多了,记不住 软件安装包名字复杂&…...
人机融合智能 | “人智交互”跨学科新领域
本文系统地提出基于“以人为中心AI(HCAI)”理念的人-人工智能交互(人智交互)这一跨学科新领域及框架,定义人智交互领域的理念、基本理论和关键问题、方法、开发流程和参与团队等,阐述提出人智交互新领域的意义。然后,提出人智交互研究的三种新范式取向以及它们的意义。最后,总结…...
RabbitMQ入门4.1.0版本(基于java、SpringBoot操作)
RabbitMQ 一、RabbitMQ概述 RabbitMQ RabbitMQ最初由LShift和CohesiveFT于2007年开发,后来由Pivotal Software Inc.(现为VMware子公司)接管。RabbitMQ 是一个开源的消息代理和队列服务器,用 Erlang 语言编写。广泛应用于各种分布…...
