OkHttp: 使用入门

阿里云国内75折 回扣 微信号:monov8
阿里云国际,腾讯云国际,低至75折。AWS 93折 免费开户实名账号 代冲值 优惠多多 微信号:monov8 飞机:@monov6

文章目录

1. 领域对象

HTTP客户端的作用就是接受你的请求获取对应的返回。理论上简单的然后实践过程会有一些陷阱。OkHttp是其中使用的比较广泛的一个。

okhttp主要包含的概念如下

1. Request

包含请求的url方法(GET/POST/PUT等待)HTTP头以及请求体。

2. Response

返回的编码(200 成功, 404 页面不存在)HTTP头以及返回体。

2.1 请求重写

我们会在高层次的描述请求哪个地址包含什么头等等。为了正确性和高效OkHttp会帮我们重写请求。

  • 添加缺失的HTTP头包括Content-Lenght、Transfer-Encoding、User-Agent、Host、Connection、Content-Type
  • 动添加一个Accept-Encoding支持自动解压gzip等压缩过的返回
  • 如果你有CookieOkHttp会自动添加一个Cookie的HTTP头
  • 可缓存的请求(Cache-Control、Expired、Etag等)自动添加If-Modified-Since、If-None-Match头
2.2 重写返回
  • 如果返回是压缩过的OkHttp会自动解压并删除Content-Encoding和Content-Length头因为这两个值解压后已经不准确
  • 如果是条件获取(If-Modified-Since等)OkHttp会自动切换从本地缓存或者服务端获取内容
2.3 跟踪请求
  • 自动完成302跳转获取最终结果页面内容
  • 如果配置了Authenticator自动HTTP权限验证
2.4 自动重试
  • 连接池内连接过期或者服务器不可触达尝试重试(重试的机制还需要深挖)

3. Calls

由于请求重写、跟踪请求、自动重试等等你描述的请求实际上会产生多个Request/Response OkHttp将这个操作抽象为一个Call。
Call实际上有两种执行方式

  • 同步执行当前线程阻塞指定请求完成
  • 异步执行当前请求如队列完成请求会回调注册的接口

Call可以被其他线程取消。如果Call被取消后还有线程还有代码去写Request或者读Response会抛出IOException

在同步模式下你使用自己线程并负责管理simultaneous太多的simultaneous connections浪费资源过少则影响延时
在异步模式下Dispatcher负责管理simultaneous你可以设置每个webserver(默认5个)以及总(默认64个)的连接数。

2. 创建连接

尽管你只提供一个URL链接OkHttp会使用三种类型去链接WebServer它们分别是: URL、Address、Route

1、URLs

URL是HTTP和互联网的基础处理提供Web上资源的统一命名服务同样指定了怎么访问Web资源。

URL是抽象的

  • 指定了请求内容是纯文本(http)还是加密的(https)
  • 没有指定加解密算法怎么去验证证书(HostnameVerifier)哪些证书是可靠的(SSLSocketFactory)
  • 没有指定怎么使用代理服务器以及代理服务器怎么鉴权

2、Addresses

Adress指定了一个WebServer包括所有连接服务器所需要的信息(包括端口、HTTPS设置、倾向的网络协议如HTTP/2、SPDY等)。

相同服务器地址的URL可能会共享底层的TCP连接共享TCP连接会有明显的性能优势:

  • 更低延迟
  • 更高吞吐量(TCP慢启动)
  • 省电

OkHttp通过ConnectionPool自动重用连接。

3、Routes

Routes提供了自动链接WebServer里的必要信息包括

  • IP地址通过DNS获取
  • 确定的代理服务器如果配置了ProxySelector的话
  • TLS(传输层安全协议)版本号

一个Adress可能会有多个Routes比如一个跨数据中心部署的WebServerDNS解析后肯能会有多个地址。

4、Connections

当你请求URL时OkHttp为你做了以下操作:

  1. 使用URL和配置好的OkHttpClient创建一个Addresss里边包含了我们怎么去连接Web服务器的信息。
  2. 尝试从ConnectionPool里获取一个对应Address的连接。
  3. 找不到连接的话它会尝试选择一个Route这意味着DNS解析、选择TLS版本以及代理服务器(需要的话)。
  4. 如果这是一个新的Route它会创建一个Socket连接、TLS隧道(经过HTTPS的代理服务)、TLS连接。如果需要的话完成TLS握手。
  5. 发生请求并读取结果。

如果连接有问题的话OkHttp会重新选择另外一个Route并重试。这有助于OkHttp从服务端部分地址不可用时恢复。同样有助于当连接过期或者TLS版本不支持的时候。

当响应读取完成后连接会被退回到连接池中等待重用一段时间没有使用后会被从连接池里清除。

3. 使用案例

我们提供了一下用OkHttp解决常见问题的样例。

1、同步GET请求

当响应结果比较小的时候(<1M),response.body().string()是方便且高效的否则应该使用流读取。

  private final OkHttpClient client = new OkHttpClient();

  public void run() throws Exception {
    Request request = new Request.Builder().url("https://publicobject.com").build();

    try (Response response = client.newCall(request).execute()) {
      if (!response.isSuccessful()) throw new IOException("Unexpected code " + response);

      Headers responseHeaders = response.headers();
      for (int i = 0; i < responseHeaders.size(); i++) {
        System.out.println(responseHeaders.name(i) + ": " + responseHeaders.value(i));
      }

      System.out.println(response.body().string());
    }
  }

2、异步GET请求

在OkHttp的工作线程里请求当响应可读的时候调用回调函数。回调函数实在响应HTTP头读取后调用的读取响应体还是需要同步完成。

private final OkHttpClient client = new OkHttpClient();

  public void run() throws Exception {
    Request request = new Request.Builder().url("http://publicobject.com").build();

    client.newCall(request).enqueue(new Callback() {
      @Override public void onFailure(Call call, IOException e) {
        e.printStackTrace();
      }

      @Override public void onResponse(Call call, Response response) throws IOException {
        try (ResponseBody responseBody = response.body()) {
          if (!response.isSuccessful()) throw new IOException("Unexpected code " + response);

          Headers responseHeaders = response.headers();
          for (int i = 0, size = responseHeaders.size(); i < size; i++) {
            System.out.println(responseHeaders.name(i) + ": " + responseHeaders.value(i));
          }

          System.out.println(responseBody.string());
        }
      }
    });
  }

3、发送和读取HTTP头

一般来说HTTP头一个KEY会对应一个值但是有的字段允许有多个值。OkHttp为这两种情况提供了支持:

  • Request.Builder.header(name,value)方法支持单值的HTTP头把已存在的同名HTTP头删除。
  • Request.Builder.addHeader(name,value)只是单纯的添加不会有删除操作。
  • Response.header(name)获取多值的最后一个没有的话返回null。
  • Response.headers(name)返回多值
 private final OkHttpClient client = new OkHttpClient();

  public void run() throws Exception {
    Request request = new Request.Builder()
        .url("https://api.github.com/repos/square/okhttp/issues")
        .header("User-Agent", "OkHttp Headers.java")
        .addHeader("Accept", "application/json; q=0.5")
        .addHeader("Accept", "application/vnd.github.v3+json")
        .build();

    try (Response response = client.newCall(request).execute()) {
      if (!response.isSuccessful()) throw new IOException("Unexpected code " + response);

      System.out.println("Server: " + response.header("Server"));
      System.out.println("Date: " + response.header("Date"));
      System.out.println("Vary: " + response.headers("Vary"));
    }
  }

4、POST 字符传

 public static final MediaType MEDIA_TYPE_MARKDOWN = MediaType.parse("text/x-markdown; charset=utf-8");

  private final OkHttpClient client = new OkHttpClient();

  public void run() throws Exception {
    String postBody = "* _1.2_ August 11, 2013\n";

    Request request = new Request.Builder()
        .url("https://api.github.com/markdown/raw")
        .post(RequestBody.create(MEDIA_TYPE_MARKDOWN, postBody))
        .build();

    try (Response response = client.newCall(request).execute()) {
      if (!response.isSuccessful()) throw new IOException("Unexpected code " + response);

      System.out.println(response.body().string());
    }
  }

5、POST 流

使用流做为请求体请求体内容会在发送请求的时候输出这个例子里使用的是Okio的BufferedSink你也可以通过BufferedSkin.outputStream获取输出流。

  public static final MediaType MEDIA_TYPE_MARKDOWN
      = MediaType.parse("text/x-markdown; charset=utf-8");

  private final OkHttpClient client = new OkHttpClient();

  public void run() throws Exception {
    RequestBody requestBody = new RequestBody() {
      @Override public MediaType contentType() {
        return MEDIA_TYPE_MARKDOWN;
      }

      @Override public void writeTo(BufferedSink sink) throws IOException {
        sink.writeUtf8("Numbers\n");
        sink.writeUtf8("-------\n");
        for (int i = 2; i <= 997; i++) {
          sink.writeUtf8(String.format(" * %s = %s\n", i, 2*i));
        }
      }
    };

    Request request = new Request.Builder()
        .url("https://api.github.com/markdown/raw")
        .post(requestBody)
        .build();

    try (Response response = client.newCall(request).execute()) {
      if (!response.isSuccessful()) throw new IOException("Unexpected code " + response);

      System.out.println(response.body().string());
    }
  }

6、POST 文件内容

  public static final MediaType MEDIA_TYPE_MARKDOWN
      = MediaType.parse("text/x-markdown; charset=utf-8");

  private final OkHttpClient client = new OkHttpClient();

  public void run() throws Exception {
    File file = new File("README.md");

    Request request = new Request.Builder()
        .url("https://api.github.com/markdown/raw")
        .post(RequestBody.create(MEDIA_TYPE_MARKDOWN, file))
        .build();

    try (Response response = client.newCall(request).execute()) {
      if (!response.isSuccessful()) throw new IOException("Unexpected code " + response);

      System.out.println(response.body().string());
    }
  }

7、POST 表单

  private final OkHttpClient client = new OkHttpClient();

  public void run() throws Exception {
    RequestBody formBody = new FormBody.Builder()
        .add("search", "Jurassic Park")
        .build();
    Request request = new Request.Builder()
        .url("https://en.wikipedia.org/w/index.php")
        .post(formBody)
        .build();

    try (Response response = client.newCall(request).execute()) {
      if (!response.isSuccessful()) throw new IOException("Unexpected code " + response);

      System.out.println(response.body().string());
    }
  }

8、上传

  private static final String IMGUR_CLIENT_ID = "...";
  private static final MediaType MEDIA_TYPE_PNG = MediaType.parse("image/png");

  private final OkHttpClient client = new OkHttpClient();

  public void run() throws Exception {
    // Use the imgur image upload API as documented at https://api.imgur.com/endpoints/image
    RequestBody requestBody = new MultipartBody.Builder()
        .setType(MultipartBody.FORM)
        .addFormDataPart("title", "Square Logo")
        .addFormDataPart("image", "logo-square.png",
            RequestBody.create(MEDIA_TYPE_PNG, new File("website/static/logo-square.png")))
        .build();

    Request request = new Request.Builder()
        .header("Authorization", "Client-ID " + IMGUR_CLIENT_ID)
        .url("https://api.imgur.com/3/image")
        .post(requestBody)
        .build();

    try (Response response = client.newCall(request).execute()) {
      if (!response.isSuccessful()) throw new IOException("Unexpected code " + response);

      System.out.println(response.body().string());
    }
  }

9、使用Moshi在JSON和对象之间转换

Moshi是一个简便的JSON转换类库。
ResponseBody.charStream()使用Content-Type里的编码解释响应内容找不到编码默认UTF-8。

  private final OkHttpClient client = new OkHttpClient();
  private final Moshi moshi = new Moshi.Builder().build();
  private final JsonAdapter<Gist> gistJsonAdapter = moshi.adapter(Gist.class);

  public void run() throws Exception {
    Request request = new Request.Builder()
        .url("https://api.github.com/gists/c2a7c39532239ff261be")
        .build();
    try (Response response = client.newCall(request).execute()) {
      if (!response.isSuccessful()) throw new IOException("Unexpected code " + response);

      Gist gist = gistJsonAdapter.fromJson(response.body().source());

      for (Map.Entry<String, GistFile> entry : gist.files.entrySet()) {
        System.out.println(entry.getKey());
        System.out.println(entry.getValue().content);
      }
    }
  }

  static class Gist {
    Map<String, GistFile> files;
  }

  static class GistFile {
    String content;
  }

10、缓存Response

要缓存Response你需要一个可以读写的目录以及设置缓存的大小。 大多数应用只需要创建一次OkHttpClient对象两个实例使用同一个缓存目录会导致冲突甚至使程序崩溃。
Response缓存通过服务端HTTP头控制。
添加一下HTTP头可以控制缓存的行为

  • CacheControl.FORCE_NETWORK强制从服务端加载。
  • CacheControl.FORCE_CACHE强制从缓存加载当缓存中信息不可获取(不存在或者过期)返回504
 private final OkHttpClient client;

  public CacheResponse(File cacheDirectory) throws Exception {
    int cacheSize = 10 * 1024 * 1024; // 10 MiB
    Cache cache = new Cache(cacheDirectory, cacheSize);
    client = new OkHttpClient.Builder().cache(cache).build();
  }

  public void run() throws Exception {
    Request request = new Request.Builder().url("http://publicobject.com/helloworld.txt").build();

    String response1Body;
    try (Response response1 = client.newCall(request).execute()) {
      if (!response1.isSuccessful()) throw new IOException("Unexpected code " + response1);

      response1Body = response1.body().string();
      System.out.println("Response 1 response:          " + response1);
      System.out.println("Response 1 cache response:    " + response1.cacheResponse());
      System.out.println("Response 1 network response:  " + response1.networkResponse());
    }

    String response2Body;
    try (Response response2 = client.newCall(request).execute()) {
      if (!response2.isSuccessful()) throw new IOException("Unexpected code " + response2);

      response2Body = response2.body().string();
      System.out.println("Response 2 response:          " + response2);
      System.out.println("Response 2 cache response:    " + response2.cacheResponse());
      System.out.println("Response 2 network response:  " + response2.networkResponse());
    }

    System.out.println("Response 2 equals Response 1? " + response1Body.equals(response2Body));
  }

11、取消Call调用

通Call.cancel()取消调用如果另外一个线程正在写请求或者读取返回时抛出IOException

  private final ScheduledExecutorService executor = Executors.newScheduledThreadPool(1);
  private final OkHttpClient client = new OkHttpClient();

  public void run() throws Exception {
    Request request = new Request.Builder()
        .url("http://httpbin.org/delay/2") // This URL is served with a 2 second delay.
        .build();

    final long startNanos = System.nanoTime();
    final Call call = client.newCall(request);

    // Schedule a job to cancel the call in 1 second.
    executor.schedule(new Runnable() {
      @Override public void run() {
        System.out.printf("%.2f Canceling call.%n", (System.nanoTime() - startNanos) / 1e9f);
        call.cancel();
        System.out.printf("%.2f Canceled call.%n", (System.nanoTime() - startNanos) / 1e9f);
      }
    }, 1, TimeUnit.SECONDS);

    System.out.printf("%.2f Executing call.%n", (System.nanoTime() - startNanos) / 1e9f);
    try (Response response = call.execute()) {
      System.out.printf("%.2f Call was expected to fail, but completed: %s%n",
          (System.nanoTime() - startNanos) / 1e9f, response);
    } catch (IOException e) {
      System.out.printf("%.2f Call failed as expected: %s%n",
          (System.nanoTime() - startNanos) / 1e9f, e);
    }
  }

12、超时

当端不可触达的时候客户端连接问题、服务端不可用、或者两者之间的问题可以通过设置超时。OkHttp支持三种超时: 连接超时写请求超时读返回超时。

  private final OkHttpClient client;

  public ConfigureTimeouts() throws Exception {
    client = new OkHttpClient.Builder()
        .connectTimeout(10, TimeUnit.SECONDS)
        .writeTimeout(10, TimeUnit.SECONDS)
        .readTimeout(30, TimeUnit.SECONDS)
        .build();
  }

  public void run() throws Exception {
    Request request = new Request.Builder()
        .url("http://httpbin.org/delay/2") // This URL is served with a 2 second delay.
        .build();

    try (Response response = client.newCall(request).execute()) {
      System.out.println("Response completed: " + response);
    }
  }

13、设置

所有的设置都是通过OkHttpClient完成的包括代理、超时、缓存等等。当你需要为一个请求修改设置的时候调用OkHttpClient.builder()方法返回的builder和原始的OkHttpClient共享连接池、Dispatcher和配置信息只需要修改特有的配置即可。

 private final OkHttpClient client = new OkHttpClient();

  public void run() throws Exception {
    Request request = new Request.Builder()
        .url("http://httpbin.org/delay/1") // This URL is served with a 1 second delay.
        .build();

    // Copy to customize OkHttp for this request.
    OkHttpClient client1 = client.newBuilder()
        .readTimeout(500, TimeUnit.MILLISECONDS)
        .build();
    try (Response response = client1.newCall(request).execute()) {
      System.out.println("Response 1 succeeded: " + response);
    } catch (IOException e) {
      System.out.println("Response 1 failed: " + e);
    }

    // Copy to customize OkHttp for this request.
    OkHttpClient client2 = client.newBuilder()
        .readTimeout(3000, TimeUnit.MILLISECONDS)
        .build();
    try (Response response = client2.newCall(request).execute()) {
      System.out.println("Response 2 succeeded: " + response);
    } catch (IOException e) {
      System.out.println("Response 2 failed: " + e);
    }
  }

14、Authentication

OkHttp会自动重试未授权的请求当服务端返回401的时候OkHttp会尝试寻找Authenticator注册信息如果有的话通过对应信息进行验证。代码关键点:

  • response.challenges()
  • Credentials.basic
  • response.request().newBuilder()
  private final OkHttpClient client;

  public Authenticate() {
    client = new OkHttpClient.Builder()
        .authenticator(new Authenticator() {
          @Override public Request authenticate(Route route, Response response) throws IOException {
            if (response.request().header("Authorization") != null) {
              return null; // Give up, we've already attempted to authenticate.
            }

            System.out.println("Authenticating for response: " + response);
            System.out.println("Challenges: " + response.challenges());
            String credential = Credentials.basic("jesse", "password1");
            return response.request().newBuilder()
                .header("Authorization", credential)
                .build();
          }
        })
        .build();
  }

  public void run() throws Exception {
    Request request = new Request.Builder()
        .url("http://publicobject.com/secrets/hellosecret.txt")
        .build();

    try (Response response = client.newCall(request).execute()) {
      if (!response.isSuccessful()) throw new IOException("Unexpected code " + response);

      System.out.println(response.body().string());
    }
  }

Authenticator返回null的时候OkHttp会停止重试。为了避免Authentication不起效时还反复重试, 可以再重试过一次之后返回null退出执行

  if (credential.equals(response.request().header("Authorization"))) {
    return null; // If we already failed with these credentials, don't retry.
  }

你以可以通过Response.priorResponse()来计数调用了多少次次数满是退出重试。

  if (responseCount(response) >= 3) {
    return null; // If we've failed 3 times, give up.
  }

  private int responseCount(Response response) {
    int result = 1;
    while ((response = response.priorResponse()) != null) {
      result++;
    }
    return result;
  }

15. 设置代理

Proxy proxy = new Proxy(Proxy.Type.HTTP, new InetSocketAddress("127.0.0.1",12345));
OkHttpClient client = new OkHttpClient.Builder().proxy(proxy).build();

Request request = new Request.Builder().url(url).header("Authorization", signature).post(RequestBody.create(MEDIA_JSON, postBody)).build();
Response response = client.newCall(request).execute();

 
 
 

阿里云国内75折 回扣 微信号:monov8
阿里云国际,腾讯云国际,低至75折。AWS 93折 免费开户实名账号 代冲值 优惠多多 微信号:monov8 飞机:@monov6

“OkHttp: 使用入门” 的相关文章

VisionMobile:开发者优先平台选择iOS vs Android vs HTML5

文章来源:http://www.visionmobile.com/blog/2013/12/developers-prioritise-platforms-ios-vs-android-vs-html5/开发者如何看待各平台,应用类型和成功之道是如何影响他们选择平台?Andreas Pappas将根...

mutable

mutable是让变量在const的函数里面可以被修改...

双显示器

首先设置主显,根据情况试试,第一个不行就试第二个: sudo xrandr --output DVI1 --primary sudo xrandr --output VGA1 --primary 然后如果鼠标移动不正常: 系统->首选项->显示器...

声明与定义

extern int i;//声明但未定义 int j;//声明并定义 extern int a = 0;//定义,如果写在函数内是错的,但可以写在函数外 Varibles must be defined exactly once but can be de...

使用C++编写STM32软件IIC

最近在重构自己的平衡车代码里面需要用到MPU6050的DMP从中读取四元数进行欧拉角解算但是看着软件IIC的代码实在是很变扭因为之前不会C++所以如果需要调用多个IIC设备那么使用的时候就需要重复的去进行软件IIC底层代码的初始化非常的麻烦而且需要调整各个引脚在学习过C+&#...

【Boto3学习笔记】session client resource的区别和使用

目录 Boto3是什么安装和配置快速开始Session&client&resource Boto3是什么 通过适用于 Python 的 AWS 开发工具包 boto3 可以支持您轻松将 Python 应用程序、库或脚本与 AWS 服务进行集成包括 Amazon S3...