okHttp 初体验

背景

之前请求 http 服务器,一直在使用 httpClient 库,最近发现 android 开发中广泛使用的为 okHttp 库。

本文体验一下 okHttp 库。

介绍

okHttp 库是一个 HTTP 客户端,在 Android 开发中广泛使用,特点为:

  • 支持 HTTP/2,允许对一个站点的所有请求共享一个 socket。
  • 如果服务端不支持 HTTP/2,则使用连接池,来降低请求延时。
  • 透明使用 GZIP,来减少下载字节。
  • 缓存响应结果,对于重复请求直接读缓存,不再走网络请求。

使用

pom 依赖

        <dependency>
            <groupId>com.squareup.okhttp3</groupId>
            <artifactId>okhttp</artifactId>
            <version>3.11.0</version>
        </dependency>

同步调用

package testOkHttp;

import java.io.IOException;

import okhttp3.MediaType;
import okhttp3.OkHttpClient;
import okhttp3.Request;
import okhttp3.RequestBody;
import okhttp3.Response;

public class MainOkHttp {
    public static void main(String[] args) throws IOException {
        OkHttpClient client = new OkHttpClient();

        // GET 示例
        {
            Request request = new Request.Builder().url("http://ip.taobao.com/service/getIpInfo2.php?ip=myip")
                    .addHeader("User-Agent", "mozilla").build();
            Response response = client.newCall(request).execute();
            System.out.println(response.body().string());
        }

        // POST 示例
        {
            String url = "http://ip.taobao.com/service/getIpInfo2.php";
            String content = "ip=myip";
            RequestBody body = RequestBody.create(MediaType.parse("application/x-www-form-urlencoded"), content);
            Request request = new Request.Builder().url(url).post(body).build();
            Response response = client.newCall(request).execute();
            System.out.println(response.body().string());
        }
    }
}

异步调用

异步发起的请求会被加入到 Dispatcher 中的 runningAsyncCalls双端队列中通过线程池来执行。不会阻塞主线程。

可以设置每个 webserver 的最大并发数 (默认为 5), 以及全局最大并发数 (默认为 64)

package testOkHttp;

import java.io.IOException;

import okhttp3.Call;
import okhttp3.Callback;
import okhttp3.MediaType;
import okhttp3.OkHttpClient;
import okhttp3.Request;
import okhttp3.RequestBody;
import okhttp3.Response;

public class MainOkHttp2 {
    public static void main(String[] args) throws IOException {
        OkHttpClient client = new OkHttpClient();

        // 异步 GET 请求示例
        {
            Request request = new Request.Builder().url("http://ip.taobao.com/service/getIpInfo2.php?ip=myip")
                    .addHeader("User-Agent", "mozilla").build();
            Call call = client.newCall(request);
            call.enqueue(new Callback() {
                @Override
                public void onFailure(Call call, IOException e) {
                    System.out.println("onFailure:" + e.getMessage());
                }

                @Override
                public void onResponse(Call call, Response response) throws IOException {
                    System.out.println("onResponse:" + response.body().string());
                }
            });
        }

        // 异步 POST 请求示例
        {
            String url = "http://ip.taobao.com/service/getIpInfo2.php";
            String content = "ip=myip";
            RequestBody body = RequestBody.create(MediaType.parse("application/x-www-form-urlencoded"), content);
            Request request = new Request.Builder().url(url).post(body).build();
            Call call = client.newCall(request);
            call.enqueue(new Callback() {
                @Override
                public void onFailure(Call call, IOException e) {
                    System.out.println("onFailure:" + e.getMessage());
                }

                @Override
                public void onResponse(Call call, Response response) throws IOException {
                    System.out.println("onResponse:" + response.body().string());
                }
            });
        }
    }
}

本文到处即可以结束的。

但是 okHttp 还有一些高级用法,不妨再继续看看。

Post 方法

RequestBody 有多种构造方法

数据流

不断的写入 sink 即可。

            RequestBody body = new RequestBody() {

                @Override
                public void writeTo(BufferedSink sink) throws IOException {
                    sink.writeUtf8("ip=myip");
                }

                @Override
                public MediaType contentType() {
                    return MediaType.parse("application/x-www-form-urlencoded");
                }
            };

提交文件

会自动从文件中把内容读出来

            File file = new File("/tmp/ip.txt");
            RequestBody body = RequestBody.create(MediaType.parse("application/x-www-form-urlencoded"), file);

提交表单

            RequestBody body = new FormBody.Builder().add("ip", "myip").build();

提交分块请求

            // 使用 imgur 图片上传 api 为例. client-Id 需要自己申请.
            // https://api.imgur.com/endpoints/image
            MultipartBody body = new MultipartBody.Builder().setType(MultipartBody.FORM)
                    .addPart(Headers.of("Content-Disposition", "form-data; name=\"title\""),
                            RequestBody.create(null, "abeffect test"))
                    .addPart(Headers.of("Content-Disposition", "form-data; name=\"image\""),
                            RequestBody.create(MediaType.parse("image/jpg"), new File("/tmp/1.jpg")))
                    .build();

            Request request = new Request.Builder().header("Authorization", "Client-ID your-own-client-id")
                    .url("https://api.imgur.com/3/image").post(body).build();

完整代码

package testOkHttp.post;

import java.io.File;
import java.io.IOException;

import okhttp3.Call;
import okhttp3.Callback;
import okhttp3.Headers;
import okhttp3.MediaType;
import okhttp3.MultipartBody;
import okhttp3.OkHttpClient;
import okhttp3.Request;
import okhttp3.RequestBody;
import okhttp3.Response;

public class PostMultipartBody {
    public static void main(String[] args) {
        OkHttpClient client = new OkHttpClient();

        // 异步 POST 请求示例
        {
            // 使用 imgur 图片上传 api 为例. client-Id 需要自己申请.
            // https://api.imgur.com/endpoints/image
            MultipartBody body = new MultipartBody.Builder().setType(MultipartBody.FORM)
                    .addPart(Headers.of("Content-Disposition", "form-data; name=\"title\""),
                            RequestBody.create(null, "abeffect test"))
                    .addPart(Headers.of("Content-Disposition", "form-data; name=\"image\""),
                            RequestBody.create(MediaType.parse("image/jpg"), new File("/tmp/1.jpg")))
                    .build();

            Request request = new Request.Builder().header("Authorization", "Client-ID your-own-client-id")
                    .url("https://api.imgur.com/3/image").post(body).build();

            Call call = client.newCall(request);
            call.enqueue(new Callback() {
                @Override
                public void onFailure(Call call, IOException e) {
                    System.out.println("onFailure:" + e.getMessage());
                }

                @Override
                public void onResponse(Call call, Response response) throws IOException {
                    System.out.println("onResponse:" + response.body().string());
                }
            });
        }
    }
}

拦截器 (Interceptors)

图片来自 [2]
imagepng

拦截器,就是在调用前后可以插入自己的代码。okHttp 可以在 Application 请求 OkHttp 前后进行拦截,也可以 OkHttp 请求 Network 前后进行拦截。

应用拦截器

应用拦截器,在请求 OkHttp core 时进行拦截。

package testOkHttp;

import java.io.IOException;
import java.util.logging.Logger;

import okhttp3.Interceptor;
import okhttp3.OkHttpClient;
import okhttp3.Request;
import okhttp3.Response;

public class ApplicationInterceptors {
    private static final Logger logger = Logger.getLogger(ApplicationInterceptors.class.getName());

    static class LoggingInterceptor implements Interceptor {
        @Override
        public Response intercept(Interceptor.Chain chain) throws IOException {
            Request request = chain.request();

            long t1 = System.nanoTime();
            logger.info(String.format("Sending request %s on %s%n%s", request.url(), chain.connection(),
                    request.headers()));

            Response response = chain.proceed(request);

            long t2 = System.nanoTime();
            logger.info(String.format("Received response for %s in %.1fms%n%s", response.request().url(),
                    (t2 - t1) / 1e6d, response.headers()));

            return response;
        }
    }

    public static void main(String[] args) throws IOException {
        OkHttpClient client = new OkHttpClient.Builder().addInterceptor(new LoggingInterceptor()).build();

        Request request = new Request.Builder().url("http://ip.taobao.com/service/getIpInfo2.php?ip=myip")
                .addHeader("User-Agent", "mozilla").build();
        Response response = client.newCall(request).execute();
        System.out.println(response.body().string());
    }
}

运行结果

Jul 23, 2018 2:56:45 PM testOkHttp.ApplicationInterceptors$LoggingInterceptor intercept
信息: Sending request http://ip.taobao.com/service/getIpInfo2.php?ip=myip on null
User-Agent: mozilla

Jul 23, 2018 2:56:45 PM testOkHttp.ApplicationInterceptors$LoggingInterceptor intercept
信息: Received response for http://ip.taobao.com/service/getIpInfo2.php?ip=myip in 165.0ms
Date: Mon, 23 Jul 2018 06:56:45 GMT
Content-Type: text/html;charset=UTF-8
Transfer-Encoding: chunked
Connection: keep-alive
Vary: Accept-Encoding

{"code":0,"data":{"ip":"182.92.253.3","country":"中国","area":"","region":"北京","city":"北京","county":"XX","isp":"阿里巴巴","country_id":"CN","area_id":"","region_id":"110000","city_id":"110100","county_id":"xx","isp_id":"100098"}}

网络拦截器

除了执行时的调用位置不同,其它和应用拦截器是一样的。

package testOkHttp;

import java.io.IOException;
import java.util.logging.Logger;

import okhttp3.Interceptor;
import okhttp3.OkHttpClient;
import okhttp3.Request;
import okhttp3.Response;

public class NetworkInterceptors {
    private static final Logger logger = Logger.getLogger(NetworkInterceptors.class.getName());

    static class LoggingInterceptor implements Interceptor {
        @Override
        public Response intercept(Interceptor.Chain chain) throws IOException {
            Request request = chain.request();

            long t1 = System.nanoTime();
            logger.info(String.format("Sending request %s on %s%n%s", request.url(), chain.connection(),
                    request.headers()));

            Response response = chain.proceed(request);

            long t2 = System.nanoTime();
            logger.info(String.format("Received response for %s in %.1fms%n%s", response.request().url(),
                    (t2 - t1) / 1e6d, response.headers()));

            return response;
        }
    }

    public static void main(String[] args) throws IOException {
        OkHttpClient client = new OkHttpClient.Builder().addNetworkInterceptor(new LoggingInterceptor()).build();

        Request request = new Request.Builder().url("http://ip.taobao.com/service/getIpInfo2.php?ip=myip")
                .addHeader("User-Agent", "mozilla").build();
        Response response = client.newCall(request).execute();
        System.out.println(response.body().string());
    }
}

执行结果

Jul 23, 2018 3:09:33 PM testOkHttp.NetworkInterceptors$LoggingInterceptor intercept
信息: Sending request http://ip.taobao.com/service/getIpInfo2.php?ip=myip on Connection{ip.taobao.com:80, proxy=DIRECT hostAddress=ip.taobao.com/106.14.52.115:80 cipherSuite=none protocol=http/1.1}
User-Agent: mozilla
Host: ip.taobao.com
Connection: Keep-Alive
Accept-Encoding: gzip

Jul 23, 2018 3:09:33 PM testOkHttp.NetworkInterceptors$LoggingInterceptor intercept
信息: Received response for http://ip.taobao.com/service/getIpInfo2.php?ip=myip in 54.6ms
Date: Mon, 23 Jul 2018 07:09:32 GMT
Content-Type: text/html;charset=UTF-8
Transfer-Encoding: chunked
Connection: keep-alive
Vary: Accept-Encoding
Content-Encoding: gzip

{"code":0,"data":{"ip":"182.92.253.3","country":"中国","area":"","region":"北京","city":"北京","county":"XX","isp":"阿里巴巴","country_id":"CN","area_id":"","region_id":"110000","city_id":"110100","county_id":"xx","isp_id":"100098"}}

内置拦截器

内置拦截器有下面这些,这里同时附上两个自定义拦截器,按执行顺序排列好:

  • client.interceptors(): 自定义的应用拦截器
  • RetryAndFollowUpInterceptor: 跟踪重定向,用户验证和错误等, 默认最多 20 个.
  • BridgeInterceptor: 桥接应用代码和网络代码, 添加必要的头信息,处理 gzip 等
  • CacheInterceptor: 维护请求的缓存
  • ConnectInterceptor: 向 server 建立连接
  • client.networkInterceptors(): 自定义的网络拦截器
  • CallServerInterceptor: 链中最后一个拦截器,向 server 发送请求

参考

  1. OkHttp
  2. OkHttp Wiki
  3. Okhttp3 基本使用