美文网首页Android-2
网络框架三部曲:OkHttp笔记

网络框架三部曲:OkHttp笔记

作者: AndroidMaster | 来源:发表于2018-02-23 21:55 被阅读252次

    一、简介

    HttpClient是Apache基金会的一个开源网络库,功能十分强大,API数量众多,但正是由于庞大的API数量使得我们很难在不破坏兼容性的情况下对它进行升级和扩展,所以Android团队在提升和优化HttpClient方面的工作态度并不积极。官方在Android 2.3以后就不建议用了,并且在Android 5.0以后废弃了HttpClient,在Android 6.0更是删除了HttpClient。

    HttpURLConnection是一种多用途、轻量极的HTTP客户端,提供的API比较简单,可以容易地去使用和扩展。不过在Android 2.2版本之前,HttpURLConnection一直存在着一些令人厌烦的bug。比如说对一个可读的InputStream调用close()方法时,就有可能会导致连接池失效了。那么我们通常的解决办法就是直接禁用掉连接池的功能。因此一般推荐是在2.2之前使用HttpClient,因为其bug较少。在2.2之后推荐使用HttpURLConnection,因为API简单、体积小、有压缩和缓存机制,并且Android团队后续会继续优化HttpURLConnection。

    自从Android4.4开始,google已经开始将源码中的HttpURLConnection替换为OkHttp,而市面上流行的Retrofit同样是使用OkHttp进行再次封装而来的。

    OkHttp是一个快速、高效的网络请求库,它的设计和实现的首要目标便是高效,有如下特性:

    • 支持HTTP/2, HTTP/2通过使用多路复用技术在一个单独的TCP连接上支持并发, 通过在一个连接上一次性发送多个请求来发送或接收数据;
    • 如果HTTP/2不可用, 连接池复用技术也可以极大减少延时;
    • 支持Gzip压缩响应体,降低传输内容的大小;
    • 支持Http缓存,避免重复请求;
    • 如果您的服务器配置了多个IP地址, 当第一个IP连接失败的时候, OkHttp会自动尝试下一个IP;
    • 使用Okio来简化数据的访问与存储,提高性能;
    • OkHttp还处理了代理服务器问题和SSL握手失败问题;

    二、OkHttp类与Http请求响应的映射

    1、Http请求

    http请求包含:请求方法, 请求地址, 请求协议, 请求头, 请求体这五部分。这些都在okhttp3.Request的类中有体现, 这个类正是代表http请求的类。

    public final class Request {
      final HttpUrl url;//请求地址
      final String method;//请求方法
      final Headers headers;//请求头
      final RequestBody body;//请求体
      final Object tag;
      ...
    }
    

    2、Http响应

    Http响应由访问协议, 响应码, 描述信息, 响应头, 响应体来组成。

    public final class Response implements Closeable {
      final Request request;//持有的请求
      final Protocol protocol;//访问协议
      final int code;//响应码
      final String message;//描述信息
      final Handshake handshake;//SSL/TLS握手协议验证时的信息,
      final Headers headers;//响应头
      final ResponseBody body;//响应体
      ...
    }
    

    三、相关方法

    1、OkHttpClient

    • OkHttpClient()
    • OkHttpClient(OkHttpClient.Builder builder)
    • newCall(Request request) Call
    OkHttpClient.Builder
    • connectTimeout(long timeout, TimeUnit unit)
    • readTimeout(long timeout, TimeUnit unit)
    • writeTimeout(long timeout, TimeUnit unit)
    • pingInterval(long interval, TimeUnit unit)
    • cache(Cache cache) 入参如:new Cache(File directory, long maxSize)
    • cookieJar(CookieJar cookieJar) CookieJar是一个接口
    • hostnameVerifier(HostnameVerifier hostnameVerifier) HostnameVerifier是一个接口,只有boolean verify(String hostname, SSLSession session)
    • sslSocketFactory(SSLSocketFactory sslSocketFactory, X509TrustManager trustManager)

    2、Request

    • Request(Request.Builder builder)
    Request.Builder
    • addHeader(String name, String value)
    • url(String url)
    • post(RequestBody body)
    • header(String name, String value) 添加键值对
    • method(String method, RequestBody body)
    • build() Request
    RequestBody
    • create(MediaType contentType, final File file) RequestBody
    • create(MediaType contentType, String content) RequestBody
    • create(MediaType contentType, byte[] content) RequestBody
    FormBody

    RequestBody的子类

    FormBody.Builder
    • add(String name, String value) FormBody.Builder
    • build() FormBody
    MultipartBody

    RequestBody的子类

    MultipartBody.Builder
    • Builder()
    • Builder(String boundary)
    • setType(MediaType type)
    • addPart(Headers headers, RequestBody body)
    • addFormDataPart(String name, String filename, RequestBody body)
    • build() MultipartBody

    3、Call

    Call负责发送请求和读取响应

    • enqueue(Callback responseCallback) 加入调度队列,异步执行
    • execute() Response 同步执行
    • cancel()

    4、Response

    • body() ResponseBody
    • code() int http请求的状态码
    • isSuccessful() code为2XX时,返回true,否则false
    • headers() Headers
    ResponseBody
    • string() String
    • bytes() byte[]
    • byteStream() InputStream
    • charStream() Reader
    • contentLength() long

    5、MediaType

    RequestBody的数据格式都要指定Content-Type,就是指定MIME,常见的有三种:

    • application/x-www-form-urlencoded 数据是个普通表单(默认)
    • multipart/form-data 数据里有文件
    • application/json 数据是个json

    方法:

    • parse(String string) MediaType
    参数 说明
    text/html HTML格式
    text/plain 纯文本格式
    image/gif gif图片格式
    image/jpeg jpg图片格式
    image/png png图片格式
    application/json JSON数据格式
    application/pdf pdf格式
    application/msword Word文档格式
    application/octet-stream 二进制流数据
    application/x-www-form-urlencoded 普通表单数据
    multipart/form-data 表单数据里有文件

    6、自动管理Cookie

    Request经常都要携带Cookie,request创建时可以通过header设置参数,Cookie也是参数之一。就像下面这样:

    Request request = new Request.Builder()
        .url(url)
        .header("Cookie", "xxx")
        .build();
    

    然后可以从返回的response里得到新的Cookie,你可能得想办法把Cookie保存起来。
    但是OkHttp可以不用我们管理Cookie,自动携带,保存和更新Cookie。
    方法是在创建OkHttpClient设置管理Cookie的CookieJar:

    private final HashMap<String, List<Cookie>> cookieStore = new HashMap<>();
    OkHttpClient okHttpClient = new OkHttpClient.Builder()
        .cookieJar(new CookieJar() {
            @Override
            public void saveFromResponse(HttpUrl httpUrl, List<Cookie> list) {
                cookieStore.put(httpUrl.host(), list);
            }
    
            @Override
            public List<Cookie> loadForRequest(HttpUrl httpUrl) {
                List<Cookie> cookies = cookieStore.get(httpUrl.host());
                return cookies != null ? cookies : new ArrayList<Cookie>();
            }
        })
        .build();
    

    这样以后发送Request都不用管Cookie这个参数也不用去response获取新Cookie什么的了。还能通过cookieStore获取当前保存的Cookie。
    最后,new OkHttpClient()只是一种快速创建OkHttpClient的方式,更标准的是使用OkHttpClient.Builder()。后者可以设置一堆参数,例如超时时间什么的。

    四、get请求

    1、异步的get请求

    //step 1: 创建 OkHttpClient 对象
    OkHttpClient okHttpClient = new OkHttpClient();
    //step 2: 创建一个请求,不指定请求方法时默认是GET。
    Request.Builder requestBuilder = new Request.Builder().url("http://www.baidu.com");
    //可以省略,默认是GET请求
    requestBuilder.method("GET", null);
    //step 3:创建 Call 对象
    Call call = okHttpClient.newCall(requestBuilder.build());
    //step 4: 开始异步请求
    call.enqueue(new Callback() {
        @Override
        public void onFailure(Call call, IOException e) {
        }
    
        @Override
        public void onResponse(Call call, Response response) throws IOException {
            //获得返回体
            ResponseBody body = response.body();
        }
    });
    

    首先要将OkHttpClient的对象与Request的对象建立起来联系,使用okHttpClient的newCall()方法得到一个Call对象,这个Call对象的作用就是相当于将请求封装成了一个任务,既然是任务,自然就会有execute()cancel()等方法。

    最后,我们希望以异步的方式去执行请求,所以我们调用的是call.enqueue,将call加入调度队列,然后等待任务执行完成,我们在Callback中即可得到结果。但要注意的是,call的回调是子线程,所以是不能直接操作界面的。当请求成功时就会回调onResponse()方法,我们可以看到返回的结果是Response对象,在此我们比较关注的是请求中的返回体body(ResponseBody类型),大多数的情况下我们希望获得字符串从而进行json解析获得数据,所以可以通过body.string()的方式获得字符串。如果希望获得返回的二进制字节数组,则调用response.body().bytes();如果你想拿到返回的inputStream,则调用response.body().byteStream()。

    2、同步的get请求

    调用Call#execute()方法,在主线程运行

    五、post请求

    1、Post上传表单(键值对)

    //step1: 同样的需要创建一个OkHttpClick对象
    OkHttpClient okHttpClient = new OkHttpClient();
    //step2: 创建 FormBody.Builder
    FormBody formBody = new FormBody.Builder()
        .add("name", "dsd") //添加键值对
        .build();
    //step3: 创建请求
    Request request = new Request.Builder().url("http://www.baidu.com")
        .post(formBody)
        .build()
    //step4: 建立联系 创建Call对象
    okHttpClient.newCall(request).enqueue(new Callback() {
        @Override
        public void onFailure(Call call, IOException e) {
        }
    
        @Override
        public void onResponse(Call call, Response response) throws IOException {
        }
    });
    

    2、Post异步上传文件

    // step 1: 创建 OkHttpClient 对象
    OkHttpClient okHttpClient = new OkHttpClient();
    //step 2:创建 RequestBody 以及所需的参数
    //2.1 获取文件
    File file = new File(Environment.getExternalStorageDirectory() + "test.txt");
    //2.2 创建 MediaType 设置上传文件类型
    MediaType MEDIATYPE = MediaType.parse("text/plain; charset=utf-8");
    //2.3 获取请求体
    RequestBody requestBody = RequestBody.create(MEDIATYPE, file);
    //step 3:创建请求
    Request request = new Request.Builder().url("http://www.baidu.com")
        .post(requestBody)
        .build();
    //step 4 建立联系
    okHttpClient.newCall(request).enqueue(new Callback() {
        @Override
        public void onFailure(Call call, IOException e) {
        }
    
        @Override
        public void onResponse(Call call, Response response) throws IOException {
        }
    });
    

    3、Post方式提交流

    以流的方式POST提交请求体. 请求体的内容由流写入产生. 这个例子是流直接写入Okio的BufferedSink. 你的程序可能会使用OutputStream, 你可以使用BufferedSink.outputStream()来获取. OkHttp的底层对流和字节的操作都是基于Okio库, Okio库也是Square开发的另一个IO库, 填补I/O和NIO的空缺, 目的是提供简单便于使用的接口来操作IO.

    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, factor(i)));
            }
        }
    
        private String factor(int n) {
            for (int i = 2; i < n; i++) {
                int x = n / i;
                if (x * i == n) return factor(x) + " × " + i;
            }
            return Integer.toString(n);
        }
    };
    
    Request request = new Request.Builder()
        .url("https://api.github.com/markdown/raw")
        .post(requestBody)
        .build();
    
    Response response = client.newCall(request).execute();
    if (!response.isSuccessful()) throw new IOException("Unexpected code " + response);
        System.out.println(response.body().string());
    }
    

    4、Post方式提交String

    下面是使用HTTP POST提交请求到服务. 这个例子提交了一个markdown文档到web服务, 以HTML方式渲染markdown. 因为整个请求体都在内存中, 因此避免使用此api提交大文档(大于1MB).

    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 = ""
            + "Releases\n"
            + "--------\n"
            + "\n"
            + " * _1.0_ May 6, 2013\n"
            + " * _1.1_ June 15, 2013\n"
            + " * _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();
    
        Response response = client.newCall(request).execute();
        if (!response.isSuccessful()) throw new IOException("Unexpected code " + response);
    
        System.out.println(response.body().string());
    }
    

    5、Post方式提交分块请求

    MultipartBody.Builder可以构建复杂的请求体, 与HTML文件上传形式兼容. 多块请求体中每块请求都是一个请求体, 可以定义自己的请求头. 这些请求头可以用来描述这块请求, 例如它的Content-Disposition. 如果Content-Length和Content-Type可用的话, 他们会被自动添加到请求头中.

    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();
    
        Response response = client.newCall(request).execute();
        if (!response.isSuccessful()) throw new IOException("Unexpected code " + response);
    
        System.out.println(response.body().string());
    }
    

    六、其他用法

    1、提取响应头

    典型的HTTP头是一个Map<String, String> : 每个字段都有一个或没有值. 但是一些头允许多个值。
    当写请求头的时候, 使用header(name, value)可以设置唯一的name、value. 如果已经有值, 旧的将被移除, 然后添加新的. 使用addHeader(name, value)可以添加多值(添加, 不移除已有的).
    当读取响应头时, 使用header(name)返回最后出现的name、value. 通常情况这也是唯一的name、value. 如果没有值, 那么header(name)将返回null. 如果想读取字段对应的所有值, 使用headers(name)`会返回一个list.
    为了获取所有的Header, Headers类支持按index访问.

    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();
     
        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"));
    }
    

    2、使用Gson来解析JSON响应

    Gson是一个在JSON和Java对象之间转换非常方便的api库. 这里我们用Gson来解析Github API的JSON响应.
    注意: ResponseBody.charStream()使用响应头Content-Type指定的字符集来解析响应体. 默认是UTF-8.

    private final OkHttpClient client = new OkHttpClient();
        private final Gson gson = new Gson();
    
        public void run() throws Exception {
        Request request = new Request.Builder()
            .url("https://api.github.com/gists/c2a7c39532239ff261be")
            .build();
        Response response = client.newCall(request).execute();
        if (!response.isSuccessful()) throw new IOException("Unexpected code " + response);
    
        Gist gist = gson.fromJson(response.body().charStream(), Gist.class);
        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;
      }
    

    3、响应缓存

    为了缓存响应, 你需要一个你可以读写的缓存目录, 和缓存大小的限制. 这个缓存目录应该是私有的, 不信任的程序应不能读取缓存内容.
    一个缓存目录同时拥有多个缓存访问是错误的. 大多数程序只需要调用一次new OkHttp(), 在第一次调用时配置好缓存, 然后其他地方只需要调用这个实例就可以了. 否则两个缓存示例互相干扰, 破坏响应缓存, 而且有可能会导致程序崩溃.
    响应缓存使用HTTP头作为配置. 你可以在请求头中添加Cache-Control: max-stale=3600 , OkHttp缓存会支持. 你的服务通过响应头确定响应缓存多长时间, 例如使用Cache-Control: max-age=9600.

    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();
        client.setCache(cache);
    }
     
    public void run() throws Exception {
        Request request = new Request.Builder()
            .url("http://publicobject.com/helloworld.txt")
            .build();
     
        Response response1 = client.newCall(request).execute();
        if (!response1.isSuccessful()) throw new IOException("Unexpected code " + response1);
     
        String 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());
     
        Response response2 = client.newCall(request).execute();
        if (!response2.isSuccessful()) throw new IOException("Unexpected code " + response2);
     
        String 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));
    }
    

    如果需要阻值response使用缓存, 使用CacheControl.FORCE_NETWORK. 如果需要阻值response使用网络, 使用CacheControl.FORCE_CACHE.
    警告: 如果你使用FORCE_CACHE, 但是response要求使用网络, OkHttp将会返回一个504 Unsatisfiable Request响应.

    3.1、 Force a Network Response

    有些时候, 比如用户刚刚点击刷新按钮, 这时必须跳过缓存, 直接从服务器抓取数据. 为了强制全面刷新, 我们需要添加no-cache指令:

    connection.addRequestProperty("Cache-Control", "no-cache");

    这样就可以强制每次请求直接发送给源服务器, 而不经过本地缓存版本的校验, 常用于需要确认认证的应用和严格要求使用最新数据的应用.

    3.2、Force a Cache Response

    有时你会想立即显示资源. 这样即使在后台正下载着最新资源, 你的客户端仍然可以先显示原有资源, 毕竟有个东西显示比没有东西显示要好.
    如果需要限制让请求优先使用本地缓存资源, 需要增加only-if-cached指令:

    try {
         connection.addRequestProperty("Cache-Control", "only-if-cached");
         InputStream cached = connection.getInputStream();
         // the resource was cached! show it
      catch (FileNotFoundException e) {
         // the resource was not cached
     }
    }
    

    4、取消一个Call

    使用Call.cancel()可以立即停止掉一个正在执行的call. 如果一个线程正在写请求或者读响应, 将会引发IOException. 当call没有必要的时候, 使用这个api可以节约网络资源. 例如当用户离开一个应用时, 不管同步还是异步的call都可以取消.
    你可以通过tags来同时取消多个请求. 当你构建一请求时, 使用RequestBuilder.tag(tag)来分配一个标签, 之后你就可以用OkHttpClient.cancel(tag)来取消所有带有这个tag的call.

      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);
    
        try {
          System.out.printf("%.2f Executing call.%n", (System.nanoTime() - startNanos) / 1e9f);
          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);
        }
      }
    

    5、超时

    没有响应时使用超时结束call. 没有响应的原因可能是客户点链接问题、服务器可用性问题或者这之间的其他东西. 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();
    
        Response response = client.newCall(request).execute();
        System.out.println("Response completed: " + response);
      }
    

    6、每个call的配置

    使用OkHttpClient, 所有的HTTP Client配置包括代理设置、超时设置、缓存设置. 当你需要为单个call改变配置的时候, 调用OkHttpClient.newBuilder(). 这个api将会返回一个builder, 这个builder和原始的client共享相同的连接池, 分发器和配置.
    下面的例子中,我们让一个请求是500ms的超时、另一个是3000ms的超时。

      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();
    
        try {
          // Copy to customize OkHttp for this request.
          OkHttpClient copy = client.newBuilder()
              .readTimeout(500, TimeUnit.MILLISECONDS)
              .build();
    
          Response response = copy.newCall(request).execute();
          System.out.println("Response 1 succeeded: " + response);
        } catch (IOException e) {
          System.out.println("Response 1 failed: " + e);
        }
    
        try {
          // Copy to customize OkHttp for this request.
          OkHttpClient copy = client.newBuilder()
              .readTimeout(3000, TimeUnit.MILLISECONDS)
              .build();
    
          Response response = copy.newCall(request).execute();
          System.out.println("Response 2 succeeded: " + response);
        } catch (IOException e) {
          System.out.println("Response 2 failed: " + e);
        }
      }
    

    七、处理验证

    这部分和HTTP AUTH有关.

    1、HTTP AUTH

    使用HTTP AUTH需要在server端配置http auth信息, 其过程如下:

    客户端发送http请求
    服务器发现配置了http auth, 于是检查request里面有没有"Authorization"的http header
    如果有, 则判断Authorization里面的内容是否在用户列表里面, Authorization header的典型数据为"Authorization: Basic jdhaHY0=", 其中Basic表示基础认证, jdhaHY0=是base64编码的"user:passwd"字符串. 如果没有,或者用户密码不对,则返回http code 401页面给客户端.
    标准的http浏览器在收到401页面之后, 应该弹出一个对话框让用户输入帐号密码; 并在用户点确认的时候再次发出请求, 这次请求里面将带上Authorization header.
    一次典型的访问场景是:

    浏览器发送http请求(没有Authorization header)
    服务器端返回401页面
    浏览器弹出认证对话框
    用户输入帐号密码,并点确认
    浏览器再次发出http请求(带着Authorization header)
    服务器端认证通过,并返回页面
    浏览器显示页面

    2、OkHttp认证

    OkHttp会自动重试未验证的请求. 当响应是401 Not Authorized时,Authenticator会被要求提供证书. Authenticator的实现中需要建立一个新的包含证书的请求. 如果没有证书可用, 返回null来跳过尝试.
    使用Response.challenges()来获得任何authentication challenges的 schemes 和 realms. 当完成一个Basic challenge, 使用Credentials.basic(username, password)来解码请求头.

      private final OkHttpClient client;
    
      public Authenticate() {
        client = new OkHttpClient.Builder()
            .authenticator(new Authenticator() {
              @Override public Request authenticate(Route route, Response response) throws IOException {
                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();
    
        Response response = client.newCall(request).execute();
        if (!response.isSuccessful()) throw new IOException("Unexpected code " + response);
    
        System.out.println(response.body().string());
      }
    

    当认证无法工作时, 为了避免多次重试, 你可以返回空来放弃认证. 例如, 当exact credentials已经尝试过, 你可能会直接想跳过认证, 可以这样做:

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

    当重试次数超过定义的次数, 你若想跳过认证, 可以这样做:

      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;
      }
    

    参考文献

    OkHttp使用完全教程

    相关文章

      网友评论

        本文标题:网络框架三部曲:OkHttp笔记

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