HttpURLConnection 使用总结

要使用 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 协议和报文。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 204,921评论 6 478
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 87,635评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 151,393评论 0 338
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,836评论 1 277
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,833评论 5 368
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,685评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,043评论 3 399
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,694评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 42,671评论 1 300
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,670评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,779评论 1 332
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,424评论 4 321
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,027评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,984评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,214评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,108评论 2 351
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,517评论 2 343