美文网首页Java服务端面试
HttpURLConnection 使用总结

HttpURLConnection 使用总结

作者: 十二书 | 来源:发表于2017-11-28 16:20 被阅读0次

    要使用 HttpURLConnection,最好对一些基础概念有所认识,比如 TCP/IP 协议,HTTP 报文, Socket 等。
    先谈一些我的认识,有可能不完全正确:

    • Socket 应该是 TCP 协议层的概念,如果要使用 Socket 直接通信,需要使用远程地址和端口号。其中,端口号根据具体的协议而不同,比如 HTTP 协议默认使用的端口号为 80/tcp。
    • HttpURLConnection 是在底层连接上的一个请求,最终也是通过 Socket 连接网络,所谓的 underlaying Socket。本文结尾我也会附上相关帖子连接。但是使用 HttpURLConnection 不需要我们专门去处理远程地址和端口号。
    • HttpURLConnection 只是一个抽象类,只能通过 url.openConection() 方法创建具体的实例。严格来说,openConection() 方法返回的是 URLConnection 的子类。根据 url 对象的不同,如可能不是 http:// 开头的,那么 openConection() 返回的可能就不是 HttpURLConnection。
    • HttpURLConnection 的 connect() 和 disconnect() 方法有必要特别强调一下,我会在下文使用到的地方详细说明。

    我在测试 HttpURLConnection 的时候,是分别使用 HTTP 的 GET 和 POST 方法发送消息到 http://ip.taobao.com//service/getIpInfo.php 查询 IP 地址归属地。http://ip.taobao.com/instructions.php 是 GET 方法接口说明。

    下面来具体说一下 HttpURLConnection 的使用步骤。

    1. 获得 HttpURLConnection 对象

      // 如果使用 POST 方法
      URL url = new 
      URL("http://ip.taobao.com//service/getIpInfo.php");
      
      // 如果打算使用 GET 方法
      //URL url = new URL("http://ip.taobao.com/service/getIpInfo.php?ip=xxx.xxx.xxx.xxx");
      
      HttpURLConnection connection = (HttpURLConnection) url.openConnection();
      
    2. 设置请求属性
      在连接到远程资源(可以简单理解为远端服务器,但是这么说不准确)之前,可以设置一些 HttpURLConnection 的属性。

      // 设置连接超时时间
      connection.setConnectTimeOut(15000);
      
      // 设置读取超时时间
      connection.setReadTimeOut(15000);
      
      // 设置请求参数,即具体的 HTTP 方法
      connection.setRequestMethod("POST");
      
      // 添加 HTTP HEAD 中的一些参数,可参考《Java 核心技术 卷II》
      connection.setRequestProperty("Connection", "Keep-Alive");
      
      // 设置是否向 httpUrlConnection 输出,
      // 对于post请求,参数要放在 http 正文内,因此需要设为true。
      // 默认情况下是false;
      connection.setDoOutput(true);
      
      // 设置是否从 httpUrlConnection 读入,默认情况下是true;
      connection.setDoInput(true);
      

      这些属性的设置要在 connect() 之前完成。如果对 HTTP 包信息的结构有很好的理解,有助于理解这些方法。
      setDoOutput() 方法是为了下面 getOutputStream();
      setDoInput() 方法是为了下面 getInputStream()。
      按照我在手机上测试,getOutputStream 和 getInputStream 内部都会隐式的调用 connect()。不过这只是我手机上的环境,严谨的来讲,我觉得还是应该自己显示的调用 conect()。(多次调用 connect(),后面的调用自动忽略)

    3. 调用 connect() 连接远程资源

      connection.connect();
      

      这会与服务器建立 Socket 连接,而连接以后,连接属性就不可以再修改;但是可以查询服务器返回的头信息了(header information)。
      connect 成功手机上 logcat 会打印相关信息,包括目标 IP 地址。我是用魅族做的测试,其他品牌理论上也应该会打印。

    4. 利用 getOutputStream() 传输 POST 消息
      说明一下,POST 消息才需要写数据,GET 不需要。

      BufferedWriter writer = new BufferedWriter(new OutputStreamWriter(connection.getOutputStream(), "UTF-8"));
      writer.write("ip=xxx.xxx.xxx.xxx");
      writer.flush();
      writer.close();
      

      上面提到过,getOutputStream 会隐式的调用 connect()。
      这里要注意的,主要是 HTTP 传输的消息要使用 URL UTF-8 编码,英文字母、数字和部分符号保持不变,空格编码成'+'。其他字符编码成 "%XY" 形式的字节序列,特别是中文字符,不能直接传输。可以考虑使用
      URLEncoder.encode(string, "UTF-8") 方法。

    5. 查询服务器头信息
      理论上,connect() 以后就可以查询服务器返回的头信息了。并且,getOutputStream 里面会隐式调用 connect()。
      但是,查询服务器消息要在写完所有要传输的数据以后。
      如果 getResponseCode 或者 getResponseMessage 以后,是不能向 outputStream 写消息的,报错为:

      cannot write request body after response has been read

      这两个方法内部都调用了 getInputStream()。

      因为有资料说,getInputStream() 的时候才会真正把 outputStream 里面的消息发出去。想想,这么做是有道理的:这样就允许我们关闭 outputStream 后重新打开,并且补充数据。这么理解的话,getResponseCode 内部调用了 getInputStream,导致 outputStream 已经发送;而一个 HttpURLConnection 只能发送一个请求,所以就不能再向 outputStream 写数据,否则就等于传输了两个消息。
      我没有在手机上安装抓手机报文的工具,所以没有直接验证。
      实际使用时,肯定是先通过 outputStream 传输数据,然后查询服务器的返回信息,所以 outputStream 消息到底是什么时候发送出去的,我们不需要太关心。
      查询头信息的方法有一下几个:
      // 这两个方法结合,可以查询所有消息头字段
      public String getHeaderFieldKey (int n)
      public String getHeaderField(int n)

      // 返回一个包含消息头所有字段的标准 map 对象
      public Map<String,List<String>> getHeaderFields()

      // 为了方便使用,以下方法可以查询各标准字段
      public String getContentType()
      public int getContentLength()
      public String getContentEncoding()
      public long getDate()
      public long getExpiration()
      public long getLastModified()

    6. 利用 getInputStream() 访问资源数据

      使用 getInputStream() 方法获取一个输入流用以读取信息(这个输入流与 URL 类中的 openStream 方法所返回的流相同)。另一个方法 getContent 在实际操作中并不是很有用。由标准内容类型(比如 text/plain 和 image/gif)所返回的对象需要使用 com.sun 层次结构中的类来进行处理。也可以注册自己的内容处理器。
      ---《Java 核心技术 卷II》,CH3 网络,使用 URLConnection 获取信息

      private String convertStreamToString() {
          InputStream inputStream = connection.getInputStream();
          BufferedReader reader = new BufferedReader(new InputStreamReader(inputStream ));
          StringBuffer sb = new StringBuffer();
          String line = null;
          while ((line = reader.readLine()) != null) {
              sb.append(line + "\n");
          }
      
          String reponse = sb.toString();
          return reponse;
      }
      
    7. 关闭 HttpURLConnection
      本身要 HttpURLConnection 是很简单的,调用 connection.disconnect() 就可以了。
      这里是想说明一下,是否需要关闭,应该根据实际需要来。
      当 HttpURLConnection 是 "Connection: close " 模式,那么关闭 inputStream 后就会自动断开连接。
      当 HttpURLConnection 是 "Connection: Keep-Alive" 模式,那么关闭 inputStream 后,并不会断开底层的 Socket 连接。这样的好处,是当需要连接到同一服务器地址时,可以复用该 Socket。这时如果要求断开连接,就可以调用 connection.disconnect() 了。
      当然,HttpURLConnection 连接到底是不是 Keep-Alive 模式,除了 HttpURLConnection 请求设置为 Keep-Alive 外 (http 1.0中默认是关闭的,http 1.1中默认启用Keep-Alive),也需要服务器支持 Keep-Alive,才可以真正建立 Keep-Alive 连接。

      // 连接 和 断开连接 的 log,IP 地址为手机 IP
      I/System.out: [socket][/192.168.1.101:60330] connected
      I/System.out: close [socket][/192.168.1.101:60330]
      
    8. 补充一点
      在我测试http://ip.taobao.com//service/getIpInfo.php 的时候,服务器一直不能正常返回 IP 地址对应的信息。最后发现,是淘宝服务器故意不响应我们这样非浏览器发起的 IP 查询请求。所以我还设置了 HttpURLConnection 的如下属性,伪装成浏览器,当然,是在 connect() 之前。

      connection.setRequestProperty("user-agent",
                       "Mozilla/5.0 (Windows NT 6.3; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/51.0.2704.7 Safari/537.36");
      

      调试联网程序的时候,出错有时候很难说是哪里的问题,用抓包软件分析是很有必要的;检查服务器的 ResponseCode 也是有必要的。


    关于 HttpURLConnection 的学习,我觉得《Java 核心技术 卷II》写的不错。
    我也参考了《Android 进阶之光》和下面两个链接。

    JDK中的URLConnection参数详解

    详解HttpURLConnection

    关于 HTTP 的 GET 方法和 POST 方法,刚开始有些疑惑,也是看了《Java 核心技术 卷II》,以及下面两个链接。

    99%的人都理解错了HTTP中GET与POST的区别

    GET和POST有什么区别?及为什么网上的多数答案都是错的。

    工作中经常用到的话,有必要专门学习一下 HTTP 协议和报文。

    相关文章

      网友评论

        本文标题:HttpURLConnection 使用总结

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