OkHttp使用徹底教程

  1. 歷史上Http請求庫優缺點
    在講述OkHttp以前, 咱們看下沒有OkHttp的時代, 咱們是如何完成http請求的.php

在沒有OkHttp的日子, 咱們使用 HttpURLConnection 或者 HttpClient . 那麼這二者都有什麼優缺點呢? 爲何不在繼續使用下去呢?
HttpClient 是Apache基金會的一個開源網絡庫, 功能十分強大, API數量衆多, 可是正是因爲龐大的API數量使得咱們很難在不破壞兼容性的狀況下對它進行升級和擴展, 因此Android團隊在提高和優化HttpClient方面的工做態度並不積極.
HttpURLConnection 是一種多用途, 輕量極的HTTP客戶端, 提供的API比較簡單, 能夠容易地去使用和擴展. 不過在Android 2.2版本以前, HttpURLConnection 一直存在着一些使人厭煩的bug. 好比說對一個可讀的InputStream調用close()方法時,就有可能會致使鏈接池失效了。那麼咱們一般的解決辦法就是直接禁用掉鏈接池的功能:
private void disableConnectionReuseIfNecessary() {html

// 這是一個2.2版本以前的bug    
if (Integer.parseInt(Build.VERSION.SDK) < Build.VERSION_CODES.FROYO) {    
    System.setProperty("http.keepAlive", "false");    
}

}
所以, 通常的推薦是在2.2以前, 使用 HttpClient , 由於其bug較少. 在2.2以後, 推薦使用 HttpURLConnection , 由於API簡單, 體積小, 而且有壓縮和緩存機制, 而且Android團隊後續會繼續優化 HttpURLConnection .
可是, 上面兩個類庫和 OkHttp 比起來就弱爆了, 由於OkHttp不只具備高效的請求效率, 而且提供了不少開箱即用的網絡疑難雜症解決方案.java

  • 支持HTTP/2, HTTP/2經過使用多路複用技術在一個單獨的TCP鏈接上支持併發, 經過在一個鏈接上一次性發送多個請求來發送或接收數據git

  • 若是HTTP/2不可用, 鏈接池複用技術也能夠極大減小延時github

  • 支持GZIP, 能夠壓縮下載體積web

  • 響應緩存能夠直接避免重複請求apache

  • 會從不少經常使用的鏈接問題中自動恢復json

  • 若是您的服務器配置了多個IP地址, 當第一個IP鏈接失敗的時候, OkHttp會自動嘗試下一個IPapi

  • OkHttp還處理了代理服務器問題和SSL握手失敗問題
    使用 OkHttp 無需重寫您程序中的網絡代碼。OkHttp實現了幾乎和java.net.HttpURLConnection同樣的API。若是你用了 Apache HttpClient,則OkHttp也提供了一個對應的okhttp-apache 模塊。數組

還有一個好消息, 從Android 4.4起, 其 HttpURLConnection 的內部實現已經變爲 OkHttp , 您能夠參考這兩個網頁: 爆棧網 和 Twitter .

  1. OkHttp類與http請求響應的映射
    在講解OkHttp使用以前, 再看下咱們Http請求和響應都有哪些部分組成.

2.1 http請求

因此一個類庫要完成一個http請求, 須要包含 請求方法 , 請求地址 , 請求協議 , 請求頭 , 請求體 這五部分. 這些都在 okhttp3.Request 的類中有體現, 這個類正是表明http請求的類. 看下圖:

其中 HttpUrl 類表明 請求地址 , String method 表明 請求方法 , Headers 表明請求頭, RequestBody 表明請求體. Object tag 這個是用來取消http請求的標誌, 這個咱們先無論. 這裏也許你在疑惑, 請求協議 呢? 爲何沒有請求協議對應的類. 且聽我慢慢道來, 下面就會講到這個問題.
2.1.1 請求協議的協商升級
目前, Http/1.1在全世界大範圍的使用中, 直接廢棄跳到http/2確定不現實. 不是每一個用戶的瀏覽器都支持http/2的, 也不是每一個服務器都打算支持http/2的, 若是咱們直接發送http/2格式的協議, 服務器又不支持, 那不是掛掉了! 總不能維護一個全世界的網站列表, 表示哪些支持http/2, 哪些不支持?
爲了解決這個問題, 從稍高層次上來講, 就是爲了更方便地部署新協議, HTTP/1.1 引入了 Upgrade 機制. 這個機制在 RFC7230 的「 6.7 Upgrade 」這一節中有詳細描述.
簡單說來, 就是先問下你支持http/2麼? 若是你支持, 那麼接下來我就用http/2和你聊天. 若是你不支持, 那麼我仍是用原來的http/1.1和你聊天.
1.客戶端在請求頭部中指定 Connection 和 Upgrade 兩個字段發起 HTTP/1.1 協議升級. HTTP/2 的協議名稱是 h2c, 表明 HTTP/2 ClearText.
GET / HTTP/1.1
Host: example.com
Connection: Upgrade, HTTP2-Settings
Upgrade: h2c
HTTP2-Settings: <base64url encoding of HTTP/2 SETTINGS payload>
2.若是服務端不一樣意升級或者不支持 Upgrade 所列出的協議,直接忽略便可(當成 HTTP/1.1 請求,以 HTTP/1.1 響應).
HTTP/1.1 200 OK
Content-Length: 243
Content-Type: text/html

...
若是服務端贊成升級,那麼須要這樣響應:
HTTP/1.1 101 Switching Protocols
Connection: Upgrade
Upgrade: h2c

[ HTTP/2 connection ... ]
HTTP Upgrade 響應的狀態碼是 101,而且響應正文可使用新協議定義的數據格式。
這樣就能夠完成從http/1.1升級到http/2了. 一樣也能夠從http/1.1升級到WebSocket.
這樣, 你就瞭解了爲何OkHttp沒有指定具體請求協議了吧. 由於OkHttp使用了請求協議的協商升級, 不管是1.1仍是2, 都先只以1.1來發送, 並在發送的信息頭裏包含協議升級字段. 接下來就看服務器是否支持協議升級了. OkHttp使用的協議升級字段是 ALPN , 若是有興趣, 能夠更深刻的查閱相關資料.
2.1.2 OkHttp請求
接下來咱們構造一個http請求, 並查看請求具體內容.
final Request request = new Request.Builder().url("https://github.com/").build();
咱們看下在內存中, 這個請求是什麼樣子的, 是否如咱們上文所說和 請求方法 , 請求地址 , 請求頭 , 請求體 一一對應.

2.2 http響應
咱們看下一個http響應由哪些部分組成, 先看下響應組成圖:

能夠看到大致由 應答首行 , 應答頭 , 應答體 構成. 可是 應答首行 表達的信息過多, HTTP/1.1 表示 訪問協議 , 200 是響應碼, OK 是描述狀態的消息. 根據單一職責, 咱們不該該把這麼多內容用一個 應答首行 來表示. 這樣的話, 咱們的響應就應該由 訪問協議 , 響應碼 , 描述信息 , 響應頭 , 響應體 來組成.
2.2.1 OkHttp響應
咱們看下OkHttp庫怎麼表示一個響應:

能夠看到 Response 類裏面有 Protocol 表明 請求協議 , int code 表明 響應碼 , String message 表明 描述信息 , Headers 表明 響應頭 , ResponseBody 表明 響應體 . 固然除此以外, 還有 Request 表明持有的請求, Handshake 表明SSL/TLS握手協議驗證時的信息, 這些額外信息咱們暫時不問.
有了剛纔說的OkHttp響應的類組成, 咱們看下OkHttp請求後響應在內存中的內容:
final Request request = new Request.Builder().url("https://github.com/").build();
Response response = client.newCall(request).execute();
能夠看到和咱們的分析十分一致.
講了OkHttp裏的請求類和響應類, 咱們接下來就能夠直接講述OkHttp的使用方法了.
3 HTTP GET
3.1 同步GET
同步GET的意思是一直等待http請求, 直到返回了響應. 在這之間會阻塞進程, 因此經過get不能在Android的主線程中執行, 不然會報錯.
private final OkHttpClient client = new OkHttpClient();

public void run() throws Exception {

Request request = new Request.Builder()
    .url("http://publicobject.com/helloworld.txt")
    .build();

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

}
OkHttpClient實現了 Call.Factory 接口, 是Call的工廠類, Call負責發送執行請求和讀取響應.
Request表明Http請求, 經過Request.Builder輔助類來構建.
client.newCall(request)經過傳入一個http request, 返回一個Call調用. 而後執行execute()方法, 同步得到
Response表明Http請求的響應. response.body()是ResponseBody類, 表明響應體, 能夠經過responseBody.string()得到字符串的表達形式, 或responseBody.bytes()得到字節數組的表達形式, 這兩種形式都會把文檔加入到內存. 也能夠經過responseBody.charStream()和responseBody.byteStream()返回流來處理.
上述代碼完成的功能是下載一個文件, 打印他的響應頭, 以string形式打印響應體.
響應體的string()方法對於小文檔來講十分方便高效. 可是若是響應體太大(超過1MB), 應避免使用 string()方法, 由於它會將把整個文檔加載到內存中.
對於超過1MB的響應body, 應使用流的方式來處理響應body. 這和咱們處理xml文檔的邏輯是一致的, 小文件能夠載入內存樹狀解析, 大文件就必須流式解析.
3.2 異步GET
異步GET是指在另外的工做線程中執行http請求, 請求時不會阻塞當前的線程, 因此能夠在Android主線程中使用.
下面是在一個工做線程中下載文件, 當響應可讀時回調Callback接口. 當響應頭準備好後, 就會調用Callback接口, 因此讀取 響應體 時可能會阻塞. OkHttp現階段不提供異步api來接收響應體。
private final OkHttpClient client = new OkHttpClient();

public void run() throws Exception {

Request request = new Request.Builder()
    .url("http://publicobject.com/helloworld.txt")
    .build();

client.newCall(request).enqueue(new Callback() {
  @Override public void onFailure(Request request, Throwable throwable) {
    throwable.printStackTrace();
  }

  @Override public void onResponse(Response response) throws IOException {
    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());
  }
});

}
4 HTTP POST
4.1 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());

}
4.2 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.3 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();

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

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

}
4.4 Post方式提交表單
使用FormEncodingBuilder來構建和HTML <form> 標籤相同效果的請求體. 鍵值對將使用一種HTML兼容形式的URL編碼來進行編碼.
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();

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

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

}
4.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. 其餘用法
    5.1 提取響應頭

典型的HTTP頭像是一個 Map<String, String> : 每一個字段都有一個或沒有值. 可是一些頭容許多個值, 像Guava的Multimap.
例如: HTTP響應裏面提供的Vary響應頭, 就是多值的. OkHttp的api試圖讓這些狀況都適用.
當寫請求頭的時候, 使用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"));

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

}
5.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 響應.
5.3.1 Force a Network Response
有些時候, 好比用戶剛剛點擊 刷新 按鈕, 這時必須跳過緩存, 直接從服務器抓取數據. 爲了強制全面刷新, 咱們須要添加 no-cache 指令:
connection.addRequestProperty("Cache-Control", "no-cache");
這樣就能夠強制每次請求直接發送給源服務器, 而不通過本地緩存版本的校驗, 經常使用於須要確認認證的應用和嚴格要求使用最新數據的應用.
5.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

}
}
5.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.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);

}
5.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);
}

}
5.7 處理驗證
這部分和HTTP AUTH有關.
5.7.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)
服務器端認證經過,並返回頁面
瀏覽器顯示頁面
5.7.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;

}

相關文章
相關標籤/搜索