Java编程语言,编写一个基于java.net.HttpURLConnection封装http请求的工具类

一、背景

在spring boot编程中,有Okhttp、Apache Httpclient等http调用的工具类,也有RestTemplate和Hutool等这样的工具,还有Retrofit和openfeign这样的更上层次封装的工具。

而本文不借助于任何三方工具包,除了json转换工具外,仅使用java.net.HttpURLConnection来封装http api接口调用。

先介绍一种方式,拿它和xxl-job开源项目的实现作对比,希望可以让你对HttpURLConnection的使用有所帮助。(还是那句话,我们在平常写http调用,建议你去使用开头提及的成熟框架)

二、流程图

  • 信任https证书:ignoreCertificateErrors()
java 复制代码
private void ignoreCertificateErrors() {
        TrustManager[] trustAllCerts = new TrustManager[]{
                new X509TrustManager() {
                    @Override
                    public java.security.cert.X509Certificate[] getAcceptedIssuers() {
                        return null;
                    }

                    @Override
                    public void checkClientTrusted(
                            java.security.cert.X509Certificate[] certs, String authType) {
                    }

                    @Override
                    public void checkServerTrusted(
                            java.security.cert.X509Certificate[] certs, String authType) {
                    }
                }
        };
        // Added per https://github.com/timols/java-gitlab-api/issues/44
        HostnameVerifier nullVerifier = (hostname, session) -> true;

        try {
            SSLContext sc = SSLContext.getInstance("SSL");
            sc.init(null, trustAllCerts, new java.security.SecureRandom());
            HttpsURLConnection.setDefaultSSLSocketFactory(sc.getSocketFactory());
            // Added per https://github.com/timols/java-gitlab-api/issues/44
            HttpsURLConnection.setDefaultHostnameVerifier(nullVerifier);
        } catch (Exception ignore) {}
    }
  • 设置连接的属性,主要包括读写和连接超时时间、请求方法、http header(这里是User-Agent和Accept-Encoding)
java 复制代码
private HttpURLConnection setupConnection(URL url) throws IOException {
        if (root.isIgnoreCertificateErrors()) {
            ignoreCertificateErrors();
        }
        connection.setReadTimeout(root.getResponseReadTimeout());
        connection.setConnectTimeout(root.getConnectionTimeout());

        connection.setRequestMethod(method.name());
        
        connection.setRequestProperty("User-Agent", root.getUserAgent());
        connection.setRequestProperty("Accept-Encoding", "gzip");
        return connection;
    }
  • 核心代码,先是建立连接,然后设置连接的属性,再拼接请求报文,最后是解析响应报文。
java 复制代码
public <T> T to(String tailAPIUrl, Class<T> type, T instance) throws IOException {
        HttpURLConnection connection = null;
        try {
            connection = setupConnection(root.getAPIUrl(tailAPIUrl));
            // 是否有附件
            if (hasAttachments()) {
                submitAttachments(connection);
            } else if (hasOutput()) {
                 submitData(connection);
            } else if (PUT.equals(method)) {
                // PUT requires Content-Length: 0 even when there is no body (eg: API for protecting a branch)
                connection.setDoOutput(true);
                // 关闭上传缓存
                connection.setFixedLengthStreamingMode(0);
            }

            try {
                return parse(connection, type, instance);
            } catch (IOException e) {
                handleAPIError(e, connection);
            }

            return null;
        } finally {
            if (connection != null) {
                connection.disconnect();
            }
        }
    }
  • hasOutput(),post请求方式,或者put请求方式且请求体不为空
java 复制代码
    private boolean hasOutput() {
        return method.equals(POST) || method.equals(PUT) && !data.isEmpty();
    }
  • submitData()
java 复制代码
import com.fasterxml.jackson.databind.DeserializationFeature;
import com.fasterxml.jackson.databind.ObjectMapper;

   private Map<String, Object> data = new HashMap<>();
   
   // HttpRequestor是工具类的类名
   // HttpRequestor.with("userId", 10002).with("userName", "zhangsan");
   public HttpRequestor with(String key, Object value) {
        if (value != null && key != null) {
            data.put(key, value);
        }
        return this;
    }

    public static final ObjectMapper MAPPER = new ObjectMapper().configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);

	private void submitData(HttpURLConnection connection) throws IOException {
        connection.setDoOutput(true);
        connection.setRequestProperty("Content-Type", "application/json");
		//  data是请求的入参,把它转换为json格式,传入请求体里
        MAPPER.writeValue(connection.getOutputStream(), data);
    }
  • parse(),如果是字节数组,则直接返回,否则需要进一步判断数据类型。

当是String类型或对象类型,需要进行数据类型的转换。

java 复制代码
private <T> T parse(HttpURLConnection connection, Class<T> type, T instance) throws IOException {
        InputStreamReader reader = null;
        try {
            // 如果是字节数组,直接返回
            if (byte[].class == type) {
                return type.cast(IOUtils.toByteArray(wrapStream(connection, connection.getInputStream())));
            }
    		// 读取响应报文内容,编码字符集是UTF-8
            reader = new InputStreamReader(wrapStream(connection, connection.getInputStream()), "UTF-8");
            
            String json = IOUtils.toString(reader);
            if (type != null && type == String.class) {
                return type.cast(json);
            }

			// MAPPER见上个方法
            if (type != null && type != Void.class) {
            // 把json字符串赋值给新建对象type
                return MAPPER.readValue(json, type);
            } else if (instance != null) {
            // 把json字符串赋值给对象type,仅更新,而非新建
                return MAPPER.readerForUpdating(instance).readValue(json);
            } else {
                return null;
            }
        } catch (SSLHandshakeException e) {
            throw new SSLException("You can disable certificate checking by setting ignoreCertificateErrors " +
                    "on GitlabHTTPRequestor.", e);
        } finally {
        // 关闭输入流
            IOUtils.closeQuietly(reader);
        }
    }
  • wrapStream(),需要对输入流进行判断,因为可能对数据进行了压缩。
java 复制代码
private InputStream wrapStream(HttpURLConnection connection, InputStream inputStream) throws IOException {
        String encoding = connection.getContentEncoding();

        if (encoding == null || inputStream == null) {
            return inputStream;
        } else if (encoding.equals("gzip")) {
            return new GZIPInputStream(inputStream);
        } else {
            throw new UnsupportedOperationException("Unexpected Content-Encoding: " + encoding);
        }
    }

三、xxl-job中的Http调用

顺便说一下,xxl-job中的http调用使用的就是HttpURLConnection,没有去依赖第三方的http请求框架。(详见其源码com.xxl.job.core.util.XxlJobRemotingUtil

java 复制代码
	public static ReturnT postBody(String url, String accessToken, int timeout, Object requestObj, Class returnTargClassOfT) {
        HttpURLConnection connection = null;
        BufferedReader bufferedReader = null;
        try {
            // connection
            URL realUrl = new URL(url);
            connection = (HttpURLConnection) realUrl.openConnection();

            // trust-https
            boolean useHttps = url.startsWith("https");
            if (useHttps) {
                HttpsURLConnection https = (HttpsURLConnection) connection;
                trustAllHosts(https);
            }

            // connection setting
            connection.setRequestMethod("POST");
            connection.setDoOutput(true);
            connection.setDoInput(true);
            connection.setUseCaches(false);
            connection.setReadTimeout(timeout * 1000);
            connection.setConnectTimeout(3 * 1000);
            connection.setRequestProperty("connection", "Keep-Alive");
            connection.setRequestProperty("Content-Type", "application/json;charset=UTF-8");
            connection.setRequestProperty("Accept-Charset", "application/json;charset=UTF-8");

            if(accessToken!=null && accessToken.trim().length()>0){
                connection.setRequestProperty(XXL_JOB_ACCESS_TOKEN, accessToken);
            }

            // do connection
            connection.connect();

            // write requestBody
            if (requestObj != null) {
                String requestBody = GsonTool.toJson(requestObj);

                DataOutputStream dataOutputStream = new DataOutputStream(connection.getOutputStream());
                dataOutputStream.write(requestBody.getBytes("UTF-8"));
                dataOutputStream.flush();
                dataOutputStream.close();
            }
            
            // valid StatusCode
            int statusCode = connection.getResponseCode();
            if (statusCode != 200) {
                return new ReturnT<String>(ReturnT.FAIL_CODE, "xxl-job remoting fail, StatusCode("+ statusCode +") invalid. for url : " + url);
            }

            // result
            bufferedReader = new BufferedReader(new InputStreamReader(connection.getInputStream(), "UTF-8"));
            StringBuilder result = new StringBuilder();
            String line;
            while ((line = bufferedReader.readLine()) != null) {
                result.append(line);
            }
            String resultJson = result.toString();

            // parse returnT
            try {
                ReturnT returnT = GsonTool.fromJson(resultJson, ReturnT.class, returnTargClassOfT);
                return returnT;
            } catch (Exception e) {
                logger.error("xxl-job remoting (url="+url+") response content invalid("+ resultJson +").", e);
                return new ReturnT<String>(ReturnT.FAIL_CODE, "xxl-job remoting (url="+url+") response content invalid("+ resultJson +").");
            }

        } catch (Exception e) {
            logger.error(e.getMessage(), e);
            return new ReturnT<String>(ReturnT.FAIL_CODE, "xxl-job remoting error("+ e.getMessage() +"), for url : " + url);
        } finally {
            try {
                if (bufferedReader != null) {
                    bufferedReader.close();
                }
                if (connection != null) {
                    connection.disconnect();
                }
            } catch (Exception e2) {
                logger.error(e2.getMessage(), e2);
            }
        }
    }
  • trustAllHosts()
java 复制代码
private static void trustAllHosts(HttpsURLConnection connection) {
        try {
            SSLContext sc = SSLContext.getInstance("TLS");
            sc.init(null, trustAllCerts, new java.security.SecureRandom());
            SSLSocketFactory newFactory = sc.getSocketFactory();

            connection.setSSLSocketFactory(newFactory);
        } catch (Exception e) {
            logger.error(e.getMessage(), e);
        }
        connection.setHostnameVerifier(new HostnameVerifier() {
            @Override
            public boolean verify(String hostname, SSLSession session) {
                return true;
            }
        });
    }
    private static final TrustManager[] trustAllCerts = new TrustManager[]{new X509TrustManager() {
        @Override
        public java.security.cert.X509Certificate[] getAcceptedIssuers() {
            return new java.security.cert.X509Certificate[]{};
        }
        @Override
        public void checkClientTrusted(X509Certificate[] chain, String authType) throws CertificateException {
        }
        @Override
        public void checkServerTrusted(X509Certificate[] chain, String authType) throws CertificateException {
        }
    }};
相关推荐
w_31234545 分钟前
自定义一个maven骨架 | 最佳实践
java·maven·intellij-idea
岁岁岁平安7 分钟前
spring学习(spring-DI(字符串或对象引用注入、集合注入)(XML配置))
java·学习·spring·依赖注入·集合注入·基本数据类型注入·引用数据类型注入
武昌库里写JAVA11 分钟前
Java成长之路(一)--SpringBoot基础学习--SpringBoot代码测试
java·开发语言·spring boot·学习·课程设计
北辰浮光12 分钟前
[spring]XML配置文件标签
xml·spring
Q_192849990617 分钟前
基于Spring Boot的九州美食城商户一体化系统
java·spring boot·后端
Q_192849990621 分钟前
基于Spring Boot的营销项目系统
spring boot
张国荣家的弟弟35 分钟前
【Yonghong 企业日常问题 06】上传的文件不在白名单,修改allow.jar.digest属性添加允许上传的文件SH256值?
java·jar·bi
ZSYP-S1 小时前
Day 15:Spring 框架基础
java·开发语言·数据结构·后端·spring
yuanbenshidiaos1 小时前
C++----------函数的调用机制
java·c++·算法
是小崔啊1 小时前
开源轮子 - EasyExcel01(核心api)
java·开发语言·开源·excel·阿里巴巴