对URLConnection、HttpURLConnection和HttpClient的使用详解

相信在开发过程中碰到关于涉及Http协议时,如何使用HttpClient还是使用HttpURLConnection会产生一定的困惑。我在网上也搜索了很多文章,来分析两者的区别。接下来我们就剖析一下这两个网络开源库,分别从背景、用法、相同点、区别这几点来入手分析。

一、背景

1、URLConnection

URLConnection是一个抽象类,表示指向URL指定资源的活动连接。URLConnection有两个不同但相关的用途:

  • URLConnection可以检查服务器发送的首部,并相应地做出响应。它可以设置客户端请求中使用的首部字段。最后URLConnection可以用POST、PUT和其他HTTP请求方法向服务器发回数据;

  • URLConnection类是Java的协议处理器机制的一部分;

2、HttpURLConnection

Sun公司提供的库,也是Java的标准类库java.net中的一员,但这个类什么都没封装,用起来很原始,若需要高级功能,则会显得不太方便,比如重访问的自定义,会话和cookie等一些高级功能。

3、HttpClient

Apache公司提供的库,提供高效的、最新的、功能丰富的支持HTTP协议工具包,支持HTTP协议最新的版本和建议,是个很不错的开源框架,封装了http的请求,参数,内容体,响应等,拥有众多API。

二、补充知识

1、TCP/IP、Socket、HTTP简要介绍

  • TCP/IP中文名为传输控制协议/因特网互联协议,又名网络通讯协议,是Internet最基本的协议、Internet国际互联网络的基础,由网络层的IP协议和传输层的TCP协议组成。

  • Socket是支持TCP/IP协议的网络通信基本操作单元,许多操作系统为应用程序提供了一套调用接口(API),方便开发者开发网络程序。注意,socket本身并不是协议,只是提供一个针对TCP或UDP的编程接口。

  • HTTP协议是一个web服务器和客户端通信的超文本传送协议,是建立在TCP协议上的一个应用层协议。HTTP连接最显著的特点是客户端发送的每次请求都需要服务器回送响应,在请求结束后,会主动释放连接。从建立连接到关闭连接的过程称为“一次连接”。

    • HTTP1.0
      客户端的每次请求都要求建立一次单独的连接,在处理完本次请求后,就自动释放连接。

    • HTTP1.1
      可以在一次连接中处理多个请求,并且多个请求可以重叠进行,不需要等待一个请求结束后再发送下一个请求。

2、GET请求与POST请求

HTTP协议是现在Internet上使用得最多、最重要的协议了,越来越多的Java应用程序需要直接通过HTTP协议来访问网络资源。

在介绍HttpURLConnection前,我们还是再来说一下URL请求最常用的两种方式:GET请求与POST请求。

  • GET请求
    1)GET请求的数据会附在URL之后(就是把数据放置在HTTP协议头中),以?分割URL和传输数据,参数之间以&相连,如:http://localhost:8080/test.do?name=test&password=123456。

    2)GET请求发送的参数如果数据是英文字母或数字,则按原样发送,如果是空格,则转换为+,如果是中文或其他字符,则直接把字符串用BASE64加密,得出如 %E4%BD%A0%E5%A5%BD 这类似的字符串,其中%XX中的XX为该符号以16进制表示的ASCII。

  • POST请求
    1)POST请求的参数不是放在URL字符串里面,而是放在HTTP请求的正文内,请求的参数被封装起来以流的形式发送给服务端。

    2)对于GET方式提交数据的大小,HTTP协议并没有硬性限制,但某些浏览器及服务器会对它进行限制,如IE对URL长度的限制是2083字节(2K+35)。理论上POST也没有限制,可传较大量的数据。

    3)POST的安全性要比GET的安全性高。比如:通过GET提交数据,用户名和密码将明文出现在URL上,因为登录页面有可能被浏览器缓存,如果其他人查看浏览器的历史纪录,那么别人就可以拿到你的账号和密码了,除此之外,使用GET提交数据还可能会造成Cross-site request forgery(CSRF,跨站请求伪造)攻击。

一般来说,Get是向服务器索取数据的一种请求,而Post是向服务器提交数据的一种请求。

三、用法

这两种方式都支持HTTPS协议,以流的形式进行上传和下载,配置超时时间,IPV6,连接池等功能。

1、URLConnection使用详解

  • 构造一个URL对象;
  • 调用这个URL对象的openConnection()获取一个对应该URL的URLConnection对象;
  • 配置这个URLConnection;
  • 读取首部字段;
  • 获得输入流并读取数据;
  • 获得输出流并写入数据;
  • 关闭连接;

2、HttpURLConnection使用详解

在JDK的java.net包中已经提供了访问HTTP协议的基本功能的类:HttpURLConnection。

HttpURLConnection是Java的标准类,它继承自URLConnection,可用于向指定网站发送GET请求、POST请求。它在URLConnection的基础上提供了如下便捷的方法:

int getResponseCode(); // 获取服务器的响应代码。
String getResponseMessage(); // 获取服务器的响应消息。
String getResponseMethod(); // 获取发送请求的方法。
void setRequestMethod(String method); // 设置发送请求的方法。

2.1 创建HttpURLConnection对象

// 1.创建URL对象,得到访问地址的urlFactory
URL urlFactory = new URL(httpModel.getUrl());
// 2.得到网络访问对象java.net.HttpURLConnection
HttpURLConnection httpURLConnection = (HttpURLConnection) urlFactory.openConnection();
  • 这个创建方法大家应该都很熟悉了,可能会认为下文中使用的HttpURLConnection的API的实现就在HttpURLConnection中了。这里要注意一下,其实这个HttpURLConnection是一个抽象类。

  • 也就是说里面很多API函数都不在这儿实现的,那在哪实现呢。翻开java源码,可以看到,URL.openConnection()的实现是在java.net.URL类中。

//java.net.URL类里面的openConnection方法:  
public URLConnection openConnection(Proxy proxy){return handler.openConnection(this, proxy); 
}
  • 这里的handler又是什么呢,跟进去,发现Handler是sun.net.www.protocol.http.Handler这个java类,继承java.net.URLStreamHandler类,是用来处理http连接请求响应的。 继续跟踪代码
//Handler的方法
protected java.net.URLConnection openConnection(URL u, Proxy p) throws IOException {  
	return new HttpURLConnection(u, p, this);
}

2.2 HttpURLConnection参数设置

最终发现只是简单的生成了HttpURLConnection对象,其实最重要的HttpURLConnection就在这里了,这个是sun.net.www.protocl.http.HttpURLConnection类的对象,继承java.net.HttpURLConnection。也就是说我们之后所用的API实现都在sun.net.www.protocl.http.HttpURLConnection这个类里面。所以大家想要看HttpURLConnection的源码实现的话,需要到这个类中去查看。

好了,说了这么多,下面还是介绍HttpURLConnection常用的API的使用吧。

  • 1)设置是否向httpUrlConnection输出,默认情况下是false。使用httpUrlConnection.getOutputStream(),把内容输出到远程服务器上。
//对于post请求,参数要放在http正文内,因此需要设为true。
httpUrlConnection.setDoOutput(true);
  • 2)设置是否从httpUrlConnection读入,默认情况下是true。使用httpUrlConnection.getInputStream(),从远程服务器上得到响应的内容。
httpUrlConnection.setDoInput(true);
  • 3)是否使用缓存。
httpUrlConnection.setUseCaches(false);
  • 4)设定传送的内容类型是可序列化的java对象 (如果不设此项,在传送序列化对象时,当WEB服务默认的不是这种类型时可能抛java.io.EOFException)。
//设置请求格式JSON,也可以设定XML格式的
httpUrlConnection.setRequestProperty("Content-type", "application/x-java-serialized-object");
httpURLConnection.setRequestProperty("Content-Type", "application/json;charset=UTF-8");
httpURLConnection.setRequestProperty("Content-Type", "application/x-www-form-urlencoded");
  • 5)设置此 HttpURLConnection 实例是否应该自动执行 HTTP 重定向
httpURLConnection.setInstanceFollowRedirects(true);
  • 6)设置连接主机服务器超时时间
httpURLConnection.setConnectTimeout(3000);
  • 7)设置读取主机服务器返回数据超时时间
httpURLConnection.setReadTimeout(30000);
  • 8)设置连接状态:长连接
httpURLConnection.setRequestProperty("Connection", "Keep-Alive");
  • 9)模拟浏览器
httpURLConnection.setRequestProperty("User-Agent", "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_14_2) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/71.0.3578.98 Safari/537.36");
  • 10)设置接收数据的格式:JSON格式数据
httpURLConnection.setRequestProperty("Accept", "application/json");
  • 11)设定请求的方法为”POST”,默认是GET 。
httpUrlConnection.setRequestMethod("POST");

2.3 HttpURLConnection连接

// 4.建立连接,也可以不用显式的调用connect,使用下面getOutputStream()会隐式的调用connect()
httpURLConnection.connect();

这会与服务器建立 Socket 连接,而连接以后,连接属性就不可以再修改;但是可以查询服务器返 回的头信息了(header information)。

2.4 HttpURLConnection写数据与发送数据

OutputStream outputStream = httpURLConnection.getOutputStream();
// HTTP传输的消息要使用URL UTF-8编码,英文字母、数字和部分符号保持不变,空格编码成'+'。其他字符编码成 "%XY" 形式的字节序列,特别是中文字符,不能直接传输。可以考虑使用URLEncoder.encode(string, "UTF-8") 方法
// 现在通过输出流对象构建对象输出流对象,以实现输出可序列化的对象
BufferedWriter bufferedWriter = new BufferedWriter(new OutputStreamWriter(outputStream, "UTF-8"));
// 向对象输出流写出数据,这些数据将存到内存缓冲区中
bufferedWriter.write(httpModel.getRequest());
// 刷新对象输出流,将任何字节都写入潜在的流中(此处为bufferedWriter)
bufferedWriter.flush();
// 关闭流对象。此时,不能再向对象输出流写入任何数据,先前写入的数据存在于内存缓冲区中, 在调用下边的getInputStream()函数时才把准备好的http请求正式发送到服务器
bufferedWriter.close();
// 调用HttpURLConnection连接对象的getInputStream()函数,将内存缓冲区中封装好的完整的HTTP请求电文发送到服务端。<注意>,实际发送请求的代码段就在这里
InputStream inputStream = httpURLConnection.getInputStream();

2.5 HttpURLConnection注意事项

  • HttpURLConnection对象不能直接构造,需要通过URL类中的openConnection()方法来获得。

  • HttpURLConnection的connect()函数,实际上只是建立了一个与服务器的tcp连接,并没有实际发送http请求。HTTP请求实际上直到我们获取服务器响应数据(如调用getInputStream()、getResponseCode()等方法)时才正式发送出去。所以对outputStream的写操作,必须要在inputStream的读操作之前。

  • 在用POST方式发送URL请求时,URL请求参数的设定顺序是重中之重,对HttpURLConnection对象的配置都需要在connect()方法执行之前完成。

  • HttpURLConnection是基于HTTP协议的,其底层通过socket通信实现。如果不设置超时(timeout),在网络异常的情况下,可能会导致程序僵死而不继续往下执行。

  • HTTP正文的内容是通过OutputStream流写入的, 向流中写入的数据不会立即发送到网络,而是存在于内存缓冲区中,待流关闭时,根据写入的内容生成HTTP正文。

  • 调用getInputStream()方法时,返回一个输入流,用于从中读取服务器对于HTTP请求的返回信息。

  • 我们可以使用HttpURLConnection.connect()方法手动的发送一个HTTP请求,但是如果要获取HTTP响应的时候,请求就会自动的发起,比如我们使用HttpURLConnection.getInputStream()方法的时候,所以完全没有必要调用connect()方法。

2.6 HttpURLConnection使用实例

  • 1)新建Http请求模板
/**
 * Http请求模板
 */
@Data
public class HttpModel {
    private String url;
    private String method;
    private HeadModel headModel;
    private String request;
    private String status;
    private String response;
}
  • 2)get和post应用实例
public class HttpUtil {
    private final static Logger log = LogUtil.getLogger(HttpUtil.class);

    /**
     * http post请求
     *
     * @param url
     * @param headModel
     * @param request
     * @return
     */
    public static String getHttpPost(String url, HeadModel headModel, String request) {
        HttpModel httpModel = setHttpModel(url, headModel, request, "POST");
        return getResponse(httpModel);
    }

    /**
     * http get请求
     *
     * @param url
     * @param headModel
     * @param request
     * @return
     */
    public static String getHttpGet(String url, HeadModel headModel, String request) {
        String reqUrl = url + "?" + request;
        HttpModel httpModel = setHttpModel(reqUrl, headModel, "", "GET");
        return getResponse(httpModel);
    }

    /**
     * 设置http请求模板
     *
     * @param url
     * @param headModel
     * @param request
     * @param method
     * @return
     */
    private static HttpModel setHttpModel(String url, HeadModel headModel, String request, String method) {
        HttpModel httpModel = new HttpModel();
        if (url != null) {
            httpModel.setUrl(url);
        }
        if (headModel != null) {
            httpModel.setHeadModel(headModel);
        }
        if (request != null) {
            httpModel.setRequest(request);
        }
        if (method != null) {
            httpModel.setMethod(method);
        }
        return httpModel;
    }

    /**
     * 获取响应
     *
     * @param httpModel
     * @return
     */
    private static String getResponse(HttpModel httpModel) {
        String response = "";
        try {
            // 1.创建URL对象,得到访问地址的urlFactory
            URL urlFactory = new URL(httpModel.getUrl());
            // 2.得到网络访问对象java.net.HttpURLConnection
            HttpURLConnection httpURLConnection = (HttpURLConnection) urlFactory.openConnection();
            // 3. 设置请求参数(过期时间,输入、输出流、访问方式),以流的形式进行连接

            // 设置是否向HttpURLConnection输出,默认情况下是false
            httpURLConnection.setDoOutput(false);
            if ("POST".equals(httpModel.getMethod())) {
                // 对于post请求,参数要放在http正文内,因此需要设为true
                httpURLConnection.setDoOutput(true);
            }

            // 设置是否从httpUrlConnection读入,默认情况下是true
            httpURLConnection.setDoInput(true);

            // 设置是否使用缓存,Post请求不能使用缓存 
            // httpURLConnection.setUseCaches(false);

            // 设置此 HttpURLConnection 实例是否应该自动执行 HTTP 重定向
            // httpURLConnection.setInstanceFollowRedirects(true);

            // 设置连接主机服务器超时时间
            httpURLConnection.setConnectTimeout(3000);

            // 设置读取主机服务器返回数据超时时间
            httpURLConnection.setReadTimeout(30000);

            // 设置连接状态:长连接
            httpURLConnection.setRequestProperty("Connection", "Keep-Alive");

            //设置请求数据类型
            if (JsonUtil.checkJson(httpModel.getRequest())) {
                httpURLConnection.setRequestProperty("Content-Type", "application/json;charset=UTF-8");
            } else {
                httpURLConnection.setRequestProperty("Content-Type", "application/x-www-form-urlencoded");
            }

            //模拟浏览器
            httpURLConnection.setRequestProperty("User-Agent", "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_14_2) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/71.0.3578.98 Safari/537.36");

            // 设置接收数据的格式:json格式数据
            httpURLConnection.setRequestProperty("Accept", "application/json");

            // 设置请求方式,默认是GET 
           httpURLConnection.setRequestMethod(httpModel.getMethod());

            if (httpModel.getHeadModel() != null) {
                for (String key : httpModel.getHeadModel().getHeadModel().keySet()) {
                    httpURLConnection.setRequestProperty(key, httpModel.getHeadModel().getHeadModel().get(key));
                }
            }

            // 4.建立连接,也可以不用显式的调用connect,使用下面的getOutputStream()会隐式的调用connect()
            httpURLConnection.connect();

            if ("POST".equals(httpModel.getMethod())) {
                OutputStream outputStream = httpURLConnection.getOutputStream();
                // 方式一
                // HTTP传输的消息要使用URL UTF-8编码,英文字母、数字和部分符号保持不变,空格编码成'+'。其他字符编码成 "%XY" 形式的字节序列,特别是中文字符,不能直接传输。可以考虑使用URLEncoder.encode(string, "UTF-8") 方法
                // 现在通过输出流对象构建对象输出流对象,以实现输出可序列化的对象
                BufferedWriter bufferedWriter = new BufferedWriter(new OutputStreamWriter(outputStream, "UTF-8"));
                // 向对象输出流写出数据,这些数据将存到内存缓冲区中
                bufferedWriter.write(httpModel.getRequest());
                // 刷新对象输出流,将任何字节都写入潜在的流中(此处为bufferedWriter)
                bufferedWriter.flush();
                // 关闭流对象。此时,不能再向对象输出流写入任何数据,先前写入的数据存在于内存缓冲区中, 在调用下边的getInputStream()函数时才把准备好的http请求正式发送到服务器
                bufferedWriter.close();

//                // 方式二
//                DataOutputStream dos = new DataOutputStream(outputStream);
//                dos.write(httpModel.getRequest().getBytes());
//                dos.flush();
//                dos.close();
//
//                // 方式三
//                OutputStreamWriter osw = new OutputStreamWriter(outputStream);
//                osw.write(httpModel.getRequest());
//                osw.flush();
//                osw.close();
            }

            /* HttpURLConnection的connect()函数,实际上只是建立了一个与服务器的tcp连接,并没有实际发送http请求。无论是post还是get请求,http请求实际上直到HttpURLConnection的getInputStream()这个函数里面才正式发送出去。所以对outputStream的写操作,必须要在inputStream的读操作之前
             */

            // 5.读取响应,得到响应状态码的返回值responseCode,如果返回值正常,数据在网络中是以流的形式得到服务端返回的数据
            if (httpURLConnection.getResponseCode() == HttpURLConnection.HTTP_OK) {
                response = convertStreamToString(httpURLConnection);

                httpModel.setStatus(String.valueOf(httpURLConnection.getResponseCode()));
                httpModel.setResponse(response);

                log.info("获取http响应成功");
            } else {
                log.info(httpURLConnection.getResponseCode());
                log.info("获取http响应失败" + httpURLConnection.getResponseMessage());
            }
            // 6. 断开连接,释放资源
            httpURLConnection.disconnect();
        } catch (Exception e) {
            log.error("发送" + httpModel.getMethod() + "请求异常!请查看执行日志记录:" + e);
            return "发送" + httpModel.getMethod() + "请求异常!请查看执行日志记录:" + e;
        } finally {

        }
        return response;
    }

    /**
     * 读取响应
     *
     * @param httpURLConnection
     * @return
     */
    private static String convertStreamToString(HttpURLConnection httpURLConnection) {
        String response = "";
        try {
            // 从流中读取响应信息
            // 调用HttpURLConnection连接对象的getInputStream()函数,将内存缓冲区中封装好的完整的HTTP请求电文发送到服务端。<注意>,实际发送请求的代码段就在这里
            InputStream inputStream = httpURLConnection.getInputStream();
            BufferedReader reader = new BufferedReader(new InputStreamReader(inputStream));

            String line;
            StringBuilder stringBuilder = new StringBuilder();
            // 循环从流中读取
            while ((line = reader.readLine()) != null) {
                stringBuilder.append(line + "\n");
            }

            response = stringBuilder.toString();

            //关闭输入流
            if (reader != null) {
                reader.close();
            }
        } catch (IOException e) {
            e.printStackTrace();
        }
        return response;
    }
}

3、HttpClient使用详解

四、区别

1、功能用法对比

  • 从功能上对比,HttpClient库要丰富很多,提供了很多工具,封装了http的请求头,参数,内容体,响应,还有一些高级功能,代理、COOKIE、鉴权、压缩、连接池的处理。

  • HttpClient高级功能代码写起来比较复杂,对开发人员的要求会高一些,而HttpURLConnection对大部分工作进行了包装,屏蔽了不需要的细节,适合开发人员直接调用。

  • 另外,HttpURLConnection在2.3版本增加了一些HTTPS方面的改进,4.0版本增加一些响应的缓存。

2、性能对比

  • HttpUrlConnection直接支持GZIP压缩;HttpClient也支持,但要自己写代码处理。

  • HttpUrlConnection直接支持系统级连接池,即打开的连接不会直接关闭,在一段时间内所有程序可共用;HttpClient当然也能做到,但毕竟不如官方直接系统底层支持好。

  • HttpUrlConnection直接在系统层面做了缓存策略处理(4.0版本以上),加快了重复请求的速度。

  • 这篇文章对两者的速度做了一个对比,做法是两个类都使用默认的方法去请求百度的网页内容,测试结果是使用httpurlconnection耗时47ms,使用httpclient耗时641ms。httpURLConnection在速度有比较明显的优势,当然这跟压缩内容和缓存都有直接关系。

3、未来发展

  • HttpClient 适用于 web browsers, 他们是可扩展的,并且拥有大量的稳定APIs。但是,在不破坏其兼容性的前提下很难对如此多的APIs做修改。因此,Android 团队对修改优化Apache HTTP Client表现的并不积极。

  • HttpURLConnect 是一个通用的、适合大多数应用的轻量级组件。这个类起步比较晚,很容易在主要API上做稳步的改善。但是HttpURLConnection在在Android 2.2及以下版本上存在一些令人厌烦的bug,尤其是在读取 InputStream时调用 close()方法,就有可能会导致连接池失效了。

  • Android团队未来的工作会将更多的时间放在优化HttpURLConnection上,它的API简单,体积较小,因而非常适用于Android项目。压缩和缓存机制可以有效地减少网络访问的流量,在提升速度和省电方面也起到了较大的作用。

4、选用建议

  • 如果一个Android应用需要向指定页面发送请求,但该页面并不是一个简单的页面,只有当用户已经登录,而且登录用户的用户名有效时才可访问该页面。如果使用HttpURLConnection来访问这个被保护的页面,那么需要处理的细节就太复杂了。这种情况建议使用HttpClient。

  • Android2.3及以上版本建议选用HttpURLConnection,2.2及以下版本建议选用HttpClient。新的应用都建议使用HttpURLConnection。

五、引用资料

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值