示例¶
我们编写了一些示例(Recipes),展示了如何使用 OkHttp 解决常见问题。通读这些示例,了解它们如何协同工作。可以随意复制和粘贴这些示例;它们就是为此而设计的。
同步GET请求 (.kt, .java)¶
下载文件,打印其Header,并将响应体打印为字符串。
响应体上的 string()
方法对于小型文档来说既方便又高效。但是如果响应体很大(大于 1 MiB),请避免使用 string()
方法,因为它会将整个文档加载到内存中。在这种情况下,最好将响应体作为流进行处理。
private val client = OkHttpClient()
fun run() {
val request = Request.Builder()
.url("https://publicobject.com/helloworld.txt")
.build()
client.newCall(request).execute().use { response ->
if (!response.isSuccessful) throw IOException("Unexpected code $response")
for ((name, value) in response.headers) {
println("$name: $value")
}
println(response.body!!.string())
}
}
private final OkHttpClient client = new OkHttpClient();
public void run() throws Exception {
Request request = new Request.Builder()
.url("https://publicobject.com/helloworld.txt")
.build();
try (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请求 (.kt, .java)¶
在工作线程上下载文件,并在响应可读时获得回调。回调在响应Header就绪后进行。读取响应体可能仍然会阻塞。OkHttp 目前不提供分批接收响应体的异步 API。
private val client = OkHttpClient()
fun run() {
val request = Request.Builder()
.url("http://publicobject.com/helloworld.txt")
.build()
client.newCall(request).enqueue(object : Callback {
override fun onFailure(call: Call, e: IOException) {
e.printStackTrace()
}
override fun onResponse(call: Call, response: Response) {
response.use {
if (!response.isSuccessful) throw IOException("Unexpected code $response")
for ((name, value) in response.headers) {
println("$name: $value")
}
println(response.body!!.string())
}
}
})
}
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(Call call, IOException e) {
e.printStackTrace();
}
@Override public void onResponse(Call call, Response response) throws IOException {
try (ResponseBody responseBody = response.body()) {
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(responseBody.string());
}
}
});
}
访问Header (.kt, .java)¶
通常 HTTP Header 的工作方式类似于 Map<String, String>
:每个字段有一个值或没有值。但有些 Header 允许有多个值,比如 Guava 的 Multimap。例如,HTTP 响应提供多个 Vary
Header 是合法且常见的。OkHttp 的 API 旨在使这两种情况都易于处理。
编写请求 Header 时,使用 header(name, value)
将 name
的唯一值设置为 value
。如果存在现有值,在添加新值之前它们将被移除。使用 addHeader(name, value)
添加 Header,而无需移除已存在的 Header。
读取响应 Header 时,使用 header(name)
返回指定名称值的最后一个出现项。通常这也是唯一一个出现项!如果不存在任何值,header(name)
将返回 null。要将字段的所有值读取为列表,使用 headers(name)
。
要遍历所有 Header,使用支持通过索引访问的 Headers
类。
private val client = OkHttpClient()
fun run() {
val request = 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()
client.newCall(request).execute().use { response ->
if (!response.isSuccessful) throw IOException("Unexpected code $response")
println("Server: ${response.header("Server")}")
println("Date: ${response.header("Date")}")
println("Vary: ${response.headers("Vary")}")
}
}
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();
try (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"));
}
}
发送字符串 (.kt, .java)¶
使用 HTTP POST 向服务发送请求体。此示例将一个 Markdown 文档发送到一个将 Markdown 渲染为 HTML 的 Web 服务。由于整个请求体同时加载到内存中,使用此 API 时请避免发送大型(大于 1 MiB)文档。
private val client = OkHttpClient()
fun run() {
val postBody = """
|Releases
|--------
|
| * _1.0_ May 6, 2013
| * _1.1_ June 15, 2013
| * _1.2_ August 11, 2013
|""".trimMargin()
val request = Request.Builder()
.url("https://api.github.com/markdown/raw")
.post(postBody.toRequestBody(MEDIA_TYPE_MARKDOWN))
.build()
client.newCall(request).execute().use { response ->
if (!response.isSuccessful) throw IOException("Unexpected code $response")
println(response.body!!.string())
}
}
companion object {
val MEDIA_TYPE_MARKDOWN = "text/x-markdown; charset=utf-8".toMediaType()
}
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();
try (Response response = client.newCall(request).execute()) {
if (!response.isSuccessful()) throw new IOException("Unexpected code " + response);
System.out.println(response.body().string());
}
}
流式POST请求 (.kt, .java)¶
这里我们以流的形式 POST
请求体。此请求体的内容在写入时生成。此示例直接流式写入 Okio 的缓冲 Sink。您的程序可能更喜欢 OutputStream
,可以通过 BufferedSink.outputStream()
获取。
private val client = OkHttpClient()
fun run() {
val requestBody = object : RequestBody() {
override fun contentType() = MEDIA_TYPE_MARKDOWN
override fun writeTo(sink: BufferedSink) {
sink.writeUtf8("Numbers\n")
sink.writeUtf8("-------\n")
for (i in 2..997) {
sink.writeUtf8(String.format(" * $i = ${factor(i)}\n"))
}
}
private fun factor(n: Int): String {
for (i in 2 until n) {
val x = n / i
if (x * i == n) return "${factor(x)} × $i"
}
return n.toString()
}
}
val request = Request.Builder()
.url("https://api.github.com/markdown/raw")
.post(requestBody)
.build()
client.newCall(request).execute().use { response ->
if (!response.isSuccessful) throw IOException("Unexpected code $response")
println(response.body!!.string())
}
}
companion object {
val MEDIA_TYPE_MARKDOWN = "text/x-markdown; charset=utf-8".toMediaType()
}
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();
try (Response response = client.newCall(request).execute()) {
if (!response.isSuccessful()) throw new IOException("Unexpected code " + response);
System.out.println(response.body().string());
}
}
发送文件 (.kt, .java)¶
使用文件作为请求体非常简单。
private val client = OkHttpClient()
fun run() {
val file = File("README.md")
val request = Request.Builder()
.url("https://api.github.com/markdown/raw")
.post(file.asRequestBody(MEDIA_TYPE_MARKDOWN))
.build()
client.newCall(request).execute().use { response ->
if (!response.isSuccessful) throw IOException("Unexpected code $response")
println(response.body!!.string())
}
}
companion object {
val MEDIA_TYPE_MARKDOWN = "text/x-markdown; charset=utf-8".toMediaType()
}
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();
try (Response response = client.newCall(request).execute()) {
if (!response.isSuccessful()) throw new IOException("Unexpected code " + response);
System.out.println(response.body().string());
}
}
发送表单参数 (.kt, .java)¶
使用 FormBody.Builder
构建类似于 HTML <form>
标签的请求体。名称和值将使用与 HTML 兼容的表单 URL 编码进行编码。
private val client = OkHttpClient()
fun run() {
val formBody = FormBody.Builder()
.add("search", "Jurassic Park")
.build()
val request = Request.Builder()
.url("https://en.wikipedia.org/w/index.php")
.post(formBody)
.build()
client.newCall(request).execute().use { response ->
if (!response.isSuccessful) throw IOException("Unexpected code $response")
println(response.body!!.string())
}
}
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();
try (Response response = client.newCall(request).execute()) {
if (!response.isSuccessful()) throw new IOException("Unexpected code " + response);
System.out.println(response.body().string());
}
}
发送Multipart请求 (.kt, .java)¶
MultipartBody.Builder
可以构建与 HTML 文件上传表单兼容的复杂请求体。Multipart 请求体的每个部分本身就是一个请求体,并且可以定义自己的 Header。如果存在,这些 Header 应该描述部分体,例如其 Content-Disposition
。如果可用,Content-Length
和 Content-Type
Header 会自动添加。
private val client = OkHttpClient()
fun run() {
// Use the imgur image upload API as documented at https://api.imgur.com/endpoints/image
val requestBody = MultipartBody.Builder()
.setType(MultipartBody.FORM)
.addFormDataPart("title", "Square Logo")
.addFormDataPart("image", "logo-square.png",
File("docs/images/logo-square.png").asRequestBody(MEDIA_TYPE_PNG))
.build()
val request = Request.Builder()
.header("Authorization", "Client-ID $IMGUR_CLIENT_ID")
.url("https://api.imgur.com/3/image")
.post(requestBody)
.build()
client.newCall(request).execute().use { response ->
if (!response.isSuccessful) throw IOException("Unexpected code $response")
println(response.body!!.string())
}
}
companion object {
/**
* The imgur client ID for OkHttp recipes. If you're using imgur for anything other than running
* these examples, please request your own client ID! https://api.imgur.com/oauth2
*/
private val IMGUR_CLIENT_ID = "9199fdef135c122"
private val MEDIA_TYPE_PNG = "image/png".toMediaType()
}
/**
* The imgur client ID for OkHttp recipes. If you're using imgur for anything other than running
* these examples, please request your own client ID! https://api.imgur.com/oauth2
*/
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();
try (Response response = client.newCall(request).execute()) {
if (!response.isSuccessful()) throw new IOException("Unexpected code " + response);
System.out.println(response.body().string());
}
}
使用 Moshi 解析 JSON 响应 (.kt, .java)¶
Moshi 是一个方便的 API,用于在 JSON 和 Java 对象之间进行转换。这里我们使用它来解码来自 GitHub API 的 JSON 响应。
注意,ResponseBody.charStream()
使用 Content-Type
响应 Header 来选择解码响应体时使用的字符集。如果未指定字符集,默认为 UTF-8
。
private val client = OkHttpClient()
private val moshi = Moshi.Builder().build()
private val gistJsonAdapter = moshi.adapter(Gist::class.java)
fun run() {
val request = Request.Builder()
.url("https://api.github.com/gists/c2a7c39532239ff261be")
.build()
client.newCall(request).execute().use { response ->
if (!response.isSuccessful) throw IOException("Unexpected code $response")
val gist = gistJsonAdapter.fromJson(response.body!!.source())
for ((key, value) in gist!!.files!!) {
println(key)
println(value.content)
}
}
}
@JsonClass(generateAdapter = true)
data class Gist(var files: Map<String, GistFile>?)
@JsonClass(generateAdapter = true)
data class GistFile(var content: String?)
private final OkHttpClient client = new OkHttpClient();
private final Moshi moshi = new Moshi.Builder().build();
private final JsonAdapter<Gist> gistJsonAdapter = moshi.adapter(Gist.class);
public void run() throws Exception {
Request request = new Request.Builder()
.url("https://api.github.com/gists/c2a7c39532239ff261be")
.build();
try (Response response = client.newCall(request).execute()) {
if (!response.isSuccessful()) throw new IOException("Unexpected code " + response);
Gist gist = gistJsonAdapter.fromJson(response.body().source());
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;
}
响应缓存 (.kt, .java)¶
要缓存响应,您需要一个可读写的缓存目录,并限制缓存的大小。缓存目录应该是私有的,不受信任的应用程序不应能读取其内容!
多个缓存同时访问同一个缓存目录是错误的。大多数应用程序应该只调用一次 new OkHttpClient()
,使用其缓存进行配置,并在所有地方使用同一个实例。否则,两个缓存实例会相互冲突,破坏响应缓存,并可能导致您的程序崩溃。
响应缓存的所有配置都使用 HTTP Header。您可以添加请求 Header,例如 Cache-Control: max-stale=3600
,OkHttp 的缓存会遵从这些设置。您的 Web 服务器使用自己的响应 Header 配置响应的缓存时长,例如 Cache-Control: max-age=9600
。有一些缓存 Header 可以强制使用缓存响应、强制使用网络响应,或者强制通过条件 GET 对网络响应进行验证。
private val client: OkHttpClient = OkHttpClient.Builder()
.cache(Cache(
directory = cacheDirectory,
maxSize = 10L * 1024L * 1024L // 10 MiB
))
.build()
fun run() {
val request = Request.Builder()
.url("http://publicobject.com/helloworld.txt")
.build()
val response1Body = client.newCall(request).execute().use {
if (!it.isSuccessful) throw IOException("Unexpected code $it")
println("Response 1 response: $it")
println("Response 1 cache response: ${it.cacheResponse}")
println("Response 1 network response: ${it.networkResponse}")
return@use it.body!!.string()
}
val response2Body = client.newCall(request).execute().use {
if (!it.isSuccessful) throw IOException("Unexpected code $it")
println("Response 2 response: $it")
println("Response 2 cache response: ${it.cacheResponse}")
println("Response 2 network response: ${it.networkResponse}")
return@use it.body!!.string()
}
println("Response 2 equals Response 1? " + (response1Body == response2Body))
}
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.Builder()
.cache(cache)
.build();
}
public void run() throws Exception {
Request request = new Request.Builder()
.url("http://publicobject.com/helloworld.txt")
.build();
String response1Body;
try (Response response1 = client.newCall(request).execute()) {
if (!response1.isSuccessful()) throw new IOException("Unexpected code " + response1);
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());
}
String response2Body;
try (Response response2 = client.newCall(request).execute()) {
if (!response2.isSuccessful()) throw new IOException("Unexpected code " + response2);
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));
}
为防止响应使用缓存,使用 CacheControl.FORCE_NETWORK
。为防止响应使用网络,使用 CacheControl.FORCE_CACHE
。请注意:如果您使用 FORCE_CACHE
但响应需要网络,OkHttp 将返回一个 504 Unsatisfiable Request
响应。
取消调用 (.kt, .java)¶
使用 Call.cancel()
立即停止正在进行的调用。如果线程正在写入请求或读取响应,它将收到一个 IOException
。当调用不再需要时,例如用户离开应用程序时,可以使用此方法来节省网络资源。同步和异步调用都可以被取消。
private val executor = Executors.newScheduledThreadPool(1)
private val client = OkHttpClient()
fun run() {
val request = Request.Builder()
.url("http://httpbin.org/delay/2") // This URL is served with a 2 second delay.
.build()
val startNanos = System.nanoTime()
val call = client.newCall(request)
// Schedule a job to cancel the call in 1 second.
executor.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)
System.out.printf("%.2f Executing call.%n", (System.nanoTime() - startNanos) / 1e9f)
try {
call.execute().use { response ->
System.out.printf("%.2f Call was expected to fail, but completed: %s%n",
(System.nanoTime() - startNanos) / 1e9f, response)
}
} catch (e: IOException) {
System.out.printf("%.2f Call failed as expected: %s%n",
(System.nanoTime() - startNanos) / 1e9f, e)
}
}
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);
System.out.printf("%.2f Executing call.%n", (System.nanoTime() - startNanos) / 1e9f);
try (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);
}
}
超时设置 (.kt, .java)¶
使用超时来使调用在其对端不可达时失败。网络分区可能由于客户端连接问题、服务器可用性问题或其间的任何原因引起。OkHttp 支持连接超时、写入超时、读取超时和总调用超时。
private val client: OkHttpClient = OkHttpClient.Builder()
.connectTimeout(5, TimeUnit.SECONDS)
.writeTimeout(5, TimeUnit.SECONDS)
.readTimeout(5, TimeUnit.SECONDS)
.callTimeout(10, TimeUnit.SECONDS)
.build()
fun run() {
val request = Request.Builder()
.url("http://httpbin.org/delay/2") // This URL is served with a 2 second delay.
.build()
client.newCall(request).execute().use { response ->
println("Response completed: $response")
}
}
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();
try (Response response = client.newCall(request).execute()) {
System.out.println("Response completed: " + response);
}
}
按调用配置 (.kt, .java)¶
所有 HTTP 客户端配置都位于 OkHttpClient
中,包括代理设置、超时和缓存。当您需要更改单个调用的配置时,调用 OkHttpClient.newBuilder()
。这将返回一个 Builder,该 Builder 与原始客户端共享相同的连接池、调度器和配置。在下面的示例中,我们执行一个超时时间为 500 毫秒的请求,另一个超时时间为 3000 毫秒的请求。
private val client = OkHttpClient()
fun run() {
val request = Request.Builder()
.url("http://httpbin.org/delay/1") // This URL is served with a 1 second delay.
.build()
// Copy to customize OkHttp for this request.
val client1 = client.newBuilder()
.readTimeout(500, TimeUnit.MILLISECONDS)
.build()
try {
client1.newCall(request).execute().use { response ->
println("Response 1 succeeded: $response")
}
} catch (e: IOException) {
println("Response 1 failed: $e")
}
// Copy to customize OkHttp for this request.
val client2 = client.newBuilder()
.readTimeout(3000, TimeUnit.MILLISECONDS)
.build()
try {
client2.newCall(request).execute().use { response ->
println("Response 2 succeeded: $response")
}
} catch (e: IOException) {
println("Response 2 failed: $e")
}
}
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();
// Copy to customize OkHttp for this request.
OkHttpClient client1 = client.newBuilder()
.readTimeout(500, TimeUnit.MILLISECONDS)
.build();
try (Response response = client1.newCall(request).execute()) {
System.out.println("Response 1 succeeded: " + response);
} catch (IOException e) {
System.out.println("Response 1 failed: " + e);
}
// Copy to customize OkHttp for this request.
OkHttpClient client2 = client.newBuilder()
.readTimeout(3000, TimeUnit.MILLISECONDS)
.build();
try (Response response = client2.newCall(request).execute()) {
System.out.println("Response 2 succeeded: " + response);
} catch (IOException e) {
System.out.println("Response 2 failed: " + e);
}
}
处理身份验证 (.kt, .java)¶
OkHttp 可以自动重试未经身份验证的请求。当响应为 401 Not Authorized
时,会要求一个 Authenticator
提供凭据。实现类应构建一个包含缺失凭据的新请求。如果没有可用的凭据,返回 null 跳过重试。
使用 Response.challenges()
获取任何身份验证挑战的 scheme 和 realm。处理 Basic
挑战时,使用 Credentials.basic(username, password)
编码请求 Header。
private val client = OkHttpClient.Builder()
.authenticator(object : Authenticator {
@Throws(IOException::class)
override fun authenticate(route: Route?, response: Response): Request? {
if (response.request.header("Authorization") != null) {
return null // Give up, we've already attempted to authenticate.
}
println("Authenticating for response: $response")
println("Challenges: ${response.challenges()}")
val credential = Credentials.basic("jesse", "password1")
return response.request.newBuilder()
.header("Authorization", credential)
.build()
}
})
.build()
fun run() {
val request = Request.Builder()
.url("http://publicobject.com/secrets/hellosecret.txt")
.build()
}
为避免在身份验证不起作用时进行多次重试,可以返回 null 放弃。例如,当使用相同的凭据已经尝试过时,您可能想跳过重试。
if (credential == response.request.header("Authorization")) {
return null // If we already failed with these credentials, don't retry.
}
当达到应用程序定义的尝试次数限制时,您也可以跳过重试。
if (response.responseCount >= 3) {
return null // If we've failed 3 times, give up.
}
上面的代码依赖于这个 responseCount
扩展 val
val Response.responseCount: Int
get() = generateSequence(this) { it.priorResponse }.count()
private final OkHttpClient client;
public Authenticate() {
client = new OkHttpClient.Builder()
.authenticator(new Authenticator() {
@Override public Request authenticate(Route route, Response response) throws IOException {
if (response.request().header("Authorization") != null) {
return null; // Give up, we've already attempted to authenticate.
}
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();
try (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;
}