Java开发-Http与Https请求的工具类

作者: 奋斗的蛐蛐 | 来源:发表于2017-07-09 02:43 被阅读171次

    1、简介

    在互联网安全通信方式上,目前用的最多的就是https配合ssl和数字证书来保证传输和认证安全了。

    2、名词介绍

    2.1 HTTP:  HTTP是hypertext transfer protocol(超文本传输协议)的简写,它是TCP/IP协议的一个应用层协议,用于定义WEB浏览器与WEB服务器之间交换数据的过程。客户端连上web服务器后,若想获得web服务器中的某个web资源,需遵守一定的通讯格式,HTTP协议用于定义客户端与web服务器通迅的格式。
    2.2 HTTPS: HTTPS(全称:Hyper Text Transfer Protocol over Secure Socket Layer),是以安全为目标的HTTP通道,简单讲是HTTP的安全版。即HTTP下加入SSL层,HTTPS的安全基础是SSL,因此加密的详细内容就需要SSL。
    2.3 SSL (Secure Socket Layer)为Netscape所研发,用以保障在Internet上数据传输之安全,利用数据加密(Encryption)技术,可确保数据在网络上之传输过程中不会被截取及窃听。

    3、Http与Https请求

    本工具封装了采用HttpClient发送HTTP请求的方法,本工具所采用的是HttpComponents-Client-4.2.1,开发HTTPS应用时,时常会遇到两种情况

    • 测试服务器没有有效的SSL证书,客户端连接时就会抛异常
      javax.net.ssl.SSLPeerUnverifiedException: peer not authenticated
    • 测试服务器有SSL证书,但可能由于各种不知名的原因,它还是会抛一堆烂码七糟的异常,诸如下面这两种
      javax.net.ssl.SSLException: hostname in certificate didn't match:
      javax.net.ssl.SSLHandshakeException: sun.security.validator.ValidatorException: PKIX path building failed:sun.security.provider.certpath.SunCertPathBuilderException: unable to
      find valid certification path to requested target

    这里使用的是HttpComponents-Client-4.2.1创建的连接,所以就要告诉它使用一个不同的TrustManager,由于SSL使用的模式是X.509,对于该模式,Java有一个特定的TrustManager,称为X509TrustManager,TrustManager是一个用于检查给定的证书是否有效的类,所以我们自己创建一个X509TrustManager实例,而在X509TrustManager实例中,若证书无效,那么TrustManager在它的checkXXX()方法中将抛出CertificateException,既然我们要接受所有的证书,那么X509TrustManager里面的方法体中不抛出异常就行了,然后创建一个SSLContext并使用X509TrustManager实例来初始化之,接着通过SSLContext创建SSLSocketFactory,最后将SSLSocketFactory注册给HttpClient就可以了。

    3.1 发送HTTP_GET请求

    该方法会自动关闭连接,释放资源,方法内设置了连接和读取超时时间,单位为毫秒,超时或发生其它异常时方法会自动返回"通信失败"字符串,请求参数含中文时,经测试可直接传入中文,HttpClient会自动编码发给Server,应用时应根据实际效果决定传入前是否转码,该方法会自动获取到响应消息头中[Content-Type:text/html; charset=GBK]的charset值作为响应报文的解码字符集,若响应消息头中无Content-Type属性,则会使用HttpClient内部默认的ISO-8859-1作为响应报文的解码字符集

    /**
    * requestURL  请求地址(含参数)
    * 远程主机响应正文
    */
    public static String sendGetRequest(String reqURL, String encodeCharset) {
    
            String respContent = "{status:\"0\",result:\"通信失败\"}"; // 响应内容
    
            @SuppressWarnings("resource")
            HttpClient httpClient = new DefaultHttpClient(); // 创建默认的httpClient实例
    
            // 设置代理服务器
    
            // httpClient.getParams().setParameter(ConnRoutePNames.DEFAULT_PROXY,
            // new HttpHost("10.0.0.4", 8080));
    
            httpClient.getParams().setParameter(CoreConnectionPNames.CONNECTION_TIMEOUT, HTTP_TIMEOUT); // 连接超时20s
    
            httpClient.getParams().setParameter(CoreConnectionPNames.SO_TIMEOUT, HTTP_TIMEOUT); // 读取超时50s
    
            HttpGet httpGet = new HttpGet(reqURL); // 创建org.apache.http.client.methods.HttpGet
    
            try {
    
                HttpResponse response = httpClient.execute(httpGet); // 执行GET请求
    
                HttpEntity entity = response.getEntity(); // 获取响应实体
    
                if (null != entity) {
    
                    // respCharset=EntityUtils.getContentCharSet(entity)也可以获取响应编码,但从4.1.3开始不建议使用这种方式
    
                    respContent = EntityUtils.toString(entity, encodeCharset);
    
                    // Consume response content
    
                    EntityUtils.consume(entity);
    
                }
    
                System.out.println("-------------------------------------------------------------------------------------------");
    
                StringBuilder respHeaderDatas = new StringBuilder();
    
                for (Header header : response.getAllHeaders()) {
    
                    respHeaderDatas.append(header.toString()).append("\r\n");
    
                }
    
                String respStatusLine = response.getStatusLine().toString(); // HTTP应答状态行信息
    
                String respHeaderMsg = respHeaderDatas.toString().trim(); // HTTP应答报文头信息
    
                String respBodyMsg = respContent; // HTTP应答报文体信息
                
                System.out.println("HTTP请求地址:" + reqURL);
    
                System.out.println("HTTP应答完整报文\n" + respStatusLine + "\r\n" + respHeaderMsg + "\r\n" + respBodyMsg + "");
    //
                System.out.println("-------------------------------------------------------------------------------------------");
    
            } catch (ConnectTimeoutException cte) {
                cte.printStackTrace();
                // Should catch ConnectTimeoutException, and don`t catch
                // org.apache.http.conn.HttpHostConnectException
    
                // //LogUtil.getLogger().error("请求通信[" + reqURL + "]时连接超时,堆栈轨迹如下",
                // cte);
    
            } catch (SocketTimeoutException ste) {
                ste.printStackTrace();
                // LogUtil.getLogger().error("请求通信[" + reqURL + "]时读取超时,堆栈轨迹如下",
                // ste);
    
            } catch (ClientProtocolException cpe) {
                cpe.printStackTrace();
                // 该异常通常是协议错误导致:比如构造HttpGet对象时传入协议不对(将'http'写成'htp')or响应内容不符合HTTP协议要求等
    
                // LogUtil.getLogger().error("请求通信[" + reqURL + "]时协议异常,堆栈轨迹如下",
                // cpe);
    
            } catch (ParseException pe) {
                pe.printStackTrace();
                // LogUtil.getLogger().error("请求通信[" + reqURL + "]时解析异常,堆栈轨迹如下",
                // pe);
    
            } catch (IOException ioe) {
                ioe.printStackTrace();
                // 该异常通常是网络原因引起的,如HTTP服务器未启动等
    
                // LogUtil.getLogger().error("请求通信[" + reqURL + "]时网络异常,堆栈轨迹如下",
                // ioe);
    
            } catch (Exception e) {
                e.printStackTrace();
                // LogUtil.getLogger().error("请求通信[" + reqURL + "]时偶遇异常,堆栈轨迹如下", e);
    
            } finally {
    
                // 关闭连接,释放资源
    
                httpClient.getConnectionManager().shutdown();
    
            }
    
            return respContent;
    
        }
    

    3.2 发送HTTP_POST请求

    该方法允许自定义任何格式和内容的HTTP请求报文体,该方法会自动关闭连接,释放资源,方法内设置了连接和读取超时时间,单位为毫秒,超时或发生其它异常时方法会自动返回"通信失败"字符串,请求参数含中文等特殊字符时,可直接传入本方法,并指明其编码字符集encodeCharset参数,方法内部会自动对其转码,该方法在解码响应报文时所采用的编码,取自响应消息头中的[Content-Type:text/html;charset=GBK]的charset值,若响应消息头中未指定Content-Type属性,则会使用HttpClient内部默认的ISO-8859-1

    /**
    * 
    * @param reqURL请求地址
    * @param reqData请求参数,若有多个参数则应拼接为param11=value11&22=value22&33=value33的形式
    * @param encodeCharset编码字符集,编码请求数据时用之,此参数为必填项(不能为""或null)
    * 
    */
    public static String sendPostRequest(String reqURL, String reqData, String encodeCharset, String contentType) {
    
            String respContent = "通信失败";
    
            @SuppressWarnings("resource")
            HttpClient httpClient = new DefaultHttpClient();
    
            httpClient.getParams().setParameter(CoreConnectionPNames.CONNECTION_TIMEOUT, HTTP_TIMEOUT);
    
            httpClient.getParams().setParameter(CoreConnectionPNames.SO_TIMEOUT, HTTP_TIMEOUT);
    
            HttpPost httpPost = new HttpPost(reqURL);
    
            // 由于下面使用的是new
            // StringEntity(....),所以默认发出去的请求报文头中CONTENT_TYPE值为text/plain;
            // charset=ISO-8859-1
    
            // 这就有可能会导致服务端接收不到POST过去的参数,比如运行在Tomcat6.0.36中的Servlet,所以我们手工指定CONTENT_TYPE头消息
    
            httpPost.setHeader(HTTP.CONTENT_TYPE, contentType + "; charset=" + encodeCharset);
    
            try {
    
                httpPost.setEntity(new StringEntity(reqData == null ? "" : reqData, encodeCharset));
    
                HttpResponse response = httpClient.execute(httpPost);
    
                HttpEntity entity = response.getEntity();
                
                if (null != entity) {
    
                    respContent = EntityUtils.toString(entity, "utf-8");
    
                    EntityUtils.consume(entity);
    
                }
                
                
                System.out.println("-------------------------------------------------------------------------------------------");
    
                StringBuilder respHeaderDatas = new StringBuilder();
    
                for (Header header : response.getAllHeaders()) {
    
                    respHeaderDatas.append(header.toString()).append("\r\n");
    
                }
    
                String respStatusLine = response.getStatusLine().toString(); // HTTP应答状态行信息
    
                String respHeaderMsg = respHeaderDatas.toString().trim(); // HTTP应答报文头信息
    
                String respBodyMsg = respContent; // HTTP应答报文体信息
                
                System.out.println("HTTP请求地址:" + reqURL);
                System.out.println("HTTP请求参数:" + reqData);
                System.out.println("HTTP请求方式:" + contentType);
                System.out.println("HTTP应答完整报文\n" + respStatusLine + "\r\n" + respHeaderMsg + "\r\n" + respBodyMsg + "");
    //
                System.out.println("-------------------------------------------------------------------------------------------");
    
            } catch (ConnectTimeoutException cte) {
                cte.printStackTrace();
                // LogUtil.getLogger().error("请求通信[" + reqURL + "]时连接超时,堆栈轨迹如下",
                // cte);
    
            } catch (SocketTimeoutException ste) {
                ste.printStackTrace();
                // LogUtil.getLogger().error("请求通信[" + reqURL + "]时读取超时,堆栈轨迹如下",
                // ste);
    
            } catch (Exception e) {
                e.printStackTrace();
                // LogUtil.getLogger().error("请求通信[" + reqURL + "]时偶遇异常,堆栈轨迹如下", e);
    
            } finally {
    
                httpClient.getConnectionManager().shutdown();
    
            }
    
            return respContent;
    
        }
    
    

    3.3 发送HTTP_POST_SSL请求

    该方法会自动关闭连接,释放资源,该方法亦可处理普通的HTTP_POST请求,当处理HTTP_POST_SSL请求时,默认请求的是对方443端口,除非reqURL参数中指明了SSL端口,方法内设置了连接和读取超时时间,单位为毫秒,超时或发生其它异常时方法会自动返回"通信失败"字符串,请求参数含中文等特殊字符时,可直接传入本方法,并指明其编码字符集encodeCharset参数,方法内部会自动对其转码,方法内部会自动注册443作为SSL端口,若实际使用中reqURL指定的SSL端口非443,可自行尝试更改方法内部注册的SSL端口该方法在解码响应报文时所采用的编码,取自响应消息头中的[Content-Type:text/html;charset=GBK]的charset值,若响应消息头中未指定Content-Type属性,则会使用HttpClient内部默认的ISO-8859-1

    
    /**
    * @param reqURL   请求地址
    * @param params 请求参数
    * @param encodeCharset 编码字符集,编码请求数据时用之,当其为null时,则取HttpClient内部默认的ISO-8859-1编码请求参数
     * @return 远程主机响应正文
    */
        public static String sendPostSSLRequest(String reqURL, Map<String, String> params, String encodeCharset) {
    
            String responseContent = "通信失败";
    
            @SuppressWarnings("resource")
            HttpClient httpClient = new DefaultHttpClient();
    
            httpClient.getParams().setParameter(CoreConnectionPNames.CONNECTION_TIMEOUT, HTTP_TIMEOUT);
    
            httpClient.getParams().setParameter(CoreConnectionPNames.SO_TIMEOUT, HTTP_TIMEOUT);
    
            // 创建TrustManager()
    
            // 用于解决javax.net.ssl.SSLPeerUnverifiedException: peer not authenticated
    
            X509TrustManager trustManager = new X509TrustManager() {
    
                public void checkClientTrusted(X509Certificate[] chain, String authType) throws CertificateException {
                }
    
                public void checkServerTrusted(X509Certificate[] chain, String authType) throws CertificateException {
                }
    
                public X509Certificate[] getAcceptedIssuers() {
                    return null;
                }
    
            };
    
            // 创建HostnameVerifier
    
            // 用于解决javax.net.ssl.SSLException: hostname in certificate didn't match:
            // <123.125.97.66> != <123.125.97.241>
    
            X509HostnameVerifier hostnameVerifier = new X509HostnameVerifier() {
    
                public void verify(String host, SSLSocket ssl) throws IOException {
                }
    
                public void verify(String host, X509Certificate cert) throws SSLException {
                }
    
                public void verify(String host, String[] cns, String[] subjectAlts) throws SSLException {
                }
    
                public boolean verify(String arg0, SSLSession arg1) {
                    return true;
                }
    
            };
    
            try {
    
                // TLS1.0与SSL3.0基本上没有太大的差别,可粗略理解为TLS是SSL的继承者,但它们使用的是相同的SSLContext
    
                SSLContext sslContext = SSLContext.getInstance(SSLSocketFactory.TLS);
    
                // 使用TrustManager来初始化该上下文,TrustManager只是被SSL的Socket所使用
    
                sslContext.init(null, new TrustManager[] { trustManager }, null);
    
                // 创建SSLSocketFactory
    
                SSLSocketFactory socketFactory = new SSLSocketFactory(sslContext, hostnameVerifier);
    
                // 通过SchemeRegistry将SSLSocketFactory注册到HttpClient上
    
                httpClient.getConnectionManager().getSchemeRegistry().register(new Scheme("https", 443, socketFactory));
    
                // 创建HttpPost
    
                HttpPost httpPost = new HttpPost(reqURL);
    
                // 由于下面使用的是new
                // UrlEncodedFormEntity(....),所以这里不需要手工指定CONTENT_TYPE为application/x-www-form-urlencoded
    
                // 因为在查看了HttpClient的源码后发现,UrlEncodedFormEntity所采用的默认CONTENT_TYPE就是application/x-www-form-urlencoded
    
                // httpPost.setHeader(HTTP.CONTENT_TYPE,
                // "application/x-www-form-urlencoded; charset=" + encodeCharset);
    
                // 构建POST请求的表单参数
    
                if (null != params) {
    
                    List<NameValuePair> formParams = new ArrayList<NameValuePair>();
    
                    for (Map.Entry<String, String> entry : params.entrySet()) {
    
                        formParams.add(new BasicNameValuePair(entry.getKey(), entry.getValue()));
    
                    }
    
                    httpPost.setEntity(new UrlEncodedFormEntity(formParams, encodeCharset));
    
                }
    
                HttpResponse response = httpClient.execute(httpPost);
    
                HttpEntity entity = response.getEntity();
    
                if (null != entity) {
    
                    responseContent = EntityUtils.toString(entity, ContentType.getOrDefault(entity).getCharset());
    
                    EntityUtils.consume(entity);
    
                }
    
            } catch (ConnectTimeoutException cte) {
                cte.printStackTrace();
                // LogUtil.getLogger().error("请求通信[" + reqURL + "]时连接超时,堆栈轨迹如下",
                // cte);
    
            } catch (SocketTimeoutException ste) {
                ste.printStackTrace();
                // LogUtil.getLogger().error("请求通信[" + reqURL + "]时读取超时,堆栈轨迹如下",
                // ste);
    
            } catch (Exception e) {
                e.printStackTrace();
                // LogUtil.getLogger().error("请求通信[" + reqURL + "]时偶遇异常,堆栈轨迹如下", e);
    
            } finally {
    
                httpClient.getConnectionManager().shutdown();
    
            }
    
            return responseContent;
    
        }
    
    

    3.4 sendGetSSLRequest

    /**
    * @param reqURL
    * @param params
    * @param encodeCharset
    * @return
    */
        public static String sendGetSSLRequest(String reqURL, String encodeCharset) {
    
            String responseContent = "通信失败";
    
            @SuppressWarnings("resource")
            HttpClient httpClient = new DefaultHttpClient();
    
            httpClient.getParams().setParameter(CoreConnectionPNames.CONNECTION_TIMEOUT, HTTP_TIMEOUT);
    
            httpClient.getParams().setParameter(CoreConnectionPNames.SO_TIMEOUT, HTTP_TIMEOUT);
    
            // 创建TrustManager()
    
            // 用于解决javax.net.ssl.SSLPeerUnverifiedException: peer not authenticated
    
            X509TrustManager trustManager = new X509TrustManager() {
    
                public void checkClientTrusted(X509Certificate[] chain, String authType) throws CertificateException {
                }
    
                public void checkServerTrusted(X509Certificate[] chain, String authType) throws CertificateException {
                }
    
                public X509Certificate[] getAcceptedIssuers() {
                    return null;
                }
    
            };
    
            // 创建HostnameVerifier
    
            // 用于解决javax.net.ssl.SSLException: hostname in certificate didn't match:
            // <123.125.97.66> != <123.125.97.241>
            X509HostnameVerifier hostnameVerifier = new X509HostnameVerifier() {
    
                public void verify(String host, SSLSocket ssl) throws IOException {
                }
    
                public void verify(String host, X509Certificate cert) throws SSLException {
                }
    
                public void verify(String host, String[] cns, String[] subjectAlts) throws SSLException {
                }
    
                public boolean verify(String arg0, SSLSession arg1) {
                    return true;
                }
    
            };
    
            try {
    
                // TLS1.0与SSL3.0基本上没有太大的差别,可粗略理解为TLS是SSL的继承者,但它们使用的是相同的SSLContext
    
                SSLContext sslContext = SSLContext.getInstance(SSLSocketFactory.TLS);
    
                // 使用TrustManager来初始化该上下文,TrustManager只是被SSL的Socket所使用
    
                sslContext.init(null, new TrustManager[] { trustManager }, null);
    
                // 创建SSLSocketFactory
    
                SSLSocketFactory socketFactory = new SSLSocketFactory(sslContext, hostnameVerifier);
    
                // 通过SchemeRegistry将SSLSocketFactory注册到HttpClient上
    
                httpClient.getConnectionManager().getSchemeRegistry().register(new Scheme("https", 443, socketFactory));
    
                // 创建org.apache.http.client.methods.HttpGet
                HttpGet httpGet = new HttpGet(reqURL);
    
                // 由于下面使用的是new
                // UrlEncodedFormEntity(....),所以这里不需要手工指定CONTENT_TYPE为application/x-www-form-urlencoded
    
                // 因为在查看了HttpClient的源码后发现,UrlEncodedFormEntity所采用的默认CONTENT_TYPE就是application/x-www-form-urlencoded
    
                HttpResponse response = httpClient.execute(httpGet);
    
                HttpEntity entity = response.getEntity();
    
                if (null != entity) {
    
                    responseContent = EntityUtils.toString(entity, encodeCharset);
    
                    EntityUtils.consume(entity);
    
                }
    
                System.out.println(
                        "-------------------------------------------------------------------------------------------");
    
                StringBuilder respHeaderDatas = new StringBuilder();
    
                for (Header header : response.getAllHeaders()) {
    
                    respHeaderDatas.append(header.toString()).append("\r\n");
    
                }
    
                @SuppressWarnings("unused")
                String respStatusLine = response.getStatusLine().toString(); // HTTP应答状态行信息
    
                
                @SuppressWarnings("unused")
                String respHeaderMsg = respHeaderDatas.toString().trim(); // HTTP应答报文头信息
    
                @SuppressWarnings("unused")
                String respBodyMsg = responseContent; // HTTP应答报文体信息
    
    //          System.out.println("HTTP应答完整报文=[" + respStatusLine + "\r\n" + respHeaderMsg + "\r\n\r\n" + respBodyMsg + "]");
    //
    //          System.out.println(
    //                  "-------------------------------------------------------------------------------------------");
    
            } catch (ConnectTimeoutException cte) {
                cte.printStackTrace();
                // LogUtil.getLogger().error("请求通信[" + reqURL + "]时连接超时,堆栈轨迹如下",
                // cte);
    
            } catch (SocketTimeoutException ste) {
                ste.printStackTrace();
                // LogUtil.getLogger().error("请求通信[" + reqURL + "]时读取超时,堆栈轨迹如下",ste);
    
            } catch (Exception e) {
                e.printStackTrace();
                // LogUtil.getLogger().error("请求通信[" + reqURL + "]时偶遇异常,堆栈轨迹如下", e);
    
            } finally {
    
                httpClient.getConnectionManager().shutdown();
    
            }
    
            return responseContent;
    
        }
    }
    
    
    

    4、结束语

    本文介绍到此结束,如果读者喜欢,您的喜欢与关注就是对我最大的支持,后续将写几篇Shiro的文章,刚刚做过的项目,希望大家喜欢,如果又不正确的地方,希望读者可以留言给我,我会及时更改!

    相关文章

      网友评论

        本文标题:Java开发-Http与Https请求的工具类

        本文链接:https://www.haomeiwen.com/subject/acxshxtx.html