2. Create OkHttp client by default or manually using the builder.
OkHttp performs best when you create a single OkHttpClient instance and reuse it for all of your HTTP calls. This is because each client holds its own connection pool and thread pools.
3. Create a request and execute it synchronously or asynchronously.
execute() - execute request synchronously
enqueue() - execute request asynchronously
// execute request synchronously
client.newCall(request).execute().use { response ->
if (!response.isSuccessful) throw IOException("Unexpected code $response")
// do something
}
// execute request asynchronously
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")
// do something
}
}
})
dowload text, i.e. GET string
val request = Request.Builder()
.url("https://publicobject.com/helloworld.txt")
.build()
// load text synchronously with GET method
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())
}
// load text asynchronously with GET method
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())
}
}
})
POST string
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()
}
POST stream
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()
}
POST file
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()
}
POST multipart file
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()
}
download json
/* To implement rest api use retrofit library */
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?)
// You can use other JSON library like Gson
val gson = Gson()
val responseBody: ResponseBody = client.newCall(request).execute().body()
val entity: FooEntity = gson.fromJson(responseBody.string(), FooEntity::class.java)
download file
val client = OkHttpClient()
// download file synchronously
fun downloadFile(url: String, dstFile: File, cb: ((Int) -> Unit)?) {
if (dstFile.exists()) {
if (dstFile.isDirectory)
throw IllegalArgumentException("dstFile is directory, but file expected.")
} else {
dstFile.absoluteFile.parentFile?.mkdirs()
}
val request: Request = Request.Builder().url(url).build()
client.newCall(request).execute().use { response ->
if (!response.isSuccessful) {
throw IOException("Unexpected code $response")
}
val body: ResponseBody = response.body!!
val contentLength = body.contentLength()
val source = body.source()
val sink = dstFile.sink().buffer()
val sinkBuffer: Buffer = sink.buffer
var totalBytesRead: Long = 0
val bufferSize = 8 * 1024
var bytesRead: Long
// also you can use FileOutputStream instead Okio (i.e. sink)
try {
while (source.read(sinkBuffer, bufferSize.toLong())
.also { bytesRead = it } != -1L
) {
sink.emit()
totalBytesRead += bytesRead
val progress = (totalBytesRead * 100 / contentLength).toInt()
cb?.invoke(progress)
}
sink.flush()
} catch (e: Exception) {
runCatching { dstFile.delete() }.onFailure { println(it) }
println(e)
} finally {
sink.closeQuietly()
source.closeQuietly()
}
}
interceptors
Interceptors are a powerful mechanism that can monitor, rewrite, and retry calls.
Interceptors are registered as either application or network interceptors. Each interceptor chain has relative merits.
Application interceptors:
Don’t need to worry about intermediate responses like redirects and retries.
Are always invoked once, even if the HTTP response is served from the cache.
Observe the application’s original intent. Unconcerned with OkHttp-injected headers like If-None-Match.
ermitted to short-circuit and not call Chain.proceed().
Permitted to retry and make multiple calls to Chain.proceed().
Can adjust Call timeouts using withConnectTimeout, withReadTimeout, withWriteTimeout.
Network interceptors:
Able to operate on intermediate responses like redirects and retries.
Not invoked for cached responses that short-circuit the network.
Observe the data just as it will be transmitted over the network.
Access to the Connection that carries the request.
Logging intercepter
class LoggingInterceptor : Interceptor {
@Throws(IOException::class)
override fun intercept(chain: Interceptor.Chain): Response {
val request: Request = chain.request()
val t1 = System.nanoTime()
logger.info(
String.format(
"Sending request %s on %s%n%s",
request.url, chain.connection(), request.headers
)
)
val response: Response = chain.proceed(request)
val t2 = System.nanoTime()
logger.info(
String.format(
"Received response for %s in %.1fms%n%s",
response.request.url, (t2 - t1) / 1e6, response.headers)
)
return response
}
}
//-------------------
val client: OkHttpClient = OkHttpClient.Builder()
.addInterceptor(LoggingInterceptor())
.build()
//-------------------
val client2: OkHttpClient = OkHttpClient.Builder()
.addNetworkInterceptor(LoggingInterceptor())
.build()
Rewrite request example
/** This interceptor compresses the HTTP request body. Many webservers can't handle this! */
class GzipRequestInterceptor : Interceptor {
@Throws(IOException::class)
override fun intercept(chain: Interceptor.Chain): Response {
val originalRequest: Request = chain.request()
if (originalRequest.body == null || originalRequest.header("Content-Encoding") != null) {
return chain.proceed(originalRequest)
}
val compressedRequest = originalRequest.newBuilder()
.header("Content-Encoding", "gzip")
.method(originalRequest.method, gzip(originalRequest.body))
.build()
return chain.proceed(compressedRequest)
}
private fun gzip(body: RequestBody?): RequestBody {
return object : RequestBody() {
override fun contentType(): MediaType? {
return body!!.contentType()
}
override fun contentLength(): Long {
return -1 // We don't know the compressed length in advance!
}
@Throws(IOException::class)
override fun writeTo(sink: BufferedSink) {
val gzipSink: BufferedSink = GzipSink(sink).buffer()
body!!.writeTo(gzipSink)
gzipSink.close()
}
}
}
}
Rewrite response example
/** Dangerous interceptor that rewrites the server's cache-control header. */
private val REWRITE_CACHE_CONTROL_INTERCEPTOR = Interceptor { chain ->
val originalResponse = chain.proceed(chain.request())
originalResponse.newBuilder()
.header("Cache-Control", "max-age=60")
.build()
}