OKHttp3.0的日常及入門
前言
Okhttp作為現在最火的一個網路請求框架,已經有無數牛人給出了工具類等等。
有別人寫好的工具類固然好,但是不光用要會用,還要止其所以然。
那麼直接取拉官方的程式碼,研究如何使用以及閱讀API說明,對加深理解OkHttp和了解基礎,就更有幫助了。
正文
OkHttp現在已經升級到3.0版本了,API較2.0還是有些不同的。稍後再說。
下面將通過一個一個的測試用例來說明和解釋API的用法,當然這些用力都是我照著example敲的,研究的過程中自然有了些瞭解,便記錄下來。
建立完工程,引入了包,別忘記建立Client例項。
OkHttpClient yOkHttpClient = new OkHttpClient();
那麼,OkHttpClient
是幹嘛用的呢?
簡單來說,通過OkHttpClient
可以傳送一個Http
請求,並讀取該Http
請求的響應,它是一個生產Call
的工廠。
此外,受益於一個共享的響應快取/執行緒池/複用的連線等因素,絕大多數應用使用一個OkHttpClient
例項,便可以滿足整個應用的Http
請求。
建立一個預設配置OkHttpClient
,可以使用預設的建構函式。或者通過new OkHttpClient.Builder()
方法來一步一步配置一個OkHttpClient
例項。另外,如果要求使用現有的例項,可以通過newBuilder()
方法來進行構造。
下面就是一個構造OkHttpClient
例項的簡單例子。
OkHttpClient client = ...
OkHttpClient clientWith30sTimeout = client.newBuilder()
.readTimeout(30, TimeUnit.SECONDS)
.build();
Response response = clientWith30sTimeout.newCall(request).execute();
看一下OkHttpClient
的原始碼,會發現快取/代理等等需求,一應俱全的按照類封裝到了Builder
中。
Dispatcher dispatcher; // 分發
Proxy proxy; // 代理
List<Protocol> protocols;
List<ConnectionSpec> connectionSpecs;
final List<Interceptor> interceptors = new ArrayList<>(); // 攔截器
final List<Interceptor> networkInterceptors = new ArrayList<>(); // 網路攔截器
ProxySelector proxySelector;
CookieJar cookieJar;
Cache cache; // 快取
InternalCache internalCache;
SocketFactory socketFactory;
SSLSocketFactory sslSocketFactory;
HostnameVerifier hostnameVerifier;
CertificatePinner certificatePinner;
Authenticator proxyAuthenticator; // 代理證書
Authenticator authenticator; // 證書
ConnectionPool connectionPool;
Dns dns; // DNS
boolean followSslRedirects;
boolean followRedirects;
boolean retryOnConnectionFailure;
int connectTimeout;
int readTimeout;
int writeTimeout;
簡單Get請求
程式碼:
@Test
public void testGet() throws IOException {
Request build = new Request.Builder()
.url("https://raw.github.com/square/okhttp/master/README.md")
.build();
Response response = yOkHttpClient.newCall(build)
.execute();
System.out.println(response);
}
結果:
在結果中列印了協議,結果碼,訊息結果,訪問的url地址等等。
Response{protocol=http/1.1, code=200, message=OK, url=https://raw.githubusercontent.com/square/okhttp/master/README.md}
簡單看一下Request
類,可以發現它代表一個Http
請求,需要注意的是Request
一旦build()
之後,便不可修改。
Request
的例項,主要通過new Request.Builder()
來一步一步構造的。看一下Builder
的程式碼。
public Builder() {
this.method = "GET";
this.headers = new Headers.Builder();
}
預設是Get
方法,此外還建立了頭資訊。值得注意的是Headers
類中是通過List<String> namesAndValues = new ArrayList<>(20)
,來存放頭資訊的,一開始我也很納悶,頭資訊都是一對一對的為什麼要用List
,看一下原始碼發現,在存取的時候都是將索引+2或者-2。並且頭資訊可以存在多個相同的Key資訊。
繼續看一下如何發起請求的。
yOkHttpClient.newCall(request)
跟到newCall()
方法中發現,又使用OkHttpClient
例項和Request
的例項,一起構造了一個RealCall
的例項。在跟進RealCall
程式碼中,可以簡單瞭解到RealCall
類簡單做了一個託管並通過Dispather
類對請求進行分發和執行,實際開啟執行緒發起請求的方法就在這個類中。
隨後又呼叫execute()
方法,拿到了一個響應。這個execute()
方法,實際上執行的就是RealCall
中的execute()
方法,那麼最後就呼叫了Dispatcher
的execute()
方法。
最後,再看一下Response
類的說明,Response
代表一個Http
的響應,這個類的例項不可修改。
致次,一個簡單的Get請求和說明就結束了,簡單跟入原始碼的原因在於,更清楚的瞭解OkHttpClient
的Api和執行原理。
簡單Post請求
提起post請求,必然是有請求體的。
程式碼:
String bowlingJson(String player1, String player2) {
return "{'winCondition':'HIGH_SCORE',"
+ "'name':'Bowling',"
+ "'round':4,"
+ "'lastSaved':1367702411696,"
+ "'dateStarted':1367702378785,"
+ "'players':["
+ "{'name':'" + player1 + "','history':[10,8,6,7,8],'color':-13388315,'total':39},"
+ "{'name':'" + player2 + "','history':[6,10,5,10,10],'color':-48060,'total':41}"
+ "]}";
}
@Test
public void testPost() throws IOException {
MediaType JSON = MediaType.parse("application/json; charset=utf-8");
RequestBody body = RequestBody.create(JSON, bowlingJson("Jesse", "Jake"));
Request request = new Request.Builder()
.url("http://www.roundsapp.com/post")
.post(body)
.build();
Response response = yOkHttpClient.newCall(request).execute();
System.out.println(response.body().string());
}
此處先給一個Http
請求頭大全的地址,非常好的。
MediaType
用於描述Http
請求和響應體的內容型別。對於Http
請求頭不瞭解的人,請看下面的圖(PS:我也不太懂)。
很明瞭吧,MediaType
代表的就是請求包體內容的型別。
例如,MediaType.parse("application/json; charset=utf-8");
這個就帶表請求體的型別為JSON格式的。
定義好資料型別,還要將其變為請求體,最後通過post()
方法,隨請求一併發出。
RequestBody body = RequestBody.create(JSON, bowlingJson("Jesse", "Jake"));
為請求指定頭資訊
程式碼:
@Test
public void testAccessHeaders() throws Exception {
Request request = new Request.Builder()
.url("https://api.github.com/repos/square/okhttp/issues")
// User-Agent User-Agent的內容包含發出請求的使用者資訊 User-Agent: Mozilla/5.0 (Linux; X11)
.header("User-Agent", "OkHttp Headers.java")
// Accept 指定客戶端能夠接收的內容型別 Accept: text/plain, text/html
.addHeader("Accept", "application/json; q=0.5")
.addHeader("Accept", "application/vnd.github.v3+json")
.build();
Response response = yOkHttpClient.newCall(request).execute();
if (response.isSuccessful()) {
System.out.println("Server: " + response.header("Server"));
System.out.println("Date: " + response.header("Date"));
System.out.println("Vary: " + response.headers("Vary"));
}
}
響應結果:
Server: GitHub.com
Date: Thu, 28 Jan 2016 15:41:13 GMT
Vary: [Accept, Accept-Encoding]
這個例子主要是Http
請求頭資訊的一個展示。
對於header(String name, String value)
方法,使用name和value設定一個頭資訊,如果請求中已經存在響應的資訊那麼直接替換掉。
而addHeader(String name, String value)
,如果請求頭中已經存在name的name-value
,那麼還會繼續新增,請求頭中便會存在多個name相同而value不同的“鍵值對”。
至於name的取值說明,可以檢視這個請求頭大全。
非同步Get請求
程式碼如下:
@Test
public void testAsyncGet() {
final Request request = new Request.Builder().url("http://publicobject.com/helloworld.txt").build();
System.out.println(request);
yOkHttpClient.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 {
System.out.println("call = [" + call + "], response = [" + response + "]");
if (!response.isSuccessful()) {
throw new IOException("" + response);
}
Headers headers = response.headers();
for (int i = 0; i < headers.size(); i++) {
System.out.println(headers.name(i) + ": " + headers.value(i));
}
System.out.println(response.body().string());
}
});
}
與同步方法不同,呼叫的是enqueue()
方法,其實際上呼叫的就是RealCall
中的enqueue()
方法,那麼實際上就是Dispatcher
的enqueue()
方法。
synchronized void enqueue(AsyncCall call) {
if (runningAsyncCalls.size() < maxRequests && runningCallsForHost(call) < maxRequestsPerHost) {
runningAsyncCalls.add(call);
executorService().execute(call);
} else {
readyAsyncCalls.add(call);
}
}
新增授權證書
程式碼如下:
@Test
public void testAuthenticate() throws IOException {
yOkHttpClient = 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");
// HTTP授權的授權證書 Authorization: Basic QWxhZGRpbjpvcGVuIHNlc2FtZQ==
return response
.request()
.newBuilder()
.header("Authorization", credential)
.build();
}
})
.build();
Request request = new Request.Builder().url("http://publicobject.com/secrets/hellosecret.txt").build();
Response response = yOkHttpClient.newCall(request).execute();
if (response.isSuccessful()) {
System.out.println(response.body().string());
}
}
結果如下:
Authenticating for response: Response{protocol=http/1.1, code=401, message=Unauthorized, url=https://publicobject.com/secrets/hellosecret.txt}
Challenges: [Basic realm="OkHttp Secrets"]
通過Authenticator
類,可以響應來自遠端或者代理伺服器的授權驗證,通常情況會返回一個授權頭以做驗證;亦或是返回空表示拒絕驗證。簡單來說,你要訪問一個服務,但是你要對方的驗證。通過Authenticator
類來代理一個認證請求,並使用Credentials.basic()
來構造一個證書。
請求快取
程式碼如下:
@Test
public void testCacheResponse() throws IOException {
int cacheSize = 10 * 1024 * 1024;
Cache cache = new Cache(new File("bzh.tmp"), cacheSize);
yOkHttpClient = new OkHttpClient.Builder()
.cache(cache)
.build();
Request request = new Request.Builder()
.url("http://publicobject.com/helloworld.txt").build();
Response response1 = yOkHttpClient.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 = yOkHttpClient.newCall(request).execute();
if (!response1.isSuccessful()) throw new IOException("Unexpected code " + response1);
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));
}
說起這個快取那就更有趣了,okhttp自帶快取,是不是很厲害。
對於OkHttpClient
來說,只要為其設定瞭如下程式碼,那麼便有了快取功能。
Cache cache = new Cache(new File("bzh.tmp"), cacheSize);
yOkHttpClient = new OkHttpClient.Builder()
.cache(cache)
.build();
其中最為關鍵的就是Cache
類,他的主要作用在於快取HTTP和HTTPS響應檔案,所以他們可以重複使用,節省時間和頻寬。
強制使用網路請求
Request request = new Request.Builder()
// or .cacheControl(CacheControl.FORCE_NETWORK)
.cacheControl(new CacheControl.Builder().noCache().build())
.url("http://publicobject.com/helloworld.txt")
.build();
其中CacheControl.FORCE_NETWORK
和new CacheControl.Builder().noCache().build()
是等效的。
測試結果如下:
Response 1 response: Response{protocol=http/1.1, code=200, message=OK, url=https://publicobject.com/helloworld.txt}
Response 1 cache response: null
Response 1 network response: Response{protocol=http/1.1, code=200, message=OK, url=https://publicobject.com/helloworld.txt}
Response 2 response: Response{protocol=http/1.1, code=200, message=OK, url=https://publicobject.com/helloworld.txt}
Response 2 cache response: null
Response 2 network response: Response{protocol=http/1.1, code=200, message=OK, url=https://publicobject.com/helloworld.txt}
Response 2 equals Response 1 ? true
可以看到,響應中只有網路響應,而快取響應不存在。
另外,如果需要強制伺服器驗證一下快取,可以使用如下程式碼:
Request request = new Request.Builder()
.cacheControl(new CacheControl.Builder()
.maxAge(0, TimeUnit.SECONDS)
.build())
.url("http://publicobject.com/helloworld.txt")
.build();
測試執行結果如下:
Response 1 response: Response{protocol=http/1.1, code=200, message=OK, url=https://publicobject.com/helloworld.txt}
Response 1 cache response: Response{protocol=http/1.1, code=200, message=OK, url=https://publicobject.com/helloworld.txt}
Response 1 network response: Response{protocol=http/1.1, code=304, message=Not Modified, url=https://publicobject.com/helloworld.txt}
Response 2 response: Response{protocol=http/1.1, code=200, message=OK, url=https://publicobject.com/helloworld.txt}
Response 2 cache response: Response{protocol=http/1.1, code=200, message=OK, url=https://publicobject.com/helloworld.txt}
Response 2 network response: Response{protocol=http/1.1, code=304, message=Not Modified, url=https://publicobject.com/helloworld.txt}
Response 2 equals Response 1 ? true
可以看到,響應中快取和網路響應都存在。
強制使用快取
Request request = new Request.Builder()
.cacheControl(new CacheControl.Builder()
.onlyIfCached()
.build())
.url("http://publicobject.com/helloworld.txt")
.build();
Response forceCacheResponse = client.newCall(request).execute();
if (forceCacheResponse.code() != 504) {
// The resource was cached! Show it.
} else {
// The resource was not cached.
}
其中,new CacheControl.Builder().onlyIfCached().build()
和CacheControl.FORCE_CACHE
是一致的。
Response 1 response: Response{protocol=http/1.1, code=200, message=OK, url=https://publicobject.com/helloworld.txt}
Response 1 cache response: Response{protocol=http/1.1, code=200, message=OK, url=https://publicobject.com/helloworld.txt}
Response 1 network response: null
Response 2 response: Response{protocol=http/1.1, code=200, message=OK, url=https://publicobject.com/helloworld.txt}
Response 2 cache response: Response{protocol=http/1.1, code=200, message=OK, url=https://publicobject.com/helloworld.txt}
Response 2 network response: null
Response 2 equals Response 1 ? true
根據結果可以發現,響應中只有快取響應而無網路響應。
另外還有一種情況,如果快取過期了,而又無法請求到網路怎麼辦?
可以通過如下程式碼,繼續使用過期的快取。
Request request = new Request.Builder()
.cacheControl(new CacheControl.Builder()
.maxStale(365, TimeUnit.DAYS)
.build())
.url("http://publicobject.com/helloworld.txt")
.build();
值得一提的
如果在構造OkHttpClient
時,沒有指定cache
,那麼便不會有快取功能。
取消任務
@Test
public void testCancelCall() {
Request request = new Request.Builder()
.url("http://httpbin.org/delay/2")
.build();
final long startNanos = System.nanoTime();
final Call call = yOkHttpClient.newCall(request);
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);
}
}
測試結果:
0.02 Executing call.
1.01 Canceling call.
1.01 Canceled call.
1.01 Call failed as expected: java.net.SocketException: Socket closed
證書
@Test
public void testCertificatePinning() throws IOException {
yOkHttpClient = new OkHttpClient.Builder()
.certificatePinner(new CertificatePinner.Builder()
.add("publicobject.com", "sha1/DmxUShsZuNiqPQsX2Oi9uv2sCnw=")
.add("publicobject.com", "sha1/SXxoaOSEzPC6BgGmxAt/EAcsajw=")
.add("publicobject.com", "sha1/blhOM3W9V/bVQhsWAcLYwPU6n24=")
.add("publicobject.com", "sha1/T5x9IXmcrQ7YuQxXnxoCmeeQ84c=")
.build())
.build();
Request request = new Request.Builder().url("https://publicobject.com/robots.txt").build();
Response response = yOkHttpClient.newCall(request).execute();
if (!response.isSuccessful()) throw new IOException("Unexpected code " + response);
for (Certificate certificate : response.handshake().peerCertificates()) {
System.out.println(CertificatePinner.pin(certificate));
}
}
測試結果:
Connected to the target VM, address: '127.0.0.1:49312', transport: 'socket'
sha1/DmxUShsZuNiqPQsX2Oi9uv2sCnw=
sha1/SXxoaOSEzPC6BgGmxAt/EAcsajw=
sha1/blhOM3W9V/bVQhsWAcLYwPU6n24=
sha1/T5x9IXmcrQ7YuQxXnxoCmeeQ84c=
Disconnected from the target VM, address: '127.0.0.1:49312', transport: 'socket'
超時設定
程式碼如下
@Test
public void testTimeout() throws IOException {
yOkHttpClient = new OkHttpClient.Builder()
.connectTimeout(10, TimeUnit.SECONDS)
.writeTimeout(10, TimeUnit.SECONDS)
.readTimeout(30, TimeUnit.SECONDS)
.build();
Request request = new Request.Builder()
.url("http://httpbin.org/delay/2") // This URL is served with a 2 second delay.
.build();
Response response = yOkHttpClient.newCall(request).execute();
System.out.println("Response completed: " + response);
}
結果如下:
Response completed: Response{protocol=http/1.1, code=200, message=OK, url=http://httpbin.org/delay/2}
新增日誌攔截器
private static class LoggingInerceptor implements Interceptor {
@Override
public Response intercept(Chain chain) throws IOException {
long t1 = System.nanoTime();
Request request = chain.request();
System.out.println(String.format("Sending request %s on %s%n%s",
request.url(), chain.connection(), request.headers()));
Response response = chain.proceed(request);
long t2 = System.nanoTime();
System.out.println(String.format("Received response for %s in %.1fms%n%s",
request.url(), (t2 - t1) / 1e6d, response.headers()));
return response;
}
}
@Test
public void testLoggingInterceptor() throws IOException {
yOkHttpClient = new OkHttpClient.Builder()
.addInterceptor(new LoggingInerceptor())
.build();
Request request = new Request.Builder()
.url("https://publicobject.com/helloworld.txt")
.build();
Response execute = yOkHttpClient.newCall(request).execute();
execute.body().close();
}
測試結果:
Sending request https://publicobject.com/helloworld.txt on null
Received response for https://publicobject.com/helloworld.txt in 2555.9ms
Server: nginx/1.4.6 (Ubuntu)
Date: Sat, 30 Jan 2016 03:49:20 GMT
Content-Type: text/plain
Content-Length: 1759
Last-Modified: Tue, 27 May 2014 02:35:47 GMT
Connection: keep-alive
ETag: "5383fa03-6df"
Accept-Ranges: bytes
OkHttp-Sent-Millis: 1454126228626
OkHttp-Received-Millis: 1454126229516
可以瞭解,在攔截器的intercept()
方法中,通過chain.request()
可以拿到請求物件;通過chain.proceed(request)
可以拿到響應結果;
Gson解析
@Test
public void testParseResponseGson() throws IOException {
Gson gson = new Gson();
Request request = new Request.Builder()
.url("https://api.github.com/gists/c2a7c39532239ff261be")
.build();
Response response = yOkHttpClient.newCall(request).execute();
if (!response.isSuccessful()) throw new IOException("Unexpected code " + response);
Gist gist = gson.fromJson(response.body().charStream(), Gist.class);
response.body().close();
for (Map.Entry<String, GistFile> entry : gist.files.entrySet()) {
System.out.println(entry.getKey());
System.out.println(entry.getValue().content);
}
}
這裡面最主要的是呼叫了response.body().charStream()
。大致看一下ResponseBody
類。
- ResponseBody儲存了服務端發往客戶端的原始位元組流。
- ResponseBody必須被關閉
- 每一個響應體都是一個有限的資源支援。如果沒有關閉的響應體將洩漏這些資源,並可能最終導致應用程式的速度慢下來或崩潰。通過
close()
,bytestream()
關閉響應體.reader().close()
。()。其中bytes()
和string()
方法會自動關閉響應體。 - 響應主體只能被消耗一次。
- 這個類可以用於非常大的響應流。例如,常見的視訊流應用的要求。
- 因為這個ResponseBody不緩衝記憶體中的全部響應,應用程式不能重新讀取響應的位元組數。可以利用
bytes()
orstring()
,source()
,byteStream()
, orcharStream()
等方法,將流內容讀入到記憶體中。
一個請求多個Client配置
@Test
public void testPerCallSettings() {
Request request = new Request.Builder()
.url("http://httpbin.org/delay/1") // This URL is served with a 1 second delay.
.build();
try {
OkHttpClient copy = yOkHttpClient.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 = yOkHttpClient.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);
}
}
執行結果:
Response 1 failed: java.net.SocketTimeoutException: timeout
Response 2 succeeded: Response{protocol=http/1.1, code=200, message=OK, url=http://httpbin.org/delay/1}
上傳小檔案
@Test
public void testPostFile() throws IOException {
MediaType MEDIA_TYPE_MARKDOWN = MediaType.parse("text/x-markdown; charset=utf-8");
File file = new File("記錄.md");
Request request = new Request.Builder()
.url("https://api.github.com/markdown/raw")
.post(RequestBody.create(MEDIA_TYPE_MARKDOWN, file))
.build();
Response response = yOkHttpClient.newCall(request).execute();
if (!response.isSuccessful()) {
throw new IOException("Unexpected code " + response);
}
System.out.println(response.body().string());
}
上傳表單
@Test
public void testPostForm() throws IOException {
FormBody formBody = new FormBody.Builder()
.add("search", "biezhihua")
.build();
Request request = new Request.Builder()
.url("https://en.wikipedia.org/w/index.php")
.post(formBody)
.build();
Response response = yOkHttpClient.newCall(request)
.execute();
if (!response.isSuccessful()) {
throw new IOException("Unexcepted code " + response);
}
System.out.println(response.body().string());
}
主要利用了FormBody
類,不用猜我們也能知道,其繼承了RequestBody
類,並內建了MediaType
型別,用且用集合儲存鍵值對資料。
上傳表單
@Test
public void testPostMultipart() throws IOException {
String IMGUR_CLIENT_ID = "9199fdef135c122";
MediaType MEDIA_TYPE_PNG = MediaType.parse("image/png");
MultipartBody 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 = yOkHttpClient.newCall(request)
.execute();
if (!response.isSuccessful()) {
throw new IOException("Unexpected code" + request);
}
System.out.println(response.body().string());
}
上傳流
@Test
public void testPostStreaming() throws IOException {
final MediaType MEDIA_TYPE_MARKDOWN = MediaType.parse("text/x-markdown; charset=utf-8");
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 = yOkHttpClient.newCall(request).execute();
if (!response.isSuccessful()) throw new IOException("Unexpected code " + response);
System.out.println(response.body().string());
}
其實以上這幾個測試,實質上都是一個東西,就是對RequestBody
做的封裝或者重寫,都是將本地資料放入到Http協議的請求體中,然後傳送到服務端。其中唯一不同,就是別人幫你寫好的;或是你自己重寫的幾個方法。
上傳字串
@Test
public void testPostString() throws IOException {
MediaType MEDIA_TYPE_MARKDOWN = MediaType.parse("text/x-markdown; charset=utf-8");
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 = yOkHttpClient.newCall(request).execute();
if (!response.isSuccessful()) throw new IOException("Unexpected code " + response);
System.out.println(response.body().string());
}
載入進度
程式碼如下
// 監聽進度的介面
interface ProgressListener {
void update(long bytesRead, long contentLength, boolean done);
// 處理進度的自定義響應體
static class ProgressResponseBody extends ResponseBody {
private final ResponseBody responseBody;
private final ProgressListener progressListener;
private BufferedSource bufferedSource;
public ProgressResponseBody(ResponseBody responseBody, ProgressListener progressListener) {
this.responseBody = responseBody;
this.progressListener = progressListener;
}
@Override
public MediaType contentType() {
return responseBody.contentType();
}
@Override
public long contentLength() {
return responseBody.contentLength();
}
@Override
public BufferedSource source() {
if (bufferedSource == null) {
bufferedSource = Okio.buffer(source(responseBody.source()));
}
return bufferedSource;
}
private Source source(Source source) {
return new ForwardingSource(source) {
long totalBytesRead = 0L;
@Override
public long read(Buffer sink, long byteCount) throws IOException {
long bytesRead = super.read(sink, byteCount);
totalBytesRead += bytesRead != -1 ? bytesRead : 0;
progressListener.update(totalBytesRead, responseBody.contentLength(), bytesRead == -1);
return bytesRead;
}
};
}
}
}
// 為客戶端例項新增網路攔截器,並相應回撥。
@Test
public void testProgress() throws IOException {
Request request = new Request.Builder()
.url("https://publicobject.com/helloworld.txt")
.build();
final ProgressListener progressListener = new ProgressListener() {
@Override
public void update(long bytesRead, long contentLength, boolean done) {
System.out.println("bytesRead = [" + bytesRead + "], contentLength = [" + contentLength + "], done = [" + done + "]");
System.out.format("%d%% done\n", (100 * bytesRead) / contentLength);
}
};
yOkHttpClient = new OkHttpClient.Builder()
.addNetworkInterceptor(new Interceptor() {
@Override
public Response intercept(Chain chain) throws IOException {
Response originalResponse = chain.proceed(chain.request());
return originalResponse.newBuilder()
.body(new ProgressResponseBody(originalResponse.body(), progressListener))
.build();
}
})
.build();
Response response = yOkHttpClient.newCall(request).execute();
if (!response.isSuccessful()) throw new IOException("Unexpected code " + response);
System.out.println(response.body().string());
}