由于示例中使用的代码段都比较简单,详细的代码段以及响应日志都已经贴出,细节不再赘述。
一、初始化
- 1、初始化OkHttpClient
val mClient = getOkClient()
private fun getOkClient(): OkHttpClient {
return OkHttpClient.Builder().addInterceptor(LogInterceptor()).build()
}
- 2、初始化Request
private var mUrl2 = "https://www.baidu.com/"
val mRequest = Request.Builder().url(mUrl2).build()
二、同步Get请求
由于网络请求不能在主线程进行,此处使用协程进行请求,防止主线程发生阻塞
- 1、代码段
mRequest?.let {
GlobalScope.launch {
mClient.newCall(it).execute().use { response ->
if (response.isSuccessful) {
for ((name, value) in response.headers) {
LogUtil.D(log = "name ===$name value===$value")
}
LogUtil.D(log = "response body== ${response.body.toString()}")
}
}
}
}
- 2、response日志
2021-07-14 19:37:33.455 31188-31247/com.example.myapplication D/tag: name ===Cache-Control value===private, no-cache, no-store, proxy-revalidate, no-transform
2021-07-14 19:37:33.456 31188-31247/com.example.myapplication D/tag: name ===Connection value===keep-alive
2021-07-14 19:37:33.456 31188-31247/com.example.myapplication D/tag: name ===Content-Type value===text/html
2021-07-14 19:37:33.456 31188-31247/com.example.myapplication D/tag: name ===Date value===Wed, 14 Jul 2021 11:37:33 GMT
2021-07-14 19:37:33.456 31188-31247/com.example.myapplication D/tag: name ===Last-Modified value===Mon, 23 Jan 2017 13:23:46 GMT
2021-07-14 19:37:33.456 31188-31247/com.example.myapplication D/tag: name ===Pragma value===no-cache
2021-07-14 19:37:33.456 31188-31247/com.example.myapplication D/tag: name ===Server value===bfe/1.0.8.18
2021-07-14 19:37:33.456 31188-31247/com.example.myapplication D/tag: name ===Set-Cookie value===BDORZ=27315; max-age=86400; domain=.baidu.com; path=/
2021-07-14 19:37:33.456 31188-31247/com.example.myapplication D/tag: name ===Transfer-Encoding value===chunked
2021-07-14 19:37:33.456 31188-31247/com.example.myapplication D/tag: response body== okhttp3.internal.http.RealResponseBody@72ab647
以上可见请求成功并打印了响应头
三、异步Get请求
通常实际项目中,使用异步的方式进行请求,
- 1、代码段
mRequest?.let {
mClient.newCall(it).enqueue(object : Callback {
override fun onFailure(call: Call, e: IOException) {
Log.d(TAG, "fail e===${e.message} cause=== ${e.cause}")
}
override fun onResponse(call: Call, response: Response) {
Log.d(TAG, " get success response=== ${response.body.toString()}")
updateText(response)
response.body?.close()
}
})
}
- 2、response日志
2021-07-14 19:43:40.671 31188-31469/com.example.myapplication D/tag: get success response=== okhttp3.internal.http.RealResponseBody@2b2273f
三、访问头文件
- 1、设置header
OkHttp设置请求头时,使用header(name, value)设定的唯一键name值value
使用addHeader(name, value)添加一个头时,如果header中存在现有值,新值将覆盖旧值。
- 2、读取header
读取响应头时,返回最后一次出现的header(name)命名值。通常键唯一,值唯一,并且一一对应。
如果不存在值,header(name)将返回 null。
如果想读取所有的header,可以使用headers(name).
- 3、代码段
val mRequest=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()
GlobalScope.launch {
mClient.newCall(mRequest).execute().use { response->
if (!response.isSuccessful)LogUtil.D(log = "response error ==== ${response.code}")
LogUtil.D(log="Server: ${response.header("Server")}")
LogUtil.D(log="Date: ${response.header("Date")}")
LogUtil.D(log="Vary: ${response.headers("Vary")}")
// 查看header中哈哈哈为键的值
LogUtil.D(log="哈哈哈: ${response.header("哈哈哈")}")
}
}
- 4、response日志
2021-07-14 20:14:03.909 32142-32180/com.example.myapplication D/tag: Server: GitHub.com
2021-07-14 20:14:03.910 32142-32180/com.example.myapplication D/tag: Date: Wed, 14 Jul 2021 12:14:02 GMT
2021-07-14 20:14:03.910 32142-32180/com.example.myapplication D/tag: Vary: [Accept, Accept-Encoding, Accept, X-Requested-With]
2021-07-14 20:14:03.910 32142-32180/com.example.myapplication D/tag: 哈哈哈: null
通过日志可见,通过header的键获取到了相应的值,由于没有查询到哈哈哈对应的值,返回了null。
四、使用post请求发送string
- 1、注意点
使用post请求发送请求体到服务端,一般限制不大于1M
- 2、代码段
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()
GlobalScope.launch {
mClient.newCall(request).execute().use { response ->
if (!response.isSuccessful) throw IOException("Unexpected code $response")
LogUtil.D(log = response.body!!.string())
}
}
- 3、response响应
2021-07-14 20:31:25.498 32462-32509/com.example.myapplication D/tag: <h2>
<a id="user-content-releases" class="anchor" href="#releases" aria-hidden="true"><span aria-hidden="true" class="octicon octicon-link"></span></a>Releases</h2>
<ul>
<li>
<em>1.0</em> May 6, 2013</li>
<li>
<em>1.1</em> June 15, 2013</li>
<li>
<em>1.2</em> August 11, 2013</li>
</ul>
五、使用post发送流
- 1、简介
示例中将请求体通过Okio的buffer sink直接转换成流进行传递,也可以使用OutputStream进行转换传递。
- 2、代码段
val requestBody = object : RequestBody() {
override fun contentType() = MEDIA_TYPE_MARKDOWN
/** Writes the content of this request to [sink]. */
override fun writeTo(sink: BufferedSink) {
sink.writeUtf8("Numbers\n")
sink.writeUtf8("-------\n")
for (i in 2..10) {
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()
GlobalScope.launch {
mClient.newCall(request).execute().use { response ->
if (!response.isSuccessful) throw IOException("Unexpected code $response")
LogUtil.D(log=response.body!!.string())
}
}
- 3、response日志
2021-07-14 20:53:51.098 1496-1551/com.example.myapplication D/tag: <h2>
<a id="user-content-numbers" class="anchor" href="#numbers" aria-hidden="true"><span aria-hidden="true" class="octicon octicon-link"></span></a>Numbers</h2>
<ul>
<li>2 = 2</li>
<li>3 = 3</li>
<li>4 = 2 × 2</li>
<li>5 = 5</li>
<li>6 = 3 × 2</li>
<li>7 = 7</li>
<li>8 = 2 × 2 × 2</li>
<li>9 = 3 × 3</li>
<li>10 = 5 × 2</li>
</ul>
六、post上传文件
- 1、示例代码
val fileDir = File(Environment.getExternalStorageDirectory(),"test")
if (!fileDir.exists()) {
fileDir.mkdir()
}
val fileName="testConnectionFile.txt"
val file=File(fileDir,fileName)
val request = Request.Builder()
.url("https://api.github.com/markdown/raw")
.post(file.asRequestBody(MEDIA_TYPE_MARKDOWN))
.build()
request.let {
mClient.newCall(it).enqueue(object : Callback {
override fun onFailure(call: Call, e: IOException) {
LogUtil.D(log = "response error=== ${e.cause} message=== ${e.message}")
}
override fun onResponse(call: Call, response: Response) {
LogUtil.D(log = response.body!!.string())
}
})
}
- 2、response日志
// interceptor
2021-07-15 21:01:05.378 12489-12554/com.example.myapplication D/tag: Sending request url== https://api.github.com/markdown/raw connection== Connection{api.github.com:443, proxy=DIRECT hostAddress=api.github.com/192.30.255.116:443 cipherSuite=TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256 protocol=h2} headers==Content-Type: text/x-markdown; charset=utf-8
Content-Length: 78
Host: api.github.com
Connection: Keep-Alive
Accept-Encoding: gzip
User-Agent: okhttp/4.9.0
// response
2021-07-15 21:01:05.719 12489-12554/com.example.myapplication D/tag: Received response url== https://api.github.com/markdown/raw costTime== 342ms header== server: GitHub.com
date: Thu, 15 Jul 2021 13:01:05 GMT
content-type: text/html;charset=utf-8
x-commonmarker-version: 0.21.0
x-ratelimit-limit: 60
x-ratelimit-remaining: 58
x-ratelimit-reset: 1626357649
x-ratelimit-used: 2
x-ratelimit-resource: core
access-control-expose-headers: ETag, Link, Location, Retry-After, X-GitHub-OTP, X-RateLimit-Limit, X-RateLimit-Remaining, X-RateLimit-Used, X-RateLimit-Resource, X-RateLimit-Reset, X-OAuth-Scopes, X-Accepted-OAuth-Scopes, X-Poll-Interval, X-GitHub-Media-Type, Deprecation, Sunset
access-control-allow-origin: *
strict-transport-security: max-age=31536000; includeSubdomains; preload
x-frame-options: deny
x-content-type-options: nosniff
x-xss-protection: 0
referrer-policy: origin-when-cross-origin, strict-origin-when-cross-origin
content-security-policy: default-src 'none'
vary: Accept-Encoding, Accept, X-Requested-With
content-encoding: gzip
x-github-request-id: 1964:7252:B6CA20:C2ED42:60F03180
2021-07-15 21:01:05.722 12489-12554/com.example.myapplication D/tag: <p>url===== <a href="http://mf.m.bch.leju.com/v70/show/index_feed.json" rel="nofollow">http://mf.m.bch.leju.com/v70/show/index_feed.json</a> Time=== 3438ms</p>
七、post上传form表单
- 1、示例代码
向百度发送form表单
val formData=FormBody.Builder()
.add("search","Jurassic Park")
.build()
val request=Request.Builder()
.url("https://www.baidu.com")
.post(formData)
.build()
mClient.newCall(request).enqueue(object :Callback{
override fun onFailure(call: Call, e: IOException) {
LogUtil.D(log="error message ==== ${e.message}")
}
override fun onResponse(call: Call, response: Response) {
LogUtil.D(log="success response== ${response.body.toString()}")
}
})
- 2、日志
通过日志查看请求成功了
// interceptor
2021-07-16 13:58:45.294 6510-6550/com.example.myapplication D/tag: Sending request url== https://www.baidu.com/ connection== Connection{www.baidu.com:443, proxy=DIRECT hostAddress=www.baidu.com/110.242.68.3:443 cipherSuite=TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256 protocol=http/1.1} headers==Content-Type: application/x-www-form-urlencoded
Content-Length: 22
Host: www.baidu.com
Connection: Keep-Alive
Accept-Encoding: gzip
User-Agent: okhttp/4.9.0
2021-07-16 13:58:45.323 6510-6550/com.example.myapplication D/tag: Received response url== https://www.baidu.com/ costTime== 30ms header== Connection: keep-alive
Content-Length: 17931
Content-Type: text/html
Date: Fri, 16 Jul 2021 05:58:45 GMT
Etag: "54d97483-460b"
Server: bfe/1.0.8.18
// response
2021-07-16 13:58:45.324 6510-6550/com.example.myapplication D/tag: success response== okhttp3.internal.http.RealResponseBody@c9f4522
八、post发送multipart request
- 1、简介
MultipartBody.Builder可以构建与 HTML 文件上传表单兼容的复杂请求体。Multipart请求体的每个部分本身就是一个请求体,并且可以分别定义heder.
- 2、代码示例
// 将手机上文件通过formData 传递
val requestBody=MultipartBody.Builder()
.setType(MultipartBody.FORM)
.addFormDataPart("title","registry_list")
.addFormDataPart("text","registry_list.txt",
getFile("360/authshare","registry_list.txt").asRequestBody(MEDIA_TYPE_MARKDOWN))
.build()
val request = Request.Builder()
.header("Authorization", "Client-ID $IMGUR_CLIENT_ID")
.url("https://www.baidu.com")
.post(requestBody)
.build()
mClient.newCall(request).enqueue(object :Callback{
override fun onFailure(call: Call, e: IOException) {
LogUtil.D(log="error message ==== ${e.message}")
}
override fun onResponse(call: Call, response: Response) {
LogUtil.D(log="success response code==== ${response.code} body===== ${response.body.toString()}")
}
})
- 3、日志打印
// interceptor
2021-07-16 14:53:07.358 7932-7991/com.example.myapplication D/tag: Sending request url== https://www.baidu.com/ connection== Connection{www.baidu.com:443, proxy=DIRECT hostAddress=www.baidu.com/110.242.68.4:443 cipherSuite=TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256 protocol=http/1.1} headers==Authorization: Client-ID 9199fdef135c122
Content-Type: multipart/form-data; boundary=83bbd2e3-0504-4096-a833-e162d2ad91e2
Content-Length: 368
Host: www.baidu.com
Connection: Keep-Alive
Accept-Encoding: gzip
User-Agent: okhttp/4.9.0
2021-07-16 14:53:07.429 7932-7991/com.example.myapplication D/tag: Received response url== https://www.baidu.com/ costTime== 71ms header== Connection: keep-alive
Content-Length: 17931
Content-Type: text/html
Date: Fri, 16 Jul 2021 06:53:07 GMT
Etag: "54d97483-460b"
Server: bfe/1.0.8.18
// response
2021-07-16 14:53:07.430 7932-7991/com.example.myapplication D/tag: success response code==== 302 body===== okhttp3.internal.http.RealResponseBody@b30f288
九、响应缓存
- 1、简介
要缓存响应,需要设置一个可以读取和写入的缓存目录,并对缓存大小的进行限制。缓存目录应该是私有的,不受信任的应用应该无法读取其内容
为了方便控制缓存增删,多个缓存同时访问的缓存目录应该独立
大多数应用程序使用同一个OkHttpClient,并配置缓存,应该在任何地方使用相同的实例。否则,这两个缓存实例将相互覆盖,破坏响应缓存,并可能使程序崩溃
可以通过配置缓存Header对缓存进行配置,OkHttp会使用配置的缓存header对缓存进行控制
- 2、代码实例
代码中将缓存保存在文件夹customCache中
val directory = File(Environment.getExternalStorageDirectory(),"customCache")
val cacheClient = OkHttpClient().newBuilder().addNetworkInterceptor(CacheAgeInterceptor())
.cache(Cache(directory = directory, maxSize = 2 * 1024 * 1024L))
.build()
// CacheControl.FORCE_CACHE 强制使用缓存
// CacheControl.FORCE_NETWORK 强制使用网络
val cc=CacheControl.Builder()
// .noCache() // 不使用缓存,但是会存储缓存数据
// .noStore() // 不使用缓存,同时不存储缓存
// .onlyIfCached() // 本地缓存时会使用缓存
.minFresh(100,TimeUnit.SECONDS) // 10s刷新缓存
.maxAge(1,TimeUnit.HOURS) // 1h最大有效时间
.maxStale(50,TimeUnit.SECONDS) // 可以接受超时5s的响应
.build()
val request=Request.Builder().url(mUrl).cacheControl(cc).build()
GlobalScope.launch {
// 响应1
val responseOne=cacheClient.newCall(request).execute().use { response ->
if (response.isSuccessful){
LogUtil.D(log="request success cacheResponse=== ${response.cacheResponse?.body?.contentType()} " +
"networkResponse=== ${response.networkResponse}")
}else{
LogUtil.D(log="requestOne error errorCode ===${response.code}")
}
}
// 响应2
val responseTwo=cacheClient.newCall(request).execute().use { response ->
if (response.isSuccessful){
LogUtil.D(log="request successTwo cacheResponse=== ${response.cacheResponse?.body.toString()} " +
" networkResponse=== ${response.networkResponse}")
}else{
LogUtil.D(log="requestTwo error errorCode ===${response.code}")
}
}
LogUtil.D(log=" responseOne===responseTwo====${responseOne==responseTwo}")
}
- 3、日志查看
2021-07-16 16:40:45.072 13883-13933/com.example.myapplication D/tag: request success cacheResponse=== null networkResponse=== Response{protocol=http/1.1, code=200, message=OK, url=https://publicobject.com/helloworld.txt}
2021-07-16 16:40:45.106 13883-13933/com.example.myapplication D/tag: request successTwo cacheResponse=== null networkResponse=== null
2021-07-16 16:40:45.106 13883-13933/com.example.myapplication D/tag: responseOne===responseTwo====true
通过日志查看,两次请求都成功了,但是第二次networkResponse为空,证明OkHttp帮我们使用了缓存
- 4、缓存文件夹查看及缓存内容查看
-
缓存文件夹
-
缓存数据
https://publicobject.com/helloworld.txt
GET
0
HTTP/1.1 200 OK
11
Server: nginx/1.10.3 (Ubuntu)
Date: Fri, 16 Jul 2021 08:40:44 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
Cache-Control: max-age=60
OkHttp-Sent-Millis: 1626424844597
OkHttp-Received-Millis: 1626424844986
TLSv1.2
- 5、注意点
-
要防止响应使用缓存,可以使用CacheControl.FORCE_NETWORK. 要防止它使用网络,可以使用CacheControl.FORCE_CACHE.
-
如果您使用FORCE_CACHE并且响应需要网络,OkHttp 将返回一个504 Unsatisfiable Request响应。
十、取消请求
- 1、简介
- 可以使用Call.cancel()方法来取消请求操作
- 该操作支持同步和异步请求的取消工作
- 如果当前取消的请求的线程正在执行写请求和读响应过程中,会报IOException
- 2、代码实例
例子中使用Executors延迟5s取消请求
// init
val executor = Executors.newScheduledThreadPool(1)
val request = Request.Builder()
.url("https://www.baidu.com")
.build()
val startTime = System.currentTimeMillis()
val mCall = mClient.newCall(request)
// 延迟5s
executor.schedule({
LogUtil.D(log = "startCallTime=== ${System.currentTimeMillis() - startTime}")
mCall.cancel()
}, 5, TimeUnit.SECONDS)
// 请求
GlobalScope.launch {
mCall.execute().use { response ->
if (response.isSuccessful) {
LogUtil.D(log = "call success response=== ${response.body.toString()}")
} else {
LogUtil.D(log = "call fail code is === ${response.code}")
}
}
}
- 3、日志
// interceptor
2021-07-19 16:16:15.950 31114-31162/com.example.myapplication D/tag: Sending request url== https://www.baidu.com/ connection== Connection{www.baidu.com:443, proxy=DIRECT hostAddress=www.baidu.com/110.242.68.3:443 cipherSuite=TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256 protocol=http/1.1} headers==Host: www.baidu.com
Connection: Keep-Alive
Accept-Encoding: gzip
User-Agent: okhttp/4.9.0
2021-07-19 16:16:15.977 31114-31162/com.example.myapplication D/tag: Received response url== https://www.baidu.com/ costTime== 27ms header== Cache-Control: private, no-cache, no-store, proxy-revalidate, no-transform
Connection: keep-alive
Content-Encoding: gzip
Content-Type: text/html
Date: Mon, 19 Jul 2021 08:16:15 GMT
Last-Modified: Mon, 23 Jan 2017 13:23:46 GMT
Pragma: no-cache
Server: bfe/1.0.8.18
Set-Cookie: BDORZ=27315; max-age=86400; domain=.baidu.com; path=/
Transfer-Encoding: chunked
// response
2021-07-19 16:16:15.979 31114-31162/com.example.myapplication D/tag: call success response=== okhttp3.internal.http.RealResponseBody@f6e4d64
2021-07-19 16:16:20.767 31114-31161/com.example.myapplication D/tag: startCallTime=== 5008
- 通过日志查看延迟了5008ms,时间接近5s,但是不是很精确
十一、超时时间
- 1、简介
当请求不可达时,可以使用超时策略来调用失败进行错误处理,OkHttp提供了设置连接、读、写和请求全过程超时时常策略。
- 2、代码实例
具体的点在代码中都写了注释
private var mUrl3 = "https://www.nytimes.com/"
val client: OkHttpClient = OkHttpClient.Builder()
.addNetworkInterceptor(LogInterceptor())
.connectTimeout(5, TimeUnit.SECONDS) // 连接超时时间
.writeTimeout(5, TimeUnit.SECONDS) // 写超时时间
.readTimeout(5, TimeUnit.SECONDS) // 读超时时间
.callTimeout(10, TimeUnit.SECONDS) // 全过程超时时间
.build()
val request = Request.Builder()
.url(mUrl3)
.build()
GlobalScope.launch {
client.newCall(request).enqueue(object : Callback {
override fun onFailure(call: Call, e: IOException) {
LogUtil.D(log = "Response error: ${e.message}")
}
override fun onResponse(call: Call, response: Response) {
LogUtil.D(log = "Response completed: ${response.body.toString()}")
}
})
}
- 3、日志
2021-07-19 16:54:56.181 4170-4240/com.example.myapplication D/tag: Response error: failed to connect to www.nytimes.com/103.252.115.53 (port 443) from /10.208.97.1 (port 47503) after 5000ms
- 通过日志查看,连接超时
十二、为不同请求添加不同配置
- 1、简介
OkHttpClient 提供了配置超时、缓存等方法,当需要不同的请求添加不同配置时,可以使用OKHttpClient.newBuilder()来进行配置。这时两个request请求会共享同一个Client的连接池、嗲调度器和配置。
- 2、代码实例
我们基于mClient创建了两个request,requestOne设置了超时时间为500ms,requestTwo设置的超时时间为3000ms
val request = Request.Builder()
.url("https://httpbin.org/delay/1")
.build()
// set timeout 500
val clientOne = mClient.newBuilder()
.readTimeout(500, TimeUnit.MILLISECONDS)
.build()
GlobalScope.launch {
clientOne.newCall(request).enqueue(object :Callback{
override fun onFailure(call: Call, e: IOException) {
LogUtil.D(log = "Response error: ${e.message}")
}
override fun onResponse(call: Call, response: Response) {
LogUtil.D(log = "Response completed: ${response.body.toString()}")
}
})
}
// set timeout 3000
val clientTwo = mClient.newBuilder()
.readTimeout(3000, TimeUnit.MILLISECONDS)
.build()
GlobalScope.launch {
clientTwo.newCall(request).enqueue(object :Callback{
override fun onFailure(call: Call, e: IOException) {
LogUtil.D(log = "Response error: ${e.message}")
}
override fun onResponse(call: Call, response: Response) {
LogUtil.D(log = "Response completed: ${response.body.toString()}")
}
})
}
- 3、日志
// interceptor
2021-07-19 18:46:35.927 12767-12814/com.example.myapplication D/tag: Sending request url== https://httpbin.org/delay/1 connection== Connection{httpbin.org:443, proxy=DIRECT hostAddress=httpbin.org/18.235.124.214:443 cipherSuite=TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256 protocol=h2} headers==Host: httpbin.org
Connection: Keep-Alive
Accept-Encoding: gzip
User-Agent: okhttp/4.9.0
2021-07-19 18:46:35.927 12767-12813/com.example.myapplication D/tag: Sending request url== https://httpbin.org/delay/1 connection== Connection{httpbin.org:443, proxy=DIRECT hostAddress=httpbin.org/18.235.124.214:443 cipherSuite=TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256 protocol=h2} headers==Host: httpbin.org
Connection: Keep-Alive
Accept-Encoding: gzip
User-Agent: okhttp/4.9.0
// response
// 超时
2021-07-19 18:46:36.439 12767-12814/com.example.myapplication D/tag: Response error: timeout
2021-07-19 18:46:37.752 12767-12813/com.example.myapplication D/tag: Received response url== https://httpbin.org/delay/1 costTime== 1824ms header== date: Mon, 19 Jul 2021 10:46:37 GMT
content-type: application/json
content-length: 315
server: gunicorn/19.9.0
access-control-allow-origin: *
access-control-allow-credentials: true
// response success
2021-07-19 18:46:37.753 12767-12813/com.example.myapplication D/tag: Response completed: okhttp3.internal.http.RealResponseBody@d22b271
通过日志查看,请求全过程耗时1824ms,requestOne超时,requestTwo获取到了响应。
十三、认证处理
- 1、简介
-
通常情况下,当响应码返回401时,OkHttp会自动向认证中心对未认证的请求进行认证重试。
-
配置认证代理的方法实现中要提供身份认证凭据,如果提供的身份认证凭据不可用时,返回null并跳过重试。
-
可以使用Response.challenges获取schemes和认证信息,当完成基础的challenge后,使用Credentials.basic(username, password)来对请求头进行编码。
- 2、代码实例
代码中,通过authenticator设置了独立的认证凭据,当response返回code为401时将使用我们提供的认证凭据进行认证。
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
}
LogUtil.D(log = "Authenticating for response: $response")
LogUtil.D(log = "Challenges: ${response.challenges()}")
// 设置认证凭据
val credential = Credentials.basic("jesse", "password1")
return response.request.newBuilder()
.header("Authorization", credential)
.build()
}
})
.build()
val request = Request.Builder()
.url("https://publicobject.com/secrets/hellosecret.txt")
.build()
client.newCall(request).enqueue(object : Callback {
override fun onFailure(call: Call, e: IOException) {
LogUtil.D(log = "Response error: ${e.message}")
}
override fun onResponse(call: Call, response: Response) {
LogUtil.D(log = "Response completed: ${response.body.toString()}")
}
})
- 3、日志
// interceptor
2021-07-19 19:26:04.941 13362-13404/com.example.myapplication D/tag: Authenticating for response: Response{protocol=http/1.1, code=401, message=Unauthorized, url=https://publicobject.com/secrets/hellosecret.txt}
// challenge
2021-07-19 19:26:04.943 13362-13404/com.example.myapplication D/tag: Challenges: [Basic authParams={realm=OkHttp Secrets}]
// response
2021-07-19 19:26:05.130 13362-13404/com.example.myapplication D/tag: Response completed: okhttp3.internal.http.RealResponseBody@52fe3e2
通过日志可见返回了状态码401,使用本地配置的认证凭据进行认证后,获取到了响应。
- 参考:https://square.github.io/okhttp/recipes/#canceling-a-call-kt-java