OkHttp Github官方教程
英文版原版地址
https://github.com/square/okhttp/wiki/Recipes
同步get
下載一個檔案,列印他的響應頭,以string形式列印響應體。
響應體的 string() 方法對於小文件來說十分方便、高效。但是如果響應體太大(超過1MB),應避免適應 string()方法 ,因為他會將把整個文件載入到記憶體中。
對於超過1MB的響應body,應使用流的方式來處理body。
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()); }
非同步get
在一個工作執行緒中下載檔案,當響應可讀時回撥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()); } }); }
提取響應頭
典型的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"));
}
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());
}
Post方式提交流
以流的方式POST提交請求體。請求體的內容由流寫入產生。這個例子是流直接寫入Okio的BufferedSink。你的程式可能會使用OutputStream,你可以使用BufferedSink.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, 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());
}
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());
}
Post方式提交表單
使用FormEncodingBuilder來構建和HTML標籤相同效果的請求體。鍵值對將使用一種HTML相容形式的URL編碼來進行編碼。
private final OkHttpClient client = new OkHttpClient();
public void run() throws Exception {
RequestBody formBody = new FormEncodingBuilder()
.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());
}
Post方式提交分塊請求
MultipartBuilder可以構建複雜的請求體,與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 MultipartBuilder()
.type(MultipartBuilder.FORM)
.addPart(
Headers.of("Content-Disposition", "form-data; name=\"title\""),
RequestBody.create(null, "Square Logo"))
.addPart(
Headers.of("Content-Disposition", "form-data; name=\"image\""),
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());
}
使用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;
}
響應快取
為了快取響應,你需要一個你可以讀寫的快取目錄,和快取大小的限制。這個快取目錄應該是私有的,不信任的程式應不能讀取快取內容。
一個快取目錄同時擁有多個快取訪問是錯誤的。大多數程式只需要呼叫一次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));
}
擴充套件
在這一節還提到了下面一句:
There are cache headers to force a cached response, force a network response, or force the network response to be validated with a conditional GET.
我不是很懂cache,平時用到的也不多,所以把Google在Android Developers一段相關的解析放到這裡吧。
Force a Network Response
In some situations, such as after a user clicks a ‘refresh’ button, it may be necessary to skip the cache, and fetch data directly from the server. To force a full refresh, add the no-cache directive:
connection.addRequestProperty(“Cache-Control”, “no-cache”);
If it is only necessary to force a cached response to be validated by the server, use the more efficient max-age=0 instead:
connection.addRequestProperty(“Cache-Control”, “max-age=0”);
Force a Cache Response
Sometimes you’ll want to show resources if they are available immediately, but not otherwise. This can be used so your application can show something while waiting for the latest data to be downloaded. To restrict a request to locally-cached resources, add the only-if-cached directive:
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
}
}
This technique works even better in situations where a stale response is better than no response. To permit stale cached responses, use the max-stale directive with the maximum staleness in seconds:
int maxStale = 60 * 60 * 24 * 28; // tolerate 4-weeks stale
connection.addRequestProperty(“Cache-Control”, “max-stale=” + maxStale);
取消一個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);
}
}
超時
沒有響應時使用超時結束call。沒有響應的原因可能是客戶點連結問題、伺服器可用性問題或者這之間的其他東西。OkHttp支援連線,讀取和寫入超時。
private final OkHttpClient client;
public ConfigureTimeouts() throws Exception {
client = new OkHttpClient();
client.setConnectTimeout(10, TimeUnit.SECONDS);
client.setWriteTimeout(10, TimeUnit.SECONDS);
client.setReadTimeout(30, TimeUnit.SECONDS);
}
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);
}
每個call的配置
使用OkHttpClient,所有的HTTP Client配置包括代理設定、超時設定、快取設定。當你需要為單個call改變配置的時候,clone 一個 OkHttpClient。這個api將會返回一個淺拷貝(shallow copy),你可以用來單獨自定義。下面的例子中,我們讓一個請求是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 {
Response response = client.clone() // Clone to make a customized OkHttp for this request.
.setReadTimeout(500, TimeUnit.MILLISECONDS)
.newCall(request)
.execute();
System.out.println("Response 1 succeeded: " + response);
} catch (IOException e) {
System.out.println("Response 1 failed: " + e);
}
try {
Response response = client.clone() // Clone to make a customized OkHttp for this request.
.setReadTimeout(3000, TimeUnit.MILLISECONDS)
.newCall(request)
.execute();
System.out.println("Response 2 succeeded: " + response);
} catch (IOException e) {
System.out.println("Response 2 failed: " + e);
}
}
處理驗證
OkHttp會自動重試未驗證的請求。當響應是401 Not Authorized時,Authenticator會被要求提供證書。Authenticator的實現中需要建立一個新的包含證書的請求。如果沒有證書可用,返回null來跳過嘗試。
public List challenges()
Returns the authorization challenges appropriate for this response’s code. If the response code is 401 unauthorized, this returns the “WWW-Authenticate” challenges. If the response code is 407 proxy unauthorized, this returns the “Proxy-Authenticate” challenges. Otherwise this returns an empty list of challenges.
當需要實現一個Basic challenge, 使用Credentials.basic(username, password)來編碼請求頭。
private final OkHttpClient client = new OkHttpClient();
public void run() throws Exception {
client.setAuthenticator(new Authenticator() {
@Override public Request authenticate(Proxy proxy, Response response) {
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();
}
@Override public Request authenticateProxy(Proxy proxy, Response response) {
return null; // Null indicates no attempt to authenticate.
}
});
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());
}