原文位置:https://github.com/square/okhttp/wiki/Recipes
We‘ve written some recipes that demonstrate证明、展示 how to solve common problems with OkHttp. Read through them to learn about how everything works together. Cut-and-paste these examples freely; that‘s what they‘re for.
本文将演示如何使用OkHttp来解决常见问题,了解每件事是如何一起工作的。
Synchronous Get,同步Get
Download a file, print its headers, and print its response body as a string.
下载一个文件,打印它的响应结果的响应头,并将它的响应体作为字符串打印出来。
The string() method on response body is convenient and efficient for small documents. But if the response body is large (greater than 1 MiB), avoid string() because it will load the entire document into memory. In that case, prefer to process the body as a stream.
响应体中的string()方法对于小文档来时是方便和高效的。但是如果响应体体积很大(大于1Mib),则应避免使用string()方法,因为它将把整个文档加载到内存中。在这种情况下,可以将响应体作为流来处理。
//同步Get
public void synchronousGet() 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());
}
Asynchronous Get,异步Get
Download a file on a worker thread, and get called back when the response is readable. The callback is made after the response headers are ready. Reading the response body may still block. OkHttp doesn‘t currently offer asynchronous APIs to receive a response body in parts.
在工作线程上下载一个文件,当响应结果可读时就会得到回调调用。回调是在响应头准备好之后进行的。读取响应体可能仍然会阻塞。在一些地方,OkHttp目前还没有提供异步api来接受响应主体。
//异步Get
public void asynchronousGet() throws Exception {
Request request = new Request.Builder()
.url("http://publicobject.com/helloworld.txt")
.build();
client.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 {
if (!response.isSuccessful()) throw new IOException("Unexpected code " + response);
Headers responseHeaders = response.headers();
for (int i = 0, size = responseHeaders.size(); i < size; i++) {
System.out.println("【" + responseHeaders.name(i) + "】" + responseHeaders.value(i));
}
System.out.println("【响应结果】" + response.body().string());
}
});
}
Accessing Headers,访问Header
典型的HTTP头部使用Map
//访问Header
public void accessingHeaders() 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"));
}
Posting a String,Post发送字符串
本例子使用HTTP POST将请求体发送到服务器,将一个markdown文档发送到一个将markdown作为HTML的web服务器上。因为整个请求体在内存中,所以使用这个API避免发送(大于1个Mib)文档。
//Post发送字符串
public void postString() 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 Streaming,Post发送流
在本例中将一个请求体作为流来进行发送。这个请求体的内容在被写入时生成。这个示例的请求体数据直接流到Okio缓冲池中,在程序中将作为OutputStream,可以从BufferedSink.outputStream()方法中获得。
//Post发送流
public void postStreaming() 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());
}
Posting a File,Post发送文件
使用OkHttp可以很容易将文件作为请求体来进行发送。
//Post发送文件
public void postFile() throws Exception {
File file = new File(Environment.getExternalStorageDirectory(), "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());
}
Posting form parameters,发布形式参数
使用FormBody.Builder表单建造者来构建一个像HTML<form>标签一样工作的请求体。键值对将使用与HTML兼容的表单URL编码进行编码。
//发布形式参数
public void postFormParameters() 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());
}
Posting a multipart request,发布多请求体的请求
MultipartBody.Builder可以构建与HTML文件上传表单兼容的复杂请求体。多部件请求体中的每一个部件本身就是一个请求体,并且可以定义它自己的头部。如果存在这样的请求体部件,这些头部应该描述请求体部件主体,比如它的内容配置Content-Disposition。同时如果这些请求体可用,那么内容长度Content-Length和内容类型Content-Type将自动添加到头部字段。
//发布多请求体的请求
public void postMultipartRequest() throws Exception {
RequestBody requestBody = new MultipartBody.Builder()
.setType(MultipartBody.FORM)
.addFormDataPart("title", "Square Logo")
.addFormDataPart("image", "logo-square.png",
RequestBody.create(MEDIA_TYPE_PNG, new File(Environment.getExternalStorageDirectory(), "logo.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());
}
Parse a JSON Response With Gson,使用Gson来解析JSON响应
Gson是一个用于在JSON和Java对象之间进行转换的一个方便的API。在本例中将使用Gson来解码来自GitHub Api的JSON响应。
注意,下面代码中的ResponseBody.charStream()方法使用Content-Type响应头部来选择在解码响应体时应使用哪个字符集。如果没有指定字符集,则默认为UTF-8。
//使用Gson来解析JSON响应
public void parseJSONResponseWithGson() 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 = new Gson().fromJson(response.body().charStream(), Gist.class);
for (Map.Entry<String, Gist.GistFile> entry : gist.files.entrySet()) {
System.out.println("【Key】" + entry.getKey());
System.out.println("【Value】" + entry.getValue().content);
}
}
static class Gist {
Map<String, GistFile> files;
static class GistFile {
String content;
}
}
Response Caching,响应缓存
要使OkHttp来缓存响应,用户需要创建一个可以读写的缓存目录,以及设置缓存大小的限制。该缓存目录应该是私有的private,不受信任的应用程序不能读取它的内容。
让多个缓存同时访问同一个缓存目录是错误的。大多数应用程序都应该只调用一次new OkHttpClient( ),并为其配置缓存,并在任何地方使用该OkHttp实例。否则,两个缓存实例将相互影响,破坏响应缓存,并可能破坏您的程序。
响应缓存使用HTTP头部来进行所有的配置。用户可以在请求头部添加Cache-Control:max-stale=3600的字段,OkHttp的缓存就会执行。用户的web服务器配置响应缓存的响应头部来设置响应缓存的大小,例如Cache-Control:max-age=9600。有一些缓存头部可以强制缓存响应,强制网络响应,或强制使用有条件的GET对网络响应进行验证。
当不使用缓存时,可以使用CacheControl.FORCE_NETWORK来防止使用缓存的响应。当只使用缓存,而不使用网络获取数据时,可以使用CacheControl.FORCE_CACHE。注意:当使用FORCE_CACHE时,响应结果需要从网络获取,OkHttp将返回504 Unsatisfiable Reques 的响应结果。
//响应缓存
public void responseCaching() throws Exception {
Cache cache = new Cache(new File(Environment.getExternalStorageDirectory().getAbsoluteFile(), "cache"),
10 * 1024 * 1024);// 10 MiB
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();
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("【两次响应结果是否相同】" + response1Body.equals(response2Body));
}
Canceling a Call,取消一个请求
当发起网络请求后,可以使用Call.cancel()来停止正在进行的调用。如果一个线程正在写请求或者读取响应,将收到一个IOException异常。当一个Call不再需要使用时,可以使用cancel()来保护网络,例如当用户退出应用程序时。注意,同步和异步的调用都可以被取消。
//取消一个请求
public void cancelingCall() 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.
Executors.newScheduledThreadPool(1).schedule(() -> {
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);
}
}
Timeouts,响应超时
当网络请求不可到达时,call请求会由于超时而导致失败。网络不可到达的原因可能是由于客户端连接问题,服务器可用性问题或者其他原因造成的。OkHttp支持连接超时,读取超时以及写入超时。
//响应超时
public void settTmeouts() throws Exception {
client = new OkHttpClient.Builder()
.connectTimeout(10, TimeUnit.SECONDS)
.writeTimeout(10, TimeUnit.SECONDS)
.readTimeout(500, TimeUnit.MILLISECONDS)
.build();
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.body().string());
}
Per-call Configuration,配置Call
所有的HTTP客户端配置都存在于OkHttpClient中,包括代理设置,超时和缓存。当需要更改单个Call调用的配置时,请调用OkHttpClient.newBuilder()方法,这个方法同样返回一个OkHttp构建器,该构建器与初始客户端OkHttpClient.Builder()共享相同的连接池Connection Pool,分发器Dispatcher和配置。在下面的例子中,将使用一个500毫秒的超时的请求和另一个3000毫秒的超时的请求。
//配置Call
public void perCallConfiguration() throws Exception {
OkHttpClient copyClient = client.newBuilder()// Copy to customize OkHttp for this request.
.readTimeout(500, TimeUnit.MILLISECONDS)
.build();
Request request = new Request.Builder()
.url("http://httpbin.org/delay/1") // This URL is served with a 1 second delay.
.build();
Response response = copyClient.newCall(request).execute();
if (!response.isSuccessful()) throw new IOException("Unexpected code " + response);
System.out.println("【响应结果】" + response.body().string());
}
Handling authentication,处理身份验证
OkHttp可以自动重试未经身份验证的请求,例如当响应状态吗是401未经授权时,会要求身份验证者提供证书。用户应该构建一个新的请求,并附带缺少的验证证书。如果没有可用的证书,则返回null以跳过重试。
使用Response.challenges()方法来获取任何身份验证的方案和领域。当需要完成一个基本的身份验证时,可以使用Credentials.basic(username,password)方法来对请求头进行编码。
//处理身份验证
public void handlingAuthentication() throws Exception {
client = new OkHttpClient.Builder()
.authenticator((route, response) -> {
System.out.println("【Authenticating for response】" + response);
System.out.println("【Challenges】" + response.challenges());
return response.request()
.newBuilder()
.header("Authorization", Credentials.basic("jesse", "password1"))
.build();
})
.build();
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());
}
为了避免在身份验证无效时进行多次重试,可以返回null来放弃该请求。例如,当这些确切的用户凭证已经被尝试时,用户可能希望跳过重试:
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.
}
上文中的responseCount()方法的源码如下所示:
private int responseCount(Response response) {
int result = 1;
while ((response = response.priorResponse()) != null) {
result++;
}
return result;
}
2017-6-19