首页 > 编程语言 >[Java/网络/HTTP(S)] 基于`Http(s)URLConnection`的网络请求工具(HttpRequestUtils)

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

时间:2024-12-28 21:20:12浏览次数:4  
标签:URLConnection HTTP String java 网络 new import public HttpURLConnection

1 序

  • 轻量级HTTP网络请求工具,接续:
  • 类比: 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.security

        FileInputStream 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[] {};

    @Override
    public 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
    }

    @Override
    public 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
    }

    @Override
    public 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 getInstance
            e.printStackTrace();
            throw new RuntimeException( e );
        } catch (KeyManagementException e) {//Fail to init
            e.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 {
    @Test
    public 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
    @Test
    public 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 推荐文献

标签:URLConnection,HTTP,String,java,网络,new,import,public,HttpURLConnection
From: https://www.cnblogs.com/johnnyzen/p/18637746

相关文章

  • 基于PSO粒子群优化的CNN-GRU-SAM网络时间序列回归预测算法matlab仿真
    1.算法运行效果图预览(完整程序运行后无水印) PSO优化过程:  PSO优化前后,模型训练对比:    数据预测对比:    误差回归对比:   2.算法运行软件版本matlab2022a 3.部分核心程序(完整版代码包含详细中文注释和操作步骤视频)LR......
  • python3网络爬虫开发实战-第2版PDF免费下载
    适读人群:本书适合Python程序员阅读。电子版仅供预览,下载后24小时内务必删除,支持正版,喜欢的请购买正版书籍:https://item.jd.com/13527222.htmlPython之父推荐的爬虫入门到实战教程书籍,上一版销量近10万册,静觅博客博主崔庆才倾力打造,App端也能爬微软中国大数据工程师、博客......
  • 解锁风电运维新密码:深度学习神经网络助力设备寿命精准预估
    摘要:当下,风电产业蓬勃发展,可恶劣运行环境使设备故障频发,精准预估剩余寿命迫在眉睫。深度学习中的神经网络为此带来曙光,其基础源于对大数据处理需求的回应,借由神经元、层架构自动提取特征。在风电应用里,CNN、LSTM深挖多源异构数据特征,MLP等架构构建预测模型,配合优化算法训......
  • HTTP 服务中的签名功能
    HTTP服务中的签名功能签名功能是通过对请求的数据或参数进行加密签名,验证请求的完整性和真实性,常用于保障HTTP服务的安全性。它确保数据未被篡改,同时也验证了请求的来源。签名功能的核心作用身份验证:确保请求确实来自合法的客户端(通常结合API密钥或私钥)。防篡改:......
  • 当下的网络安全行业前景到底怎么样?2025年还能否入行?
    很多人不知道网络安全发展前景好吗?学习网络安全能做什么?今天为大家解答下先说结论,网络安全的前景必然是超级好的作为一个有丰富Web安全攻防、渗透领域老工程师,之前也写了不少网络安全技术相关的文章,不少读者朋友知道我是从事网络安全相关的工作,于是经常有人私信向我“......
  • 2025年转行网安到底行不行,网络安全有没有发展前途,零基础转行难不难?
    在被新冠疫情常态化影响的今天,职场当中呈现出了严重的两极分化现象,具体的表现形式为:一些人薪资翻倍、愈加繁忙,另一些人则加入了失业大军、不知所措;一些行业实现了井喷式增长,一些行业却不断裁员、随时面临倒闭的风险。也有这样一个行业,正因为疫情的出现,使其变得更加重要,它......
  • [Wireshark] 使用Wireshark抓包https数据包并显示为明文、配置SSLKEYLOGFILE变量(附下
    wireshark下载链接:https://pan.quark.cn/s/eab7f1e963be提取码:rRAg链接失效(可能会被官方和谐)可评论或私信我重发chrome与firefox在访问https网站的时候会将密钥写入这个环境变量SSLKEYLOGFILE中,在wireshark中设置就可以解析https变量配置环境变量先创建文件ssl.log,点击......
  • 网络流 Dinic 算法笔记
    网络流Dinic算法笔记步骤建图,初始时令反向边权值为零,之后将该边每次用去的权值累计赋值给该反向边。分层,每次只能找下一个层的点。每次多向找增广路,并将跑满的边去掉,之后再去跑残量网络,直到榨干所有可用管道。大致就这样,然后注意时时减脂优化。code#include<bits/stdc+......
  • https://lalrpop.github.io/lalrpop/lexer_tutorial/003_writing_custom_lexer.html
    https://lalrpop.github.io/lalrpop/lexer_tutorial/003_writing_custom_lexer.html这是lalrpop官方教程的一篇文章,你言简意赅的总结一下主要讲的是什么UUUUUUUUUUUUUUUUUUUUUU这篇文章详细介绍了如何在LALRPOP中编写自定义的词法分析器(lexer),以便更精确地控制输入的标记化......
  • [Java SE] 核心源码精讲:java.net.URLConnection
    概述:URLConnectionURLConnection是一个抽象类,表示指向URL【指定资源】的活动连接URLConnection可以检查服务器发送的首部,并相应地做出响应。它可以设置客户端请求中使用的首部字段。URLConnection可以用POST、PUT和其他HTTP请求方法向服务器发回数据;URLConnection类是J......