Android 中 HttpURLConnection 使用详解

Android 中 HttpURLConnection 使用详解

认识Http协议

Android中发送http网络请求是很常见的,要有GET请求和POST请求。一个完整的http请求需要经历两个过程:客户端发送请求到服务器,然后服务器将结果返回给客户端,如下图所示:


  • 客户端->服务器

客户端向服务器发送请求主要包含以下信息:请求的Url地址、请求头以及可选的请求体,打开百度首页,客户端向服务器发送的信息如下所示:

1.请求URL(Request URL)

上图中的Request URL就是请求的Url地址,即 baidu.com ,该Url没有附加其他的参数。其实可以通过?和&符向URL地址后面追加一系列的键值对参数,比如地址 baidu.com/s? ,该Url包含两个键值对,ie=utf-8,以及wd=Android,ie和wd是key,utf-8和Android分别是其对应的value,服务端可以获取ie和wd所对应的value的值。由此我们可以看出,Url可以携带额外的数据信息。一般情况下,URL的长度不能超过2048个字符,即2KB,超过此限制的话服务器可能就不识别。

2.请求头(Request Headers)

上图中Request Headers部分就是请求头,请求头其实也是一些键值对,不过这些键值通常都是W3C定义了的一些标准的Http请求头的名称,请求头包含了客户端想告诉服务端的一些元数据信息,注意是元数据,而不是数据,比如请求头User-Agent会告诉服务器这条请求来自于什么浏览器,再比如请求头Accept-Encoding会告诉服务器客户端支持的压缩格式。除了这些标准的请求头,我们还可以添加自定义的请求头。

3.请求体(Request Body)

之前我们提到,URL的最大长度就是2048个字符,如果我们发送的数据很大,超过了2KB怎么办?我们可以将很大的数据放到请求体中,GET请求不支持请求体,只有POST请求才能设置请求体。请求体中可以放置任意的字节流,从而可以很方便地发送任意格式的数据,服务端只需要读取该输入流即可。

  • 服务器->客户端

服务器接收到客户端发来的请求后,会进行相应的处理,并向客户端输出信息,输出的信息包括响应头和响应体。

1.响应头(Response Headers)

响应头也是一些键值对,如下所示:

响应头包含了服务器想要告诉客户端的一些元数据信息,注意不是数据,是元数据,比如通过响应头Content-Encoding告诉客户端服务器所采用的压缩格式,响应头Content-Type告诉客户端响应体是什么格式的数据,再比如服务端可以通过多个Set-Cookie响应头向客户端写入多条Cookie信息,等等。刚刚提到的几个请求头都是W3C规定的标准的请求头名称,我们也可以在服务端向客户端写入自定义的响应头。

2.响应体(Response Body)

响应体是服务端向客户端传输的实际的数据信息,本质就是一堆字节流,可以表示文本,也可以表示图片或者其他格式的信息,如下所示:

GETvsPOST

Http协议支持的操作有GET、POST、HEAD、PUT、TRACE、OPTIONS、DELETE,其中最最常用的还是GET和POST操作,下面我们看一下GET和POST的区别。

GET:

  • GET请求可以被缓存。
  • 我们之前提到,当发送键值对信息时,可以在URL上面直接追加键值对参数。当用GET请求发送键值对时,键值对会随着URL一起发送的。
  • 由于GET请求发送的键值对时随着URL一起发送的,所以一旦该URL被黑客截获,那么就能看到发送的键值对信息,所以GET请求的安全性很低,不能用GET请求发送敏感的信息(比如用户名密码)。
  • 由于URL不能超过2048个字符,所以GET请求发送数据是有长度限制的。
  • 由于GET请求较低的安全性,我们不应该用GET请求去执行增加、删除、修改等的操作,应该只用它获取数据。

POST:

  • POST请求从不会被缓存。
  • POST请求的URL中追加键值对参数,不过这些键值对参数不是随着URL发送的,而是被放入到请求体中发送的,这样安全性稍微好一些。
  • 应该用POST请求发送敏感信息,而不是用GET。
  • 由于可以在请求体中发送任意的数据,所以理论上POST请求不存在发送数据大小的限制。
  • 当执行增减、删除、修改等操作时,应该使用POST请求,而不应该使用GET请求。

HttpURLConnection vs DefaultHttpClient

在Android API Level 9(Android 2.2)之前之能使用DefaultHttpClient类发送http请求。DefaultHttpClient是Apache用于发送http请求的客户端,其提供了强大的API支持,而且基本没有什么bug,但是由于其太过复杂,Android团队在保持向后兼容的情况下,很难对DefaultHttpClient进行增强。为此,Android团队从Android API Level 9开始自己实现了一个发送http请求的客户端类——–HttpURLConnection。

相比于DefaultHttpClient,HttpURLConnection比较轻量级,虽然功能没有DefaultHttpClient那么强大,但是能够满足大部分的需求,所以Android推荐使用HttpURLConnection代替DefaultHttpClient,并不强制使用HttpURLConnection。

但从Android API Level 23(Android 6.0)开始,不能再在Android中使用DefaultHttpClient,强制使用HttpURLConnection。


Demo介绍

为了演示HttpURLConnection的常见用法,我做了一个App,界面如下所示:

主界面MainActivity有四个按钮,分别表示用GET发送请求、用POST发送键值对数据、用POST发送XML数据以及用POST发送JSON数据,点击对应的按钮会启动NetworkActivity并执行相应的操作。

NetworkActivity的源码如下所示,此处先贴出代码,后面会详细说明。

package com.ispring.httpurlconnection;
import android.content.Intent;
import android.content.res.AssetManager;
import android.os.AsyncTask;
import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
import android.widget.TextView;
import java.io.BufferedInputStream;
import java.io.BufferedOutputStream;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.io.UnsupportedEncodingException;
import java.net.HttpURLConnection;
import java.net.MalformedURLException;
import java.net.URL;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
public class NetworkActivity extends AppCompatActivity {
    private NetworkAsyncTask networkAsyncTask = new NetworkAsyncTask();
    private TextView tvUrl = null;
    private TextView tvRequestHeader = null;
    private TextView tvRequestBody = null;
    private TextView tvResponseHeader = null;
    private TextView tvResponseBody = null;
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_network);
        tvUrl = (TextView) findViewById(R.id.tvUrl);
        tvRequestHeader = (TextView) findViewById(R.id.tvRequestHeader);
        tvRequestBody = (TextView) findViewById(R.id.tvRequestBody);
        tvResponseHeader = (TextView) findViewById(R.id.tvResponseHeader);
        tvResponseBody = (TextView) findViewById(R.id.tvResponseBody);
        Intent intent = getIntent();
        if (intent != null && intent.getExtras() != null) {
            String networkAction = intent.getStringExtra("action");
            networkAsyncTask.execute(networkAction);
    //用于进行网络请求的AsyncTask
    class NetworkAsyncTask extends AsyncTask<String, Integer, Map<String, Object>> {
        //NETWORK_GET表示发送GET请求
        public static final String NETWORK_GET = "NETWORK_GET";
        //NETWORK_POST_KEY_VALUE表示用POST发送键值对数据
        public static final String NETWORK_POST_KEY_VALUE = "NETWORK_POST_KEY_VALUE";
        //NETWORK_POST_XML表示用POST发送XML数据
        public static final String NETWORK_POST_XML = "NETWORK_POST_XML";
        //NETWORK_POST_JSON表示用POST发送JSON数据
        public static final String NETWORK_POST_JSON = "NETWORK_POST_JSON";
        @Override
        protected MapdoInBackground(String... params) {
            Map result = new HashMap<>();
            URL url = null;//请求的URL地址
            HttpURLConnection conn = null;
            String requestHeader = null;//请求头
            byte[] requestBody = null;//请求体
            String responseHeader = null;//响应头
            byte[] responseBody = null;//响应体
            String action = params[0];//http请求的操作类型
            try {
                if (NETWORK_GET.equals(action)) {
                    //发送GET请求
                    url = new URL("http://192.168.31.200:8080/HttpServer/MyServlet?name=孙群&age=27");
                    conn = (HttpURLConnection) url.openConnection();
                    //HttpURLConnection默认就是用GET发送请求,所以下面的setRequestMethod可以省略
                    conn.setRequestMethod("GET");
                    //HttpURLConnection默认也支持从服务端读取结果流,所以下面的setDoInput也可以省略
                    conn.setDoInput(true);
                    //用setRequestProperty方法设置一个自定义的请求头:action,由于后端判断
                    conn.setRequestProperty("action", NETWORK_GET);
                    //禁用网络缓存
                    conn.setUseCaches(false);
                    //获取请求头
                    requestHeader = getReqeustHeader(conn);
                    //在对各种参数配置完成后,通过调用connect方法建立TCP连接,但是并未真正获取数据
                    //conn.connect()方法不必显式调用,当调用conn.getInputStream()方法时内部也会自动调用connect方法
                    conn.connect();
                    //调用getInputStream方法后,服务端才会收到请求,并阻塞式地接收服务端返回的数据
                    InputStream is = conn.getInputStream();
                    //将InputStream转换成byte数组,getBytesByInputStream会关闭输入流
                    responseBody = getBytesByInputStream(is);
                    //获取响应头
                    responseHeader = getResponseHeader(conn);
                } else if (NETWORK_POST_KEY_VALUE.equals(action)) {
                    //用POST发送键值对数据
                    url = new URL("http://192.168.31.200:8080/HttpServer/MyServlet");
                    conn = (HttpURLConnection) url.openConnection();
                    //通过setRequestMethod将conn设置成POST方法
                    conn.setRequestMethod("POST");
                    //调用conn.setDoOutput()方法以显式开启请求体
                    conn.setDoOutput(true);
                    //用setRequestProperty方法设置一个自定义的请求头:action,由于后端判断
                    conn.setRequestProperty("action", NETWORK_POST_KEY_VALUE);
                    //获取请求头
                    requestHeader = getReqeustHeader(conn);
                    //获取conn的输出流
                    OutputStream os = conn.getOutputStream();
                    //获取两个键值对name=孙群和age=27的字节数组,将该字节数组作为请求体
                    requestBody = new String("name=孙群&age=27").getBytes("UTF-8");
                    //将请求体写入到conn的输出流中
                    os.write(requestBody);
                    //记得调用输出流的flush方法
                    os.flush();
                    //关闭输出流
                    os.close();
                    //当调用getInputStream方法时才真正将请求体数据上传至服务器
                    InputStream is = conn.getInputStream();
                    //获得响应体的字节数组
                    responseBody = getBytesByInputStream(is);
                    //获得响应头
                    responseHeader = getResponseHeader(conn);
                } else if (NETWORK_POST_XML.equals(action)) {
                    //用POST发送XML数据
                    url = new URL("http://192.168.31.200:8080/HttpServer/MyServlet");
                    conn = (HttpURLConnection) url.openConnection();
                    //通过setRequestMethod将conn设置成POST方法
                    conn.setRequestMethod("POST");
                    //调用conn.setDoOutput()方法以显式开启请求体
                    conn.setDoOutput(true);
                    //用setRequestProperty方法设置一个自定义的请求头:action,由于后端判断
                    conn.setRequestProperty("action", NETWORK_POST_XML);
                    //获取请求头
                    requestHeader = getReqeustHeader(conn);
                    //获取conn的输出流
                    OutputStream os = conn.getOutputStream();
                    //读取assets目录下的person.xml文件,将其字节数组作为请求体
                    requestBody = getBytesFromAssets("person.xml");
                    //将请求体写入到conn的输出流中
                    os.write(requestBody);
                    //记得调用输出流的flush方法
                    os.flush();
                    //关闭输出流
                    os.close();
                    //当调用getInputStream方法时才真正将请求体数据上传至服务器
                    InputStream is = conn.getInputStream();
                    //获得响应体的字节数组
                    responseBody = getBytesByInputStream(is);
                    //获得响应头
                    responseHeader = getResponseHeader(conn);
                } else if (NETWORK_POST_JSON.equals(action)) {
                    //用POST发送JSON数据
                    url = new URL("http://192.168.31.200:8080/HttpServer/MyServlet");
                    conn = (HttpURLConnection) url.openConnection();
                    //通过setRequestMethod将conn设置成POST方法
                    conn.setRequestMethod("POST");
                    //调用conn.setDoOutput()方法以显式开启请求体
                    conn.setDoOutput(true);
                    //用setRequestProperty方法设置一个自定义的请求头:action,由于后端判断
                    conn.setRequestProperty("action", NETWORK_POST_JSON);
                    //获取请求头
                    requestHeader = getReqeustHeader(conn);
                    //获取conn的输出流
                    OutputStream os = conn.getOutputStream();
                    //读取assets目录下的person.json文件,将其字节数组作为请求体
                    requestBody = getBytesFromAssets("person.json");
                    //将请求体写入到conn的输出流中
                    os.write(requestBody);
                    //记得调用输出流的flush方法
                    os.flush();
                    //关闭输出流
                    os.close();
                    //当调用getInputStream方法时才真正将请求体数据上传至服务器
                    InputStream is = conn.getInputStream();
                    //获得响应体的字节数组
                    responseBody = getBytesByInputStream(is);
                    //获得响应头
                    responseHeader = getResponseHeader(conn);
            } catch (MalformedURLException e) {
                e.printStackTrace();
            } catch (IOException e) {
                e.printStackTrace();
            } finally {
                //最后将conn断开连接
                if (conn != null) {
                    conn.disconnect();
            result.put("url", url.toString());
            result.put("action", action);
            result.put("requestHeader", requestHeader);
            result.put("requestBody", requestBody);
            result.put("responseHeader", responseHeader);
            result.put("responseBody", responseBody);
            return result;
        @Override
        protected void onPostExecute(Map result) {
            super.onPostExecute(result);
            String url = (String)result.get("url");//请求的URL地址
            String action = (String) result.get("action");//http请求的操作类型
            String requestHeader = (String) result.get("requestHeader");//请求头
            byte[] requestBody = (byte[]) result.get("requestBody");//请求体
            String responseHeader = (String) result.get("responseHeader");//响应头
            byte[] responseBody = (byte[]) result.get("responseBody");//响应体
            //更新tvUrl,显示Url
            tvUrl.setText(url);
            //更新tvRequestHeader,显示请求头
            if (requestHeader != null) {
                tvRequestHeader.setText(requestHeader);
            //更新tvRequestBody,显示请求体
            if(requestBody != null){
                    String request = new String(requestBody, "UTF-8");
                    tvRequestBody.setText(request);
                }catch (UnsupportedEncodingException e){
                    e.printStackTrace();
            //更新tvResponseHeader,显示响应头
            if (responseHeader != null) {
                tvResponseHeader.setText(responseHeader);
            //更新tvResponseBody,显示响应体
            if (NETWORK_GET.equals(action)) {
                String response = getStringByBytes(responseBody);
                tvResponseBody.setText(response);
            } else if (NETWORK_POST_KEY_VALUE.equals(action)) {
                String response = getStringByBytes(responseBody);
                tvResponseBody.setText(response);
            } else if (NETWORK_POST_XML.equals(action)) {
                //将表示xml的字节数组进行解析
                String response = parseXmlResultByBytes(responseBody);
                tvResponseBody.setText(response);
            } else if (NETWORK_POST_JSON.equals(action)) {
                //将表示json的字节数组进行解析
                String response = parseJsonResultByBytes(responseBody);
                tvResponseBody.setText(response);
        //读取请求头
        private String getReqeustHeader(HttpURLConnection conn) {
            //https://github.com/square/okhttp/blob/master/okhttp-urlconnection/src/main/java/okhttp3/internal/huc/HttpURLConnectionImpl.java#L236
            Map> requestHeaderMap = conn.getRequestProperties();
            Iterator requestHeaderIterator = requestHeaderMap.keySet().iterator();
            StringBuilder sbRequestHeader = new StringBuilder();
            while (requestHeaderIterator.hasNext()) {
                String requestHeaderKey = requestHeaderIterator.next();
                String requestHeaderValue = conn.getRequestProperty(requestHeaderKey);
                sbRequestHeader.append(requestHeaderKey);
                sbRequestHeader.append(":");
                sbRequestHeader.append(requestHeaderValue);
                sbRequestHeader.append("\n");
            return sbRequestHeader.toString();
        //读取响应头
        private String getResponseHeader(HttpURLConnection conn) {
            Map> responseHeaderMap = conn.getHeaderFields();
            int size = responseHeaderMap.size();
            StringBuilder sbResponseHeader = new StringBuilder();
            for(int i = 0; i < size; i++){
                String responseHeaderKey = conn.getHeaderFieldKey(i);
                String responseHeaderValue = conn.getHeaderField(i);
                sbResponseHeader.append(responseHeaderKey);
                sbResponseHeader.append(":");
                sbResponseHeader.append(responseHeaderValue);
                sbResponseHeader.append("\n");
            return sbResponseHeader.toString();
        //根据字节数组构建UTF-8字符串
        private String getStringByBytes(byte[] bytes) {
            String str = "";
            try {
                str = new String(bytes, "UTF-8");
            } catch (UnsupportedEncodingException e) {
                e.printStackTrace();
            return str;
        //从InputStream中读取数据,转换成byte数组,最后关闭InputStream
        private byte[] getBytesByInputStream(InputStream is) {
            byte[] bytes = null;
            BufferedInputStream bis = new BufferedInputStream(is);
            ByteArrayOutputStream baos = new ByteArrayOutputStream();
            BufferedOutputStream bos = new BufferedOutputStream(baos);
            byte[] buffer = new byte[1024 * 8];
            int length = 0;
            try {
                while ((length = bis.read(buffer)) > 0) {
                    bos.write(buffer, 0, length);
                bos.flush();
                bytes = baos.toByteArray();
            } catch (IOException e) {
                e.printStackTrace();
            } finally {
                try {
                    bos.close();
                } catch (IOException e) {
                    e.printStackTrace();
                try {
                    bis.close();
                } catch (IOException e) {
                    e.printStackTrace();
            return bytes;
        //根据文件名,从asserts目录中读取文件的字节数组
        private byte[] getBytesFromAssets(String fileName){
            byte[] bytes = null;
            AssetManager assetManager = getAssets();
            InputStream is = null;
                is = assetManager.open(fileName);
                bytes = getBytesByInputStream(is);
            }catch (IOException e){
                e.printStackTrace();
            return bytes;
        //将表示xml的字节数组进行解析
        private String parseXmlResultByBytes(byte[] bytes) {
            InputStream is = new ByteArrayInputStream(bytes);
            StringBuilder sb = new StringBuilder();
            List persons = XmlParser.parse(is);
            for (Person person : persons) {
                sb.append(person.toString()).append("\n");
            return sb.toString();
        //将表示json的字节数组进行解析
        private String parseJsonResultByBytes(byte[] bytes){
            String jsonString = getStringByBytes(bytes);
            List persons = JsonParser.parse(jsonString);
            StringBuilder sb = new StringBuilder();
            for (Person person : persons) {
                sb.append(person.toString()).append("\n");
            return sb.toString();
}

这个App是用来发送http请求的客户端,除此之外,我还创建了一个JSP的WebProject作为服务端,用Servlet对客户端发来的请求进行处理,Servlet的代码如下所示:

import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.*;
import java.util.Enumeration;
@WebServlet(name = "MyServlet")
public class MyServlet extends HttpServlet {
    //GET请求
    private static final String NETWORK_GET = "NETWORK_GET";
    //用POST发送键值对
    private static final String NETWORK_POST_KEY_VALUE = "NETWORK_POST_KEY_VALUE";
    //用POST发送XML数据
    private static final String NETWORK_POST_XML = "NETWORK_POST_XML";
    //用POST发送JSON数据
    private static final String NETWORK_POST_JSON = "NETWORK_POST_JSON";
    protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        doGet(request, response);
    protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        String action = request.getHeader("action");
        //将输入与输出都设置为UTF-8编码
        request.setCharacterEncoding("UTF-8");
        response.setCharacterEncoding("UTF-8");
        response.setContentType("text/plain;charset=UTF-8");
        //response.setHeader("content-type","text/plain;charset=UTF-8");
        if(NETWORK_GET.equals(action) || NETWORK_POST_KEY_VALUE.equals(action)){
            //对于NETWORK_GET和NETWORK_POST_KEY_VALUE,遍历键值对,并将键值对重新写回到输出结果中
            Enumeration parameterNames = request.getParameterNames();
            PrintWriter writer = response.getWriter();
            while(parameterNames.hasMoreElements()){
                String name = parameterNames.nextElement();
                String value = request.getParameter(name);
                if(request.getMethod().toUpperCase().equals("GET")){
                    //GET请求需要进行编码转换,POST不需要
                    value = new String(value.getBytes("ISO-8859-1"), "UTF-8");
                writer.write(name + "=" + value + "\n");
            writer.flush();
            writer.close();
        }else if(NETWORK_POST_XML.equals(action) || NETWORK_POST_JSON.equals(action)){
            //对于NETWORK_POST_XML和NETWORK_POST_JSON,将请求体重新写入到响应体的输出流中
            //通过request.getInputStream()得到http请求的请求体
            BufferedInputStream bis  = new BufferedInputStream(request.getInputStream());
            //通过response.getOutputStream()得到http请求的响应体
            BufferedOutputStream bos = new BufferedOutputStream(response.getOutputStream());
            byte[] buffer = new byte[1024 * 8];
            int length = 0;
            while ( (length = bis.read(buffer)) > 0){
                bos.write(buffer, 0, length);
            bos.flush();
            bos.close();
            bis.close();
        }else{
            PrintWriter writer = response.getWriter();
            writer.write("非法的请求头: action");
            writer.flush();
            writer.close();
}

发送GET请求

由于网络请求耗时而且会阻塞当前线程,所以我们将发送http请求的操作都放到NetworkAsyncTask中,NetworkAsyncTask是继承自AsyncTask。

点击”GET”按钮后,界面如下所示:

GET请求是最简单的http请求,其发送请求的代码如下所示:

if (NETWORK_GET.equals(action)) {
    //发送GET请求
    url = new URL("http://192.168.31.200:8080/HttpServer/MyServlet?name=孙群&age=27");
    conn = (HttpURLConnection) url.openConnection();
    //HttpURLConnection默认就是用GET发送请求,所以下面的setRequestMethod可以省略
    conn.setRequestMethod("GET");
    //HttpURLConnection默认也支持从服务端读取结果流,所以下面的setDoInput也可以省略
    conn.setDoInput(true);
    //用setRequestProperty方法设置一个自定义的请求头:action,由于后端判断
    conn.setRequestProperty("action", NETWORK_GET);
    //禁用网络缓存
    conn.setUseCaches(false);
    //获取请求头
    requestHeader = getReqeustHeader(conn);
    //在对各种参数配置完成后,通过调用connect方法建立TCP连接,但是并未真正获取数据
    //conn.connect()方法不必显式调用,当调用conn.getInputStream()方法时内部也会自动调用connect方法
    conn.connect();
    //调用getInputStream方法后,服务端才会收到请求,并阻塞式地接收服务端返回的数据
    InputStream is = conn.getInputStream();
    //将InputStream转换成byte数组,getBytesByInputStream会关闭输入流
    responseBody = getBytesByInputStream(is);
    //获取响应头
    responseHeader = getResponseHeader(conn);
}

上面的注释写的比较详细了,此处对代码进行一下简单说明。

  • 我们在URL的后面添加了?name=孙群&age=27,这样就相当于添加了两个键值对,其形式如 ?key1=value1&key2=value2&key3 =value3,在?后面添加键值对,键和值之间用=相连,键值对之间用&分隔,服务端可以读取这些键值对信息。
  • HttpURLConnection默认就是用GET发送请求,当然也可以用conn.setRequestMethod(“GET”)将其显式地设置为GET请求。
  • 通过setRequestProperty方法可以设置请求头,既可以是标准的请求头,也可以是自定义的请求头,此处我们设置了自定义的请求头action,用于服务端判断请求的类型。
  • GET请求容易被缓存,我们可以用conn.setUseCaches(false)禁用缓存。
  • 通过调用connect方法可以让客户端和服务器之间建立TCP连接,建立连接之后不会立即传输数据,只是表示处于connected状态了。该方法不必显式调用,因为在之后的getInputStream方法中会隐式地调用该方法。
  • 调用HttpURLConnection的getInputStream()方法可以获得响应结果的输入流,即服务器向客户端输出的信息,需要注意的是,getInputStream()方法的调用必须在一系列的set方法之后进行。
  • 然后在方法getBytesByInputStream中,通过输入流的read方法得到字节数组,read方法是阻塞式的,每read一次,其实就是从服务器上下载一部分数据,直到将服务器的输出全部下载完成,这样就得到响应体responseBody了。
  • 我们可以通过getReqeustHeader方法读取请求头,代码如下所示:
        //读取请求头
        private String getReqeustHeader(HttpURLConnection conn) {            
            Map> requestHeaderMap = conn.getRequestProperties();
            Iterator requestHeaderIterator = requestHeaderMap.keySet().iterator();
            StringBuilder sbRequestHeader = new StringBuilder();
            while (requestHeaderIterator.hasNext()) {
                String requestHeaderKey = requestHeaderIterator.next();
                String requestHeaderValue = conn.getRequestProperty(requestHeaderKey);
                sbRequestHeader.append(requestHeaderKey);
                sbRequestHeader.append(":");
                sbRequestHeader.append(requestHeaderValue);
                sbRequestHeader.append("\n");
            return sbRequestHeader.toString();
        }
  • 由上可以看出,以上方法主要还是调用了HttpURLConnection的方法getRequestProperty获取请求头,需要注意的是getRequestProperty方法执行时,客户端和服务器之间必须还未建立TCP连接,即还没有调用connect方法,在connected之后执行getRequestProperty会抛出异常, 查看详情

我们还可以通过getResponseHeader方法获取响应头,代码如下所示:

 //读取响应头
    private String getResponseHeader(HttpURLConnection conn) {
        Map> responseHeaderMap = conn.getHeaderFields();
        int size = responseHeaderMap.size();
        StringBuilder sbResponseHeader = new StringBuilder();
        for(int i = 0; i < size; i++){
            String responseHeaderKey = conn.getHeaderFieldKey(i);
            String responseHeaderValue = conn.getHeaderField(i);
            sbResponseHeader.append(responseHeaderKey);
            sbResponseHeader.append(":");
            sbResponseHeader.append(responseHeaderValue);
            sbResponseHeader.append("\n");
        return sbResponseHeader.toString();
    }

通过方法getHeaderFieldKey可以获得响应头的key值,通过方法getHeaderField可以获得响应头的value值。

在后台的Servelt中将键值对信息重新原样写入到客户端,服务端的代码如下所示:

if(NETWORK_GET.equals(action) || NETWORK_POST_KEY_VALUE.equals(action)){
            //对于NETWORK_GET和NETWORK_POST_KEY_VALUE,遍历键值对,并将键值对重新写回到输出结果中
            Enumeration parameterNames = request.getParameterNames();
            PrintWriter writer = response.getWriter();
            while(parameterNames.hasMoreElements()){
                String name = parameterNames.nextElement();
                String value = request.getParameter(name);
                if(request.getMethod().toUpperCase().equals("GET")){
                    //GET请求需要进行编码转换,POST不需要
                    value = new String(value.getBytes("ISO-8859-1"), "UTF-8");
                writer.write(name + "=" + value + "\n");
            writer.flush();
            writer.close();
        }

在接收到服务端返回的数据后,在AsyncTask的onPostExecute方法中会将Url、请求头、响应头、响应体的值展示在UI上。


用POST发送键值对数据

点击”POST KEY VALUE”按钮,可以用POST发送键值对数据,界面如下所示:

代码如下所示:

if (NETWORK_POST_KEY_VALUE.equals(action)) {
    //用POST发送键值对数据
    url = new URL("http://192.168.31.200:8080/HttpServer/MyServlet");
    conn = (HttpURLConnection) url.openConnection();
    //通过setRequestMethod将conn设置成POST方法
    conn.setRequestMethod("POST");
    //调用conn.setDoOutput()方法以显式开启请求体
    conn.setDoOutput(true);
    //用setRequestProperty方法设置一个自定义的请求头:action,由于后端判断
    conn.setRequestProperty("action", NETWORK_POST_KEY_VALUE);
    //获取请求头
    requestHeader = getReqeustHeader(conn);
    //获取conn的输出流
    OutputStream os = conn.getOutputStream();
    //获取两个键值对name=孙群和age=27的字节数组,将该字节数组作为请求体
    requestBody = new String("name=孙群&age=27").getBytes("UTF-8");
    //将请求体写入到conn的输出流中
    os.write(requestBody);
    //记得调用输出流的flush方法
    os.flush();
    //关闭输出流
    os.close();
    //当调用getInputStream方法时才真正将请求体数据上传至服务器
    InputStream is = conn.getInputStream();
    //获得响应体的字节数组
    responseBody = getBytesByInputStream(is);
    //获得响应头
    responseHeader = getResponseHeader(conn);
}

使用POST发送请求的代码与用GET发送请求的代码大部分类似,我们只对其中不同的地方做下说明。

  • 需要通过setRequestMethod将conn设置成POST方法。
  • 如果想用POST发送请求体,那么需要调用setDoOutput方法,将其设置为true。
  • 通过conn.getOutputStream()获得输出流,可以向输出流中写入请求体,最后记得调用输出流的flush方法,注意此时并没有真正将请求体发送到服务器端。
  • 当调用getInputStream方法后,才真正将请求体的内容发送到服务器。

在我们的服务器端的Servlet中,在接收到POST请求发送的键值对数据后,也只是简单地将键值对数据原样写入给客户端,具体代码参见上文,不再赘述。


用POST发送XML数据

点击”POST XML”按钮,可以用POST发送XML数据,界面如下所示:

代码如下所示:


if (NETWORK_POST_XML.equals(action)) {
    //用POST发送XML数据
    url = new URL("http://192.168.31.200:8080/HttpServer/MyServlet");
    conn = (HttpURLConnection) url.openConnection();
    //通过setRequestMethod将conn设置成POST方法
    conn.setRequestMethod("POST");
    //调用conn.setDoOutput()方法以显式开启请求体
    conn.setDoOutput(true);
    //用setRequestProperty方法设置一个自定义的请求头:action,由于后端判断
    conn.setRequestProperty("action", NETWORK_POST_XML);
    //获取请求头
    requestHeader = getReqeustHeader(conn);
    //获取conn的输出流
    OutputStream os = conn.getOutputStream();
    //读取assets目录下的person.xml文件,将其字节数组作为请求体
    requestBody = getBytesFromAssets("person.xml");
    //将请求体写入到conn的输出流中
    os.write(requestBody);
    //记得调用输出流的flush方法
    os.flush();
    //关闭输出流
    os.close();
    //当调用getInputStream方法时才真正将请求体数据上传至服务器
    InputStream is = conn.getInputStream();
    //获得响应体的字节数组
    responseBody = getBytesByInputStream(is);
    //获得响应头
    responseHeader = getResponseHeader(conn);
}

上面的代码与用POST发送键值对的代码很相似,对其进行简单说明。

  • 上述代码通过getBytesFromAssets方法读取了assets目录下的person.xml文件,将xml文件的字节流作为请求体requestBody,然后将该请求体发送到服务器。
  • person.xml文件如下所示:
"1.0" encoding="utf-8"?>
"101">
        张三27"102">
        李四28

标签对应着Person类,Person类代码如下所示:

package com.ispring.httpurlconnection;
public class Person {
    private String id = "";
    private String name = "";
    private int age = 0;
    public String getId(){
        return id;
    public void setId(String id){
        this.id = id;
    public String getName() {
        return name;
    public void setName(String name) {
        this.name = name;
    public int getAge() {
        return age;
    public void setAge(int age) {
        this.age = age;
    @Override
    public String toString() {
        return new StringBuilder().append("name:").append(getName()).append(", age:").append(getAge()).toString();
}

服务端接收到客户端发送来的XML数据之后,只是简单的将其原样写回到客户端,即客户端接收到的响应体还是原来的XML数据,然后客户端通过parseXmlResultByBytes方法对该XML数据进行解析,将字节数组转换成List,parseXmlResultByBytes代码如下所示:

 //将表示xml的字节数组进行解析
    private String parseXmlResultByBytes(byte[] bytes) {
        InputStream is = new ByteArrayInputStream(bytes);
        StringBuilder sb = new StringBuilder();
        List persons = XmlParser.parse(is);
        for (Person person : persons) {
            sb.append(person.toString()).append("\n");
        return sb.toString();
    }

该方法使用了自定义的XmlParser类对XML数据进行解析,其源码如下所示:

package com.ispring.httpurlconnection;
import org.xml.sax.Attributes;
import org.xml.sax.SAXException;
import org.xml.sax.helpers.DefaultHandler;
import java.io.InputStream;
import java.util.ArrayList;
import java.util.List;
import javax.xml.parsers.SAXParser;
import javax.xml.parsers.SAXParserFactory;
public class XmlParser {
    public static Listparse(InputStream is) {
        List persons = new ArrayList<>();
            SAXParserFactory factory = SAXParserFactory.newInstance();
            SAXParser parser = factory.newSAXParser();
            PersonHandler personHandler = new PersonHandler();
            parser.parse(is, personHandler);
            persons = personHandler.getPersons();
        }catch (Exception e){
            e.printStackTrace();
        return persons;
    static class PersonHandler extends DefaultHandler {
        private List persons;
        private Person temp;
        private StringBuilder sb;
        public ListgetPersons(){
            return persons;
        @Override
        public void startDocument() throws SAXException {
            super.startDocument();
            persons = new ArrayList<>();
            sb = new StringBuilder();
        @Override
        public void startElement(String uri, String localName, String qName, Attributes attributes) throws SAXException {
            super.startElement(uri, localName, qName, attributes);
            sb.setLength(0);
            if(localName.equals("person")){
                temp = new Person();
                int length = attributes.getLength();
                for(int i = 0; i < length; i++){
                    String name = attributes.getLocalName(i);
                    if(name.equals("id")){
                        String value = attributes.getValue(i);
                        temp.setId(value);
        @Override
        public void characters(char[] ch, int start, int length) throws SAXException {
            super.characters(ch, start, length);
            sb.append(ch, start, length);
        @Override
        public void endElement(String uri, String localName, String qName) throws SAXException {
            super.endElement(uri, localName, qName);
            if(localName.equals("name")){
                String name = sb.toString();
                temp.setName(name);
            }else if(localName.equals("age")){
                int age = Integer.parseInt(sb.toString());
                temp.setAge(age);
            }else if(localName.equals("person")){
                persons.add(temp);
}

用POST发送JSON数据

点击”POST JSON”按钮,可以用POST发送JSON数据,界面如下所示:

代码如下所示:

if (NETWORK_POST_JSON.equals(action)) {
     //用POST发送JSON数据
     url = new URL("http://192.168.31.200:8080/HttpServer/MyServlet");
     conn = (HttpURLConnection) url.openConnection();
     //通过setRequestMethod将conn设置成POST方法
     conn.setRequestMethod("POST");
     //调用conn.setDoOutput()方法以显式开启请求体
     conn.setDoOutput(true);
     //用setRequestProperty方法设置一个自定义的请求头:action,由于后端判断
     conn.setRequestProperty("action", NETWORK_POST_JSON);
     //获取请求头
     requestHeader = getReqeustHeader(conn);
     //获取conn的输出流
     OutputStream os = conn.getOutputStream();
     //读取assets目录下的person.json文件,将其字节数组作为请求体
     requestBody = getBytesFromAssets("person.json");
     //将请求体写入到conn的输出流中
     os.write(requestBody);
     //记得调用输出流的flush方法
     os.flush();
     //关闭输出流
     os.close();
     //当调用getInputStream方法时才真正将请求体数据上传至服务器
     InputStream is = conn.getInputStream();
     //获得响应体的字节数组
     responseBody = getBytesByInputStream(is);
     //获得响应头
     responseHeader = getResponseHeader(conn);
}

上面的代码与用POST发送XML的代码很相似,对其进行简单说明。

  • 上述代码通过getBytesFromAssets方法读取了assets目录下的person.json文件,将json文件的字节流作为请求体requestBody,然后将该请求体发送到服务器。
  • person.json文件如下所示:

{
  "persons": [{
    "id": "101",
    "name":"张三",
    "age":27
    "id": "102",
    "name":"李四",
    "age":28
}
  • persons数组中的每个元素都对应着一个Person对象。
  • 服务端接收到客户端发送来的JSON数据之后,只是简单的将其原样写回到客户端,即客户端接收到的响应体还是原来的JSON数据,然后客户端通过parseJsonResultByBytes方法对该XML数据进行解析,将字节数组转换成List,parseXmlResultByBytes代码如下所示:
    //将表示json的字节数组进行解析
    private String parseJsonResultByBytes(byte[] bytes){
        String jsonString = getStringByBytes(bytes);
        List persons = JsonParser.parse(jsonString);
        StringBuilder sb = new StringBuilder();
        for (Person person : persons) {
            sb.append(person.toString()).append("\n");
        return sb.toString();
    }

该方法又使用了自定义的JsonParset类,源码如下所示:

package com.ispring.httpurlconnection;
import org.json.JSONArray;
import org.json.JSONException;
import org.json.JSONObject;
import java.util.ArrayList;
import java.util.List;
public class JsonParser {
    public static Listparse(String jsonString){
        List persons = new ArrayList<>();
            JSONObject jsonObject = new JSONObject(jsonString);
            JSONArray jsonArray = jsonObject.getJSONArray("persons");
            int length = jsonArray.length();
            for(int i = 0; i < length; i++){
                JSONObject personObject = jsonArray.getJSONObject(i);
                String id = personObject.getString("id");
                String name = personObject.getString("name");
                int age = personObject.getInt("age");
                Person person = new Person();
                person.setId(id);
                person.setName(name);
                person.setAge(age);
                persons.add(person);
        }catch (JSONException e){