Header 设置
@Test
public void whenSetHeader_thenCorrect() throws IOException {
Request request = new Request.Builder()
.url(SAMPLE_URL)
.addHeader("Content-Type", "application/json")
.build();
Call call = client.newCall(request);
Response response = call.execute();
response.close();
}Copy to clipboardErrorCopiedCopy to clipboardErrorCopiedCopy to clipboardErrorCopied
在此示例中,我们将看到如何在客户端本身上配置默认标头,而不是在每个请求上都设置默认标头。例如,如果要为每个请求设置内容类型 application/json,则需要为客户端设置拦截器。方法如下:
@Test
public void whenSetDefaultHeader_thenCorrect()
throws IOException {
OkHttpClient client = new OkHttpClient.Builder()
.addInterceptor(
new DefaultContentTypeInterceptor("application/json"))
.build();
Request request = new Request.Builder()
.url(SAMPLE_URL)
.build();
Call call = client.newCall(request);
Response response = call.execute();
response.close();
}Copy to clipboardErrorCopiedCopy to clipboardErrorCopiedCopy to clipboardErrorCopied
这是 DefaultContentTypeInterceptor,它是 Interceptor 的扩展版本:
public class DefaultContentTypeInterceptor implements Interceptor {
public Response intercept(Interceptor.Chain chain) throws IOException {
Request originalRequest = chain.request();
Request requestWithUserAgent = originalRequest
.newBuilder()
.header("Content-Type", contentType)
.build();
return chain.proceed(requestWithUserAgent);
}
}Copy to clipboardErrorCopiedCopy to clipboardErrorCopiedCopy to clipboardErrorCopied
避免重定向
在此示例中,我们将看到如何配置 OkHttpClient 以停止跟随重定向。默认情况下,如果使用 HTTP 301 永久移动响应了 GET 请求,则会自动遵循重定向。在某些用例中,可能会很好,但是在某些用例中肯定是不需要的。为了实现此行为,在构建客户端时,需要将 followRedirects 设置为 false。请注意,响应将返回 HTTP 301 状态代码:
@Test
public void whenSetFollowRedirects_thenNotRedirected()
throws IOException {
OkHttpClient client = new OkHttpClient().newBuilder()
.followRedirects(false)
.build();
Request request = new Request.Builder()
.url("http://t.co/I5YYd9tddw")
.build();
Call call = client.newCall(request);
Response response = call.execute();
assertThat(response.code(), equalTo(301));
}Copy to clipboardErrorCopiedCopy to clipboardErrorCopiedCopy to clipboardErrorCopied
如果我们使用 true 参数打开重定向(或将其完全删除),则客户端将遵循重定向,并且测试将失败,因为返回码将为 HTTP 200。
超时与取消
当对方无法访问时,请使用超时使呼叫失败。网络故障可能是由于客户端连接问题,服务器可用性问题或两者之间的任何原因引起的。OkHttp 支持连接,读取和写入超时。在此示例中,我们以 1 秒的 readTimeout 构建客户端,而以 2 秒的延迟提供 URL:
@Test
public void whenSetRequestTimeout_thenFail()
throws IOException {
OkHttpClient client = new OkHttpClient.Builder()
.readTimeout(1, TimeUnit.SECONDS)
.build();
Request request = new Request.Builder()
.url(BASE_URL + "/delay/2")
.build();
Call call = client.newCall(request);
Response response = call.execute();
assertThat(response.code(), equalTo(200));
}Copy to clipboardErrorCopiedCopy to clipboardErrorCopiedCopy to clipboardErrorCopied
使用 Call.cancel() 立即停止正在进行的呼叫。如果线程当前正在写入请求或读取响应,则将抛出 IOException。当不再需要通话时,使用此功能可以节省网络。例如,当您的用户离开应用程序时:
@Test(expected = IOException.class)
public void whenCancelRequest_thenCorrect()
throws IOException {
ScheduledExecutorService executor
= Executors.newScheduledThreadPool(1);
Request request = new Request.Builder()
.url(BASE_URL + "/delay/2")
.build();
int seconds = 1;
long startNanos = System.nanoTime();
Call call = client.newCall(request);
executor.schedule(() -> {
logger.debug("Canceling call: "
+ (System.nanoTime() - startNanos) / 1e9f);
call.cancel();
logger.debug("Canceled call: "
+ (System.nanoTime() - startNanos) / 1e9f);
}, seconds, TimeUnit.SECONDS);
logger.debug("Executing call: "
+ (System.nanoTime() - startNanos) / 1e9f);
Response response = call.execute();
logger.debug(Call was expected to fail, but completed: "
+ (System.nanoTime() - startNanos) / 1e9f, response);
}Copy to clipboardErrorCopiedCopy to clipboardErrorCopiedCopy to clipboardErrorCopied
响应缓存
要创建一个缓存,我们需要一个我们可以读写的缓存目录,以及对缓存大小的限制。客户端将使用它来缓存响应:
@Test
public void whenSetResponseCache_thenCorrect()
throws IOException {
int cacheSize = 10 * 1024 * 1024;
File cacheDirectory = new File("src/test/resources/cache");
Cache cache = new Cache(cacheDirectory, cacheSize);
OkHttpClient client = new OkHttpClient.Builder()
.cache(cache)
.build();
Request request = new Request.Builder()
.url("http://publicobject.com/helloworld.txt")
.build();
Response response1 = client.newCall(request).execute();
logResponse(response1);
Response response2 = client.newCall(request).execute();
logResponse(response2);
}Copy to clipboardErrorCopiedCopy to clipboardErrorCopiedCopy to clipboardErrorCopied
启动测试后,第一个调用的响应将不会被缓存。对方法 cacheResponse 的调用将返回 null,而对方法 networkResponse 的调用将返回来自网络的响应。此外,缓存文件夹中将填充缓存文件。第二次调用执行将产生相反的效果,因为响应已经被缓存了。这意味着对 networkResponse 的调用将返回 null,而对 cacheResponse 的调用将返回来自缓存的响应。为了防止响应使用缓存,请使用 CacheControl.FORCE_NETWORK。若要阻止它使用网络,请使用 CacheControl.FORCE_CACHE。
Get
OkHttpClient client = new OkHttpClient();
String run(String url) throws IOException {
Request request = new Request.Builder()
.url(url)
.build();
try (Response response = client.newCall(request).execute()) {
return response.body().string();
}
}Copy to clipboardErrorCopied
要发送同步的 GET 请求,我们需要基于 URL 构建一个 Request 对象并进行调用。执行之后,OkHttp 返回一个 Response 实例:
@Test
public void whenGetRequest_thenCorrect() throws IOException {
Request request = new Request.Builder()
.url(BASE_URL + "/date")
.build();
Call call = client.newCall(request);
Response response = call.execute();
assertThat(response.code(), equalTo(200));
}Copy to clipboardErrorCopied
现在,要进行异步 GET,我们需要排队一个 Call。回调使我们可以读取可读的响应,这是在响应头准备好之后发生的。读取响应正文可能仍然会阻塞。OkHttp 当前不提供任何异步 API 来部分接收响应正文:
@Test
public void whenAsynchronousGetRequest_thenCorrect() {
Request request = new Request.Builder()
.url(BASE_URL + "/date")
.build();
Call call = client.newCall(request);
call.enqueue(new Callback() {
public void onResponse(Call call, Response response)
throws IOException {
// ...
}
public void onFailure(Call call, IOException e) {
fail();
}
});
}Copy to clipboardErrorCopied
最后,要将查询参数添加到 GET 请求中,我们可以利用 HttpUrl.Builder。我们可以将构建好的请求传递给我们的 Request 对象:
@Test
public void whenGetRequestWithQueryParameter_thenCorrect()
throws IOException {
HttpUrl.Builder urlBuilder
= HttpUrl.parse(BASE_URL + "/ex/bars").newBuilder();
urlBuilder.addQueryParameter("id", "1");
String url = urlBuilder.build().toString();
Request request = new Request.Builder()
.url(url)
.build();
Call call = client.newCall(request);
Response response = call.execute();
assertThat(response.code(), equalTo(200));
}Copy to clipboardErrorCopied
Post
public static final MediaType JSON
= MediaType.get("application/json; charset=utf-8");
OkHttpClient client = new OkHttpClient();
String post(String url, String json) throws IOException {
RequestBody body = RequestBody.create(JSON, json);
Request request = new Request.Builder()
.url(url)
.post(body)
.build();
try (Response response = client.newCall(request).execute()) {
return response.body().string();
}
}Copy to clipboardErrorCopied
表单构建
让我们看一个简单的 POST 请求,在其中构建一个 RequestBody 来发送参数 username 和 password:
@Test
public void whenSendPostRequest_thenCorrect()
throws IOException {
RequestBody formBody = new FormBody.Builder()
.add("username", "test")
.add("password", "test")
.build();
Request request = new Request.Builder()
.url(BASE_URL + "/users")
.post(formBody)
.build();
Call call = client.newCall(request);
Response response = call.execute();
assertThat(response.code(), equalTo(200));
}Copy to clipboardErrorCopied
我们也可以构建 MultipartBody 的请求:
@Test
public void whenSendMultipartRequest_thenCorrect()
throws IOException {
RequestBody requestBody = new MultipartBody.Builder()
.setType(MultipartBody.FORM)
.addFormDataPart("username", "test")
.addFormDataPart("password", "test")
.addFormDataPart("file", "file.txt",
RequestBody.create(MediaType.parse("application/octet-stream"),
new File("src/test/resources/test.txt")))
.build();
Request request = new Request.Builder()
.url(BASE_URL + "/users/multipart")
.post(requestBody)
.build();
Call call = client.newCall(request);
Response response = call.execute();
assertThat(response.code(), equalTo(200));
}Copy to clipboardErrorCopied
认证
如果要验证请求,可以使用 Credentials.basic 构建器将凭证添加到标头。在这个简单的示例中,我们还将发送一个 String 作为请求的主体:
@Test
public void whenSendPostRequestWithAuthorization_thenCorrect()
throws IOException {
String postBody = "test post";
Request request = new Request.Builder()
.url(URL_SECURED_BY_BASIC_AUTHENTICATION)
.addHeader("Authorization", Credentials.basic("username", "password"))
.post(RequestBody.create(
MediaType.parse("text/x-markdown), postBody))
.build();
Call call = client.newCall(request);
Response response = call.execute();
assertThat(response.code(), equalTo(200));
}Copy to clipboardErrorCopied
JSON
为了在请求正文中发送 JSON,我们必须设置其媒体类型 application/json。我们可以使用 RequestBody.create 构建器来做到这一点:
@Test
public void whenPostJson_thenCorrect() throws IOException {
String json = "{\"id\":1,\"name\":\"John\"}";
RequestBody body = RequestBody.create(
MediaType.parse("application/json"), json);
Request request = new Request.Builder()
.url(BASE_URL + "/users/detail")
.post(body)
.build();
Call call = client.newCall(request);
Response response = call.execute();
assertThat(response.code(), equalTo(200));
}Copy to clipboardErrorCopied
编码
如果我们要使用其他字符编码,可以将其作为 MediaType.parse()
的第二个参数传递:
@Test
public void whenPostJsonWithoutCharset_thenCharsetIsUtf8() throws IOException {
final String json = "{\"id\":1,\"name\":\"John\"}";
final RequestBody body = RequestBody.create(
MediaType.parse("application/json"), json);
String charset = body.contentType().charset().displayName();
assertThat(charset, equalTo("UTF-8"));
}
@Test
public void whenPostJsonWithUtf16Charset_thenCharsetIsUtf16() throws IOException {
final String json = "{\"id\":1,\"name\":\"John\"}";
final RequestBody body = RequestBody.create(
MediaType.parse("application/json; charset=utf-16"), json);
String charset = body.contentType().charset().displayName();
assertThat(charset, equalTo("UTF-16"));
}
文件上传
在此示例中,我们将看到如何上传文件。我们将使用 MultipartBody.Builder 上传 test.ext 文件:
@Test
public void whenUploadFile_thenCorrect() throws IOException {
RequestBody requestBody = new MultipartBody.Builder()
.setType(MultipartBody.FORM)
.addFormDataPart("file", "file.txt",
RequestBody.create(MediaType.parse("application/octet-stream"),
new File("src/test/resources/test.txt")))
.build();
Request request = new Request.Builder()
.url(BASE_URL + "/users/upload")
.post(requestBody)
.build();
Call call = client.newCall(request);
Response response = call.execute();
assertThat(response.code(), equalTo(200));
}Copy to clipboardErrorCopied
文件上传进度
@Test
public void whenGetUploadFileProgress_thenCorrect()
throws IOException {
RequestBody requestBody = new MultipartBody.Builder()
.setType(MultipartBody.FORM)
.addFormDataPart("file", "file.txt",
RequestBody.create(MediaType.parse("application/octet-stream"),
new File("src/test/resources/test.txt")))
.build();
ProgressRequestWrapper.ProgressListener listener
= (bytesWritten, contentLength) -> {
float percentage = 100f * bytesWritten / contentLength;
assertFalse(Float.compare(percentage, 100) > 0);
};
ProgressRequestWrapper countingBody
= new ProgressRequestWrapper(requestBody, listener);
Request request = new Request.Builder()
.url(BASE_URL + "/users/upload")
.post(countingBody)
.build();
Call call = client.newCall(request);
Response response = call.execute();
assertThat(response.code(), equalTo(200));
}Copy to clipboardErrorCopied
ProgressListener 接口定义如下:
public interface ProgressListener {
void onRequestProgress(long bytesWritten, long contentLength);
}Copy to clipboardErrorCopied
ProgressRequestWrapper 接口定义如下:
public class ProgressRequestWrapper extends RequestBody {
@Override
public void writeTo(BufferedSink sink) throws IOException {
BufferedSink bufferedSink;
countingSink = new CountingSink(sink);
bufferedSink = Okio.buffer(countingSink);
delegate.writeTo(bufferedSink);
bufferedSink.flush();
}
}Copy to clipboardErrorCopied
protected class CountingSink extends ForwardingSink {
private long bytesWritten = 0;
public CountingSink(Sink delegate) {
super(delegate);
}
@Override
public void write(Buffer source, long byteCount) throws IOException {
super.write(source, byteCount);
bytesWritten += byteCount;
listener.onRequestProgress(bytesWritten, contentLength());
}
}