专题¶
我们编写了一些专题来演示如何使用OkHttp解决常见问题。 通读它们,了解一切是如何协同工作的。 自由地剪切和粘贴这些示例;它们本来就是作为这类用途编写的。
同步 Get (.kt, .java)¶
Response Body上的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)¶
在工作线程上下载一个文件,并在响应可读时被回调。 响应头准备就绪后进行回调。 读取响应正文仍可能会阻塞。 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());
}
}
});
}
访问Headers (.kt, .java)¶
通常,HTTP headers的工作方式类似于 Map<String, String>
: 每个字段都有一个值或没有值。 但有些header允许多个值,如Guava的Multimap. 例如,HTTP响应提供多个 “vary” 标头是合法且常见的。 OkHttp的API试图让这两种情况都变得舒适。
写入请求头部时,使用header(name, value)
将name
的唯一匹配项设置为value
。 如果存在现有值,则在添加新值之前将其删除。 使用addHeader(name, value)
可以在不删除已有header的情况下添加header。
读取响应标头时,请使用 header(name)
返回命名_最后_出现值。 通常这也是唯一的一次! 如果没有值,header(name)
将返回null。 要以列表形式读取字段的所有值,请使用headers(name)
。
要访问所有标头,请使用支持按索引访问的 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"));
}
}
Post一个String (.kt, .java)¶
使用HTTP POST向服务发送请求主体。 此示例将标记文档发布到将标记呈现为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 Streaming (.kt, .java)¶
这里我们POST
一个请求体作为一个流。 此请求主体的内容是在编写过程中生成的。 此示例直接流入Okio缓冲接收器。 你的程序可能更喜欢 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());
}
}
Posting 一个文件 (.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());
}
}
Posting 表单参数 (.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文件上传表单兼容的复杂请求体。 多部分请求主体的每个部分本身就是请求主体,并且可以定义自己的标头。 如果存在,这些Header应该描述Part Body,例如它的Content-Dispostion
。 如果可用,则会自动添加 Content-Length
和 Content-Type
标头。
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
响应标头来选择解码响应主体时要使用的字符集。 如果不指定字符集,则默认为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头。 你可以添加Cache-Control: max-stale=3600
这样的请求头,OkHttp的缓存会认可它们。 你的web服务器使用其自己的响应标头 (例如 Cache-Control: max-age=9600
) 配置缓存的响应时间。 有一些缓存头可以强制缓存响应、强制网络响应或强制使用条件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
. To prevent it from using the network, use CacheControl.FORCE_CACHE
。 注意:如果你使用FORCE_CACHE
,并且响应需要网络,则OkHttp会返回504 Unsatisfiable Request
响应。
取消Call (.kt, .java)¶
使用Call.ancel()
可以立即停止正在进行的call。 如果线程当前正在编写请求或读取响应,它将收到 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)¶
使用超时可在对方无法联系到call时使其失败。 网络分区可能是由于客户端连接问题、服务器可用性问题或两者之间的任何原因。 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);
}
}
Per-call Configuration (.kt, .java)¶
所有的HTTP客户端配置都在OkHttpClient
中,包括代理设置、超时和缓存。 当你需要更改单个调用的配置时,请调用 OkHttpClient.newBuilder()
。 这将返回一个构建器,该构建器与原始客户端共享相同的连接池、调度程序和配置。 在下面的示例中,我们提出一个请求,超时时间为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()
来获取任何身份验证挑战的schemes和realms。 完成Basic
挑战时,请使用Credentials.basic(username, password)
对请求头进行编码。
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.
}
当你达到应用程序定义的尝试限制时,你也可以跳过重试:
```kotlin
if (response.responseCount >= 3) {
return null // If we've failed 3 times, give up.
}
```
上面的代码依赖于这个ResponseCount
扩展val:
```kotlin
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());
}
}
To avoid making many retries when authentication isn’t working, you can return null to give up. For example, you may want to skip the retry when these exact credentials have already been attempted:
if (credential.equals(response.request().header("Authorization"))) {
return null; // If we already failed with these credentials, don't retry.
}
You may also skip the retry when you’ve hit an application-defined attempt limit:
if (responseCount(response) >= 3) {
return null; // If we've failed 3 times, give up.
}
This above code relies on this responseCount()
method:
private int responseCount(Response response) {
int result = 1;
while ((response = response.priorResponse()) != null) {
result++;
}
return result;
}