[Java/网络/HTTP(S)] 基于`Http(s)URLConnection`的网络请求工具(HttpRequestUtils)

news/2024/12/28 21:14:55/文章来源:https://www.cnblogs.com/johnnyzen/p/18637746

1 序

  • 轻量级HTTP网络请求工具,接续:
  • [Java SE/JDK/网络] 核心源码精讲:java.net.HttpURLConnection - 博客园/千千寰宇
  • [身份认证/JWT] 身份认证方案与HTTP请求中Authorization Header - 博客园/千千寰宇 【推荐】
  • [网络/HTTPS/Java] PKI公钥基础设施体系:数字证书(X.509)、CA机构 | 含:证书管理工具(jdk keytool / openssl) - 博客园/千千寰宇 【推荐】
  • 类比: HttpClient / OkHttp 等 Java HTTP 网络请求工具

引入依赖

  • jdk 1.8.0_261
  • maven 依赖
<dependency><groupId>com.alibaba</groupId><artifactId>fastjson</artifactId><version>2.0.40</version>
</dependency><dependency><groupId>org.apache.httpcomponents</groupId><artifactId>httpclient</artifactId><version>4.5.6</version><scope>compile</scope>
</dependency><dependency><groupId>org.apache.httpcomponents</groupId><artifactId>httpcore</artifactId><version>4.5.6</version><scope>compile</scope>
</dependency><dependency><groupId>org.apache.httpcomponents</groupId><artifactId>httpcore-nio</artifactId><version>4.5.6</version><scope>compile</scope>
</dependency><dependency><groupId>org.apache.httpcomponents</groupId><artifactId>httpasyncclient</artifactId><version>4.5.6</version><scope>compile</scope>
</dependency>

HttpRequestUtils

import com.alibaba.fastjson2.JSON;
import com.fasterxml.jackson.databind.ObjectMapper;
import org.apache.commons.lang3.ObjectUtils;
import org.apache.http.conn.socket.LayeredConnectionSocketFactory;
import org.apache.http.conn.ssl.SSLConnectionSocketFactory;
import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.impl.client.HttpClients;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;import javax.net.ssl.HttpsURLConnection;
import javax.net.ssl.SSLContext;
import javax.net.ssl.SSLSocketFactory;
import javax.net.ssl.TrustManagerFactory;
import java.io.*;
import java.net.HttpURLConnection;
import java.net.URL;
import java.net.URLConnection;
import java.security.*;
import java.security.cert.CertificateException;
import java.util.HashMap;
import java.util.Map;public class HttpRequestUtils {private final static Logger logger = LoggerFactory.getLogger(HttpRequestUtils.class);private final static String APPLICATION_JSON = "application/json";private final static String APPLICATION_OCTET_STREAM = "application/octet-stream";/*** get unconnected HttpURLConnection* @reference-doc*  [1] java Url请求带header - 51cto - https://blog.51cto.com/u_16213438/7165885*  [2] HttpURLConnection 链接详解 - CSDN - https://blog.csdn.net/small_love/article/details/122410998* @param netUrl* @param requestProperties* @return HttpURLConnection* @throws IOException*/public static HttpURLConnection getHttpURLConnection(String netUrl, Map<String, String> requestProperties) throws IOException {return getHttpURLConnection(null, netUrl, requestProperties );}/*** get unconnected HttpURLConnection* @param sslContext* @param netUrl* @param requestProperties* @return HttpURLConnection* @throws IOException*/public static HttpURLConnection getHttpURLConnection(SSLContext sslContext, String netUrl, Map<String, String> requestProperties) throws IOException {checkUrlProtocol(netUrl);if(!ObjectUtils.isEmpty(sslContext)){setDefaultSSLSocketFactoryForHttpsURLConnection(sslContext);}URL url = new URL(netUrl);/** step1 打开连接 **/URLConnection connection = url.openConnection();/** step2 设置请求属性,如 请求头 **///connection.setRequestProperty("Content-Type", "application/json");//connection.setRequestProperty("Authorization", "Bearer your_token_here");if(!ObjectUtils.isEmpty(requestProperties)){requestProperties.entrySet().stream().forEach(requestPropertyEntry -> {connection.setRequestProperty(requestPropertyEntry.getKey(), requestPropertyEntry.getValue());});}/** step3 建立 TCP 连接 **///connection.connect();//建立TCP连接(但尚未发起HTTP请求) | 改由让调用方发起,并最终负责关闭连接HttpURLConnection httpURLConnection = (HttpURLConnection)connection;return httpURLConnection;}/*** get unconnected HttpURLConnection* @param netUrl* @return* @throws IOException*/public static HttpURLConnection getHttpURLConnection(String netUrl) throws IOException {return getHttpURLConnection(netUrl, new HashMap<>());}private static void checkUrlProtocol(String netUrl){if(netUrl == null){throw new RuntimeException("Fail to get HttpURLConnection cause by empty url!");}if(!netUrl.toLowerCase().startsWith("http")){//不支持非 http / https 协议的 url , 例如: "ftp://" , "file://" , "mail://" , ...throw new RuntimeException("Fail to get HttpURLConnection cause by not supported protocol(`http(s)`)!url:"+ netUrl);}}/*** 读取连接响应内容为 String* @description*  1. 一般该连接对象的响应类型(content-type)为 application/json*  2. 关闭连接 : 在调用方进行关闭,本方法不主动关闭流 【强制建议】* @param responseInputStream* @param connectionAlias* @throws IOException*/public static String readConnectionResponseAsString(InputStream responseInputStream, String connectionAlias) throws IOException {//step1 读取 HTTP 响应内容BufferedReader in = new BufferedReader(new InputStreamReader(responseInputStream));String inputLine;StringBuffer responseJsonStringBuffer = new StringBuffer();while ((inputLine = in.readLine()) != null) {responseJsonStringBuffer.append(inputLine);}//in.close();String responseJsonStr = responseJsonStringBuffer.toString();if(ObjectUtils.isEmpty(responseJsonStr)){logger.error(String.format("The connected http URL connection 's response content is empty!", connectionAlias));return "{}";}return responseJsonStr;}/*** 是否请求/响应成功* @param connectedHttpURLConnection 已连接的 HTTP-URL 连接 (有响应信息,也有请求信息)* @param connectionAlias 连接的别称,如: "xxxBackendXxxBusinessSceneApiRequest"* @note 注意事项*  1. 上层调用时,需捕获本方法抛出的异常,并及时关闭连接(`connectedHttpURLConnection.disconnect()`),以防止报如下错误:*      java.io.IOException: stream is closed*          at sun.net.www.protocol.http.HttpURLConnection$HttpInputStream.ensureOpen(HttpURLConnection.java:3429)*          at sun.net.www.protocol.http.HttpURLConnection$HttpInputStream.read(HttpURLConnection.java:3454)*          at sun.nio.cs.StreamDecoder.readBytes(StreamDecoder.java:284)*          ...* @note*  因本方法调用了 getResponseCode() / getInputStream() ,故: 强制建议调用完本方法后,需主动调用 HttpURLConnection#disconect() 关闭连接*  因抛异常时不会主动关闭连接 ,故: 强制建议调用完本方法后,需主动捕获异常,并调用 HttpURLConnection#disconect() 关闭连接*/public static boolean isRequestSuccess(HttpURLConnection connectedHttpURLConnection,String connectionAlias) throws IOException {if(ObjectUtils.isEmpty(connectedHttpURLConnection)){throw new RuntimeException(String.format("The connected http URL connection (`%s`) is empty! request property: %s", connectionAlias, JSON.toJSONString( connectedHttpURLConnection.getRequestProperties() ) ));}/** 200 / ... **/int responseCode = connectedHttpURLConnection.getResponseCode();//getResponseCode : 实际上底层会调用 getInputStream() => 会彻底触发/发起 HTTP 请求if( !(responseCode == HttpURLConnection.HTTP_OK || responseCode == HttpURLConnection.HTTP_MOVED_TEMP) ){//200(成功) or 302(重定向)//connectedHttpURLConnection.disconnect();//关闭连接,不在此处操作,在外层捕获异常时操作throw new RuntimeException( String.format("The connected http URL connection (`%s`)'s response code is not success/200 (%d)!", connectionAlias, responseCode ) );}/** "application/json" / "application/octet-stream" / ... */String responseContentType = connectedHttpURLConnection.getContentType();if(ObjectUtils.isEmpty(responseContentType)){throw new RuntimeException(String.format("The connected http URL connection (`%s`)'s response's content type is empty!", connectionAlias ) );}switch (responseContentType){case APPLICATION_JSON:InputStream responseInputStream = connectedHttpURLConnection.getInputStream();String responseStr = readConnectionResponseAsString( responseInputStream, connectionAlias );responseInputStream.close();//关闭输入流/*** step1 将HTTP响应结果转为JSON格式* @sample "{"status":false,"data":null,"errorCode":"XXX_BACKEND_099","errorMsg":"获取外链失败","traceId":"52bbd4f1d8264a76bc161faee9d92509.120.16992379058863989"}"*/ObjectMapper objectMapper = new ObjectMapper();HashMap<String, Object> response = objectMapper.readValue( responseStr, HashMap.class);/*** step2 判断 status 是否为空或为真*/Boolean status = (Boolean) response.get("status");if(ObjectUtils.isEmpty(status) || status != true){throw new RuntimeException(String.format("The connected http URL connection (`%s`)'s response's content type is %s and it's status is empty or not true!The response content is : %s.", connectionAlias, APPLICATION_JSON, JSON.toJSONString(response)));}break;case APPLICATION_OCTET_STREAM://Do Nothing; //暂不检查此类型,默认均为成功break;default://Do Nothing;break;}return true;}/*** 获取 信任指定SSL证书的 SSLContext* @return*/public static SSLContext getAllowTargetSslSSLContext(String keyStoreFilePath, String keyStorePassword) throws KeyStoreException, IOException, CertificateException, NoSuchAlgorithmException, KeyManagementException {KeyStore keyStore = KeyStore.getInstance(KeyStore.getDefaultType());//keystore.type=jks[default] | configFile: $JAVA_HOME/jre/lib/security/java.securityFileInputStream keyStoreFileInputStream = new FileInputStream(keyStoreFilePath);keyStore.load(keyStoreFileInputStream, keyStorePassword.toCharArray());//FileInputStream keyStoreFileInputStream = new FileInputStream(keyStoreFilePath);//如 : "root.crt"//CertificateFactory certificateFactory = CertificateFactory.getInstance("X.509");//Certificate certificate = certificateFactory.generateCertificate(keyStoreFileInputStream);//keyStore.load(null, null);//keyStore.setCertificateEntry("ca", certificate);String trustManagerFactoryAlgorithm = TrustManagerFactory.getDefaultAlgorithm();TrustManagerFactory trustManagerFactory = TrustManagerFactory.getInstance(trustManagerFactoryAlgorithm);trustManagerFactory.init(keyStore);SSLContext sslContext = SSLContext.getInstance("TLS");//sslContext.init(null, trustManagerFactory.getTrustManagers(), null);//或:sslContext.init(null, trustManagerFactory.getTrustManagers(), new SecureRandom());return sslContext;}/*** 获取 SSLSocketFactory* @note 外部直接使用本方法即可*/public static SSLSocketFactory getSSLSocketFactory(SSLContext sslContext){//LayeredConnectionSocketFactory sslSocketFactory = new SSLConnectionSocketFactory(context);//或:SSLSocketFactory sslSocketFactory = sslContext.getSocketFactory();return sslSocketFactory;}/*** 为 HttpsURLConnection 设置默认的SSLSocketFactory* @useage*  信任所有SSL,为 HttpsURLConnection 设置信任所有SSL的策略*  在所有https开始进行请求之前,执行一次即可:*      SSLContext sslContext = AllowAllSslHTTPSTrustManager.getAllowAllSslSSLContext();//信任所有证书* @note 外部直接使用本方法即可*/public static void setDefaultSSLSocketFactoryForHttpsURLConnection(SSLContext sslContext){//影响整个应用程序的 HTTPS 连接HttpsURLConnection.setDefaultSSLSocketFactory( getSSLSocketFactory(sslContext) );}/*** 为 HttpClient 设置默认的 SSLSocketFactory* @useage*   HttpGet request = new HttpGet(url);*   request.setHeader("User-Agent", "Mozilla/5.0 (Windows NT 6.1; WOW64) ...");*   CloseableHttpResponse response = httpclient.execute(request);*   String responseBody = readResponseBody(response);*   System.out.println(responseBody);* @return*/public static CloseableHttpClient setDefaultSSLSocketFactoryForHttpClient(SSLContext sslContext){LayeredConnectionSocketFactory sslSocketFactory = new SSLConnectionSocketFactory( sslContext );//或://SSLSocketFactory sslSocketFactory =  getSSLSocketFactory();CloseableHttpClient httpclient = HttpClients.custom().setSSLSocketFactory(sslSocketFactory).build();return httpclient;}
}

AllowAllSslHTTPSTrustManager(可选)


import javax.net.ssl.*;
import java.security.KeyManagementException;
import java.security.NoSuchAlgorithmException;
import java.security.SecureRandom;
import java.security.cert.X509Certificate;/*** HTTPS信任证书管理器* @reference-doc*  [1] 使用HttpsURLConnection的3种方法小结 - CSDN - https://blog.csdn.net/suyimin2010/article/details/81025083*/
public class AllowAllSslHTTPSTrustManager implements X509TrustManager {private static TrustManager[] trustManagers;private static final X509Certificate[] acceptedIssuers = new X509Certificate[] {};@Overridepublic void checkClientTrusted( X509Certificate[] x509Certificates, String s) throws java.security.cert.CertificateException {// To change body of implemented methods use File | Settings | File// Templates.// Do Nothing | don't check / allow all}@Overridepublic void checkServerTrusted(X509Certificate[] x509Certificates, String s)throws java.security.cert.CertificateException {// To change body of implemented methods use File | Settings | File// Templates.// Do Nothing | don't check / allow all}@Overridepublic X509Certificate[] getAcceptedIssuers() {return acceptedIssuers;}/*** 获取 信任全部 SSL/TLS CA证书的 SSLContext* @note 外部直接使用本方法即可*/public static SSLContext getAllowAllSslSSLContext() {HttpsURLConnection.setDefaultHostnameVerifier(DO_NOT_VERIFY);SSLContext context = null;if (trustManagers == null) {trustManagers = new TrustManager[] {new AllowAllSslHTTPSTrustManager()};}try {context = SSLContext.getInstance("TLS");//context.init(null, trustManagers, null);//或:context.init(null, trustManagers, new SecureRandom());} catch (NoSuchAlgorithmException e) {//Fail to getInstancee.printStackTrace();throw new RuntimeException( e );} catch (KeyManagementException e) {//Fail to inite.printStackTrace();throw new RuntimeException( e );}return context;}public static SSLSocketFactory getAllowAllSslSSLContextFactory() {SSLContext context = getAllowAllSslSSLContext();return context.getSocketFactory();}@SuppressWarnings("squid:S5527")final static HostnameVerifier DO_NOT_VERIFY = new HostnameVerifier() {//根据 hostname 和 SSLSession ,做 SSL 验证public boolean verify(String hostname, SSLSession session) {//将所有验证的结果都直接设为true => 即 不验证return true;}};
}

Test

import lombok.SneakyThrows;
import lombok.extern.slf4j.Slf4j;
import org.junit.Test;import javax.net.ssl.SSLContext;
import java.io.IOException;
import java.io.InputStream;
import java.net.HttpURLConnection;
import java.util.HashMap;
import java.util.Map;@Slf4j
public class HttpRequestUtilsTest {@Testpublic void test() throws IOException {/** step0 准备参数 **/String apiConnection = "fileTextDownloadApiRequest";String fileUri = "https://www.baidu.com";String agentCode = "xxx";String jobName = "xxxJob";Map<String, String> requestProperties = new HashMap<>();Boolean isEnableAgentCode = true;/** step1 设置租户代码 **/if(isEnableAgentCode){//requestProperties.put(Constants.AgentCode.AGENT_CODE_REQUEST_HEADER, agentCode);requestProperties.put("requester",  "FLINK_JOB#" + jobName);//标识请求方,便于问题溯源}/** step2 发起API请求,获得连接 **/HttpURLConnection connectedHttpURLConnection = HttpRequestUtils.getHttpURLConnection(fileUri, requestProperties);/** step3 建立 tcp 连接 **/connectedHttpURLConnection.connect();/** step4 判定是否请求成功 **/boolean isRequestSuccess = true;try {isRequestSuccess = HttpRequestUtils.isRequestSuccess(connectedHttpURLConnection, apiConnection);log.debug(String.format("Success to build the api request for `%s`.fileUri : %s, agentCode : %s, isEnableAgentCode : %s", apiConnection, fileUri, agentCode, isEnableAgentCode));} catch (Exception exception){isRequestSuccess = false;log.error(exception.getMessage());log.error(String.format("Fail to build the api request for `%s`.fileUri : %s, agentCode : %s, isEnableAgentCode : %s", apiConnection, fileUri, agentCode, isEnableAgentCode));exception.printStackTrace();}InputStream responseInputStream = connectedHttpURLConnection.getInputStream();/** step5 解析 HTTP响应 + 断开连接 **///Map<String, List<XXXDTO>> xxxDtoMapList = null;//if(isRequestSuccess){//    xxxDtoMapList = XXXFileUtils.parseXXXFileToSignalsDto(responseInputStream);//}/** step6 断开连接 **/connectedHttpURLConnection.disconnect();log.debug(String.format("disconnected the http url connection(`%s`)!", apiConnection));//return xxxDtoMapList;}@SneakyThrows@Testpublic void notVerifySslTest(){String url = "https://baidu.com";String urlAlias = "baiduIndexHttpRequest";Map<String, String> requestProperties = new HashMap<>();//SSLContext sslContext = HttpRequestUtils.getAllowTargetSslSSLContext(String keyStoreFilePath, String keyStorePassword);SSLContext sslContext = AllowAllSslHTTPSTrustManager.getAllowAllSslSSLContext();HttpURLConnection httpURLConnection = HttpRequestUtils.getHttpURLConnection(sslContext, url, requestProperties);httpURLConnection.connect();Boolean isRequestSuccess = null;try {isRequestSuccess = HttpRequestUtils.isRequestSuccess(httpURLConnection, urlAlias);log.debug(String.format("Success to build the api request for `%s`! url : %s", urlAlias , url));} catch (Exception exception) {log.error("Fail to request the target url!url:{},exception:\n{}", url, exception);exception.printStackTrace();httpURLConnection.disconnect();System.exit(0);}if(isRequestSuccess){InputStream responseInputStream = httpURLConnection.getInputStream();int available = responseInputStream.available();log.info("responseInputStream.available", available);}httpURLConnection.disconnect();//主动关闭连接log.info("end");}
}

Y 推荐文献

  • [Java SE/JDK/网络] 核心源码精讲:java.net.HttpURLConnection - 博客园/千千寰宇
  • [身份认证/JWT] 身份认证方案与HTTP请求中Authorization Header - 博客园/千千寰宇 【推荐】
  • [网络/HTTPS/Java] PKI公钥基础设施体系:数字证书(X.509)、CA机构 | 含:证书管理工具(jdk keytool / openssl) - 博客园/千千寰宇 【推荐】

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

如若内容造成侵权/违法违规/事实不符,请联系编程知识网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

2024/12/17 【字符串】LeetCode 459.重复的子字符串 【❌】

https://programmercarl.com/0459.%E9%87%8D%E5%A4%8D%E7%9A%84%E5%AD%90%E5%AD%97%E7%AC%A6%E4%B8%B2.html#%E6%80%9D%E8%B7%AF https://leetcode.cn/problems/repeated-substring-pattern/ 子串结束位置大于中间位置的话,一定不能重复组成字符串。 如果 next[len - 1] != -1…

一起鸿蒙吧,现在到了「绝佳时刻」

摘要:它已经不止于可用和好用,而是迈向“必用”的新阶段了。 10月22日,原生鸿蒙操作系统如约而至。 作为一个全新的操作系统,原生鸿蒙拥有流畅、安全、智能、互联、精致五大原生特性,为无数用户开启了一个全新的数字生活方式。 如果你正在犹豫是否要升级原生鸿蒙系统,那么…

题目集7~8总结性博客

前言 在本学期的学习过程中,我们共完成了三次题目集的练习,其中第七题和第八题集在知识点、题量和难度上具有一定的代表性。总体而言,这两次题目集涵盖了面向对象编程(OOP)、设计模式、数据结构与算法、软件工程等多个核心知识点。 知识点总结: 面向对象编程(OOP): 类…

Python 中使用 Matplotlib 进行多图绘制

Python 中使用 Matplotlib 进行多图绘制 Matplotlib 是 Python 中非常强大的数据可视化工具,它可以用来生成简单到复杂的各种图形。无论是处理单张图表还是多图并列展示,Matplotlib 都能提供高效的支持。在本篇文章中,我们将介绍如何使用 Matplotlib 绘制多图,以便在同一画…

fping 的使用方法

fping简介 fping是一个小型命令行工具,用于向网络主机发送ICMP回应请求,类似于ping,但在ping多个主机时性能要高得多。 fping完全不同于ping,因为可以在命令行上定义任意数量的主机,或者指定包含要ping的IP地址或主机列表的文件。 与ping要等待某一主机连接超时或发回反馈…

.NET Bioss相关数据读写

本文我们介绍针对Bios如何读取、写入数据,比如最常见的SN读取以及烧录 WMI查询 先看看WMI方式,可以用于查询和管理Windows系统的各种信息,包括读取BIOS信息 WMI-Win32_BIOS,可以查看Bios版本、制造商以及Bios Sn等:1 var searcher = new ManagementObjectSear…

家居强电电路模拟程序总结

一、前言:这两次的PTA作业第一次是以前两次家居强电电路模拟程序为基础所扩展的,在上一次作业的基础上增加了一个新的互斥开关,互斥开关的电路符号为H,其12引脚之间电阻为5欧,13引脚之间电阻为10欧,还增加了一个新的受控窗帘,受控窗帘的电路符号为S,窗帘电阻为15欧,其…

Python 正则表达式进阶用法:字符集与字符范围详解

Python 正则表达式进阶用法:字符集与字符范围详解 正则表达式是文本处理和数据清洗中不可或缺的工具。在前面的学习中,我们已经了解了基本的正则表达式匹配,如匹配单个字符、字符串开始和结束的位置等。今天,我们将进入正则表达式的一个进阶主题:字符集(Character Set)和…

如何免费使用 Termius Pro 版?Termius 工具下载与破解教程

今天想和大家聊聊 Termius 工具,Termius的下载与Termius破解教程,一款跨平台的 SSH/SFTP 终端工具。它不仅功能强大,还特别适合需要频繁上传文件夹和进行远程管理的小伙伴。无论你是程序员、运维工程师,还是需要远程管理服务器的爱好者,这款工具都能成为你的得力助手!今天…

Python 正则表达式进阶用法:边界匹配

Python 正则表达式进阶用法:边界匹配 正则表达式是一种强大的工具,用于处理文本中的模式匹配。它广泛应用于文本查找、替换、数据清洗等任务。在学习了正则表达式的基础知识后,掌握更高级的用法将使得正则表达式的应用更加灵活。边界匹配(Boundary Matching)是正则表达式中…

java-BLOG3

一:前言知识点总结题目集7控制设备:引入了开关、分档调速器、连续调速器和互斥开关四种控制设备,重点在于理解这些设备的工作原理及其在电路中的作用。 受控设备:包括灯(白炽灯、日光灯)和风扇(吊扇、落地扇),以及新增的受控窗帘。主要考察学生对不同设备工作状态的理…

学习笔记 - 汉明码

汉明码属于一种具备纠错功能的线性分组码。 在数据传输时,受外界干扰影响,单个比特可能产生差错。汉明码借助添加的冗余监督位,依照特定规则精准判断出错比特位,进而完成纠正,确保信息恢复如初,宛如给数据披上一层“防护甲”,使其即便处于复杂环境,也能维持精准可靠,在…