OkHttp in Android
OkHttp is a popular open-source HTTP client for Android and Java applications. It simplifies the process of making HTTP requests and handling responses. Here’s a basic overview of using OkHttp in an Android application:
Here are the key advantages to using OkHttp:
- HTTP/2 and SPDY Support:
- OkHttp supports the HTTP/2 and SPDY protocols, providing improved performance by allowing multiple requests and responses to be multiplexed over a single connection.
2. Connection Pooling(reduces request latency in the absence of HTTP/2):
- OkHttp automatically reuses and maintains a pool of connections, reducing the overhead of creating new connections for each request. This can significantly improve performance, especially for applications with frequent network requests.
3. Transparent GZIP Compression(shrinks download sizes):
- OkHttp automatically handles GZIP compression and decompression, reducing the amount of data transferred over the network and improving efficiency.
4. Request and Response Interceptors:
- Interceptors allow you to modify requests and responses at runtime. You can add custom interceptors to log, modify, or manipulate requests and responses before they are sent or received.
5. Timeouts:
- OkHttp allows you to set timeouts for various aspects of the network operations, such as connect timeout, read timeout, and write timeout. This helps in handling slow or unresponsive network conditions.
6. Cookie Management:
- OkHttp provides automatic cookie handling, making it easy to manage and persist cookies across multiple requests. This is essential for maintaining stateful sessions in web applications.
7. Multipart Requests:
- OkHttp simplifies the process of sending multipart requests, which is useful for uploading files or sending complex data structures.
8. WebSocket Support:
- OkHttp includes support for WebSocket communication, allowing bidirectional communication between the client and server over a single, long-lived connection.
9. TLS/SSL Configuration:
- OkHttp allows you to customize TLS/SSL settings, enabling you to control certificate pinning, choose specific cipher suites, and configure other security-related parameters.
10. Response Caching(avoids re-fetching the same data):
- OkHttp provides built-in support for response caching, allowing you to cache responses locally and reduce the need to fetch the same data repeatedly from the server.
11. Synchronous and asynchronous call support:
- OkHttp simplifies asynchronous programming by providing a callback-based approach for handling responses. This helps prevent blocking the main thread and keeps the user interface responsive.
12. Authentication Support:
- OkHttp supports various authentication mechanisms, including basic authentication and OAuth, making it suitable for a wide range of applications that require secure communication.
13. Alternative IP address detection (in IPv4 and IPv6 environments)
14 Proxy set up
How to set up OkHttp
To use OkHttp in your Android project, you need to import it in the application-level Gradle file:
implementation("com.squareup.okhttp3:okhttp:4.9.1")
You need to request the INTERNET
permission in the AndroidManifest.xml
file of your application if you would like to access network resources:
<uses-permission android:name="android.permission.INTERNET"/>
Synchronous and asynchronous GET requests, as well as query parameters.
GET requests
Synchronous GET Request:
import okhttp3.OkHttpClient
import okhttp3.Request
fun synchronousGetRequest(url: String): String {
val client = OkHttpClient()
val request = Request.Builder()
.url(url)
.build()
val response = client.newCall(request).execute()
return response.body?.string() ?: ""
}
// Example usage:
val result = synchronousGetRequest("https://example.com/api")
println(result)
This is a synchronous way to execute the request with OkHttp. (You should run this on a non-UI thread, otherwise, you will have performance issues within your application and Android will throw an error)
Asynchronous GET Request:
import okhttp3.*
fun asynchronousGetRequest(url: String) {
val client = OkHttpClient()
val request = Request.Builder()
.url(url)
.build()
client.newCall(request).enqueue(object : Callback {
override fun onFailure(call: Call, e: java.io.IOException) {
// Handle failure
}
override fun onResponse(call: Call, response: Response) {
// Handle success
val result = response.body?.string() ?: ""
// Process the response data
println(result)
}
})
}
// Example usage:
asynchronousGetRequest("https://example.com/api")
The asynchronous version of this request provides you with a callback when the response was fetched or an error occurred.
Asynchronous GET Request with Query Parameters:
import okhttp3.HttpUrl
import okhttp3.OkHttpClient
import okhttp3.Request
import okhttp3.Callback
import okhttp3.Response
import java.io.IOException
fun asynchronousGetRequestWithQueryParameters(baseUrl: String, queryParams: Map<String, String>) {
val client = OkHttpClient()
// Constructing the URL with query parameters
val httpUrl = HttpUrl.Builder().parse(baseUrl)!!
for ((key, value) in queryParams) {
httpUrl.addQueryParameter(key, value)
}
val request = Request.Builder()
.url(httpUrl.build())
.build()
client.newCall(request).enqueue(object : Callback {
override fun onFailure(call: okhttp3.Call, e: IOException) {
// Handle failure
e.printStackTrace()
}
override fun onResponse(call: okhttp3.Call, response: Response) {
// Handle success
val result = response.body?.string() ?: ""
// Process the response data
println(result)
}
})
}
// Example usage:
val baseUrl = "https://example.com/api"
val queryParams = mapOf("param1" to "value1", "param2" to "value2")
asynchronousGetRequestWithQueryParameters(baseUrl, queryParams)
The HttpUrl.Builder
is used to construct a URL with the specified base URL and query parameters. The resulting HttpUrl
is then used to build the final URL for the OkHttp request. The request is then made either synchronously or asynchronously, depending on your requirements
Synchronous POST Request:
import okhttp3.MediaType.Companion.toMediaType
import okhttp3.OkHttpClient
import okhttp3.Request
import okhttp3.RequestBody.Companion.toRequestBody
fun synchronousPostRequest(url: String, requestBody: String): String {
val client = OkHttpClient()
val body = requestBody.toRequestBody("application/json".toMediaType())
val request = Request.Builder()
.url(url)
.post(body)
.build()
val response = client.newCall(request).execute()
return response.body?.string() ?: ""
}
// Example usage:
val postResult = synchronousPostRequest("https://example.com/api", "{\"key\": \"value\"}")
println(postResult)
Asynchronous POST Request:
import okhttp3.*
fun asynchronousPostRequest(url: String, requestBody: String) {
val client = OkHttpClient()
val body = requestBody.toRequestBody("application/json".toMediaType())
val request = Request.Builder()
.url(url)
.post(body)
.build()
client.newCall(request).enqueue(object : Callback {
override fun onFailure(call: Call, e: java.io.IOException) {
// Handle failure
}
override fun onResponse(call: Call, response: Response) {
// Handle success
val result = response.body?.string() ?: ""
// Process the response data
println(result)
}
})
}
// Example usage:
asynchronousPostRequest("https://example.com/api", "{\"key\": \"value\"}")
Note that when making asynchronous requests, the callbacks are executed on a background thread, so you should avoid updating the UI directly from within the callback methods. If you need to update the UI, consider using the appropriate mechanisms such as runOnUiThread
in Android or other UI thread synchronization techniques.
File upload
Uploading a file using OkHttp in Kotlin involves creating a RequestBody
for the file and adding it to the request as a part. Below is an example demonstrating how to make a file upload request using OkHttp in Kotlin:
import okhttp3.MediaType.Companion.toMediaTypeOrNull
import okhttp3.MultipartBody
import okhttp3.OkHttpClient
import okhttp3.Request
import okhttp3.RequestBody.Companion.asRequestBody
import okhttp3.RequestBody.Companion.toRequestBody
import java.io.File
fun uploadFile(url: String, file: File) {
val client = OkHttpClient()
// Create the file part
val filePart: MultipartBody.Part = MultipartBody.Part.createFormData(
"file", // Field name
file.name, // File name
file.asRequestBody("multipart/form-data".toMediaTypeOrNull())
)
// Create the request body with the file part
val requestBody: RequestBody = MultipartBody.Builder()
.setType(MultipartBody.FORM)
.addPart(filePart)
.build()
// Create the request with the URL and request body
val request: Request = Request.Builder()
.url(url)
.post(requestBody)
.build()
// Make the request asynchronously
client.newCall(request).enqueue(object : okhttp3.Callback {
override fun onFailure(call: okhttp3.Call, e: java.io.IOException) {
// Handle failure
e.printStackTrace()
}
override fun onResponse(call: okhttp3.Call, response: okhttp3.Response) {
// Handle success
val result = response.body?.string() ?: ""
// Process the response data
println(result)
}
})
}
// Example usage:
val fileToUpload = File("path/to/your/file.txt")
uploadFile("https://example.com/upload", fileToUpload)
MultipartBody.Part
is used to create a part for the file.- The
MultipartBody.Builder
is used to build the request body with the file part. - The request is created using
Request.Builder
, specifying the URL and the request body. - The file is uploaded asynchronously using
client.newCall(request).enqueue
.
Canceling a request
In OkHttp for Kotlin, you can cancel a request by using the Call
object returned when making the request. The Call
interface provides a cancel()
method that can be used to cancel an ongoing request. Here's an example:
val call: Call = client.newCall(request)
call.enqueue(object : Callback {
override fun onFailure(call: Call, e: IOException) {
// Handle failure
}
override fun onResponse(call: Call, response: Response) {
// Handle success
}
})
// Somewhere else in your code, if you want to cancel the request
call.cancel()
Security and authorization in OkHttp
Setting an HTTP header on a request
In OkHttp for Kotlin, you can set HTTP headers on a request using the header
method of the Request.Builder
class. Here's an example of how to set an HTTP header in an OkHttp request:
import okhttp3.OkHttpClient
import okhttp3.Request
fun sendHttpRequest(url: String) {
val client = OkHttpClient()
val request = Request.Builder()
.url(url)
.header("Authorization", "Bearer YourAccessToken")
.header("Content-Type", "application/json") // You can add additional headers as needed
.build()
val response = client.newCall(request).execute()
// Handle the response as needed
if (response.isSuccessful) {
val responseBody = response.body?.string()
// Process the response data
println(responseBody)
} else {
// Handle the error response
println("Error: ${response.code} - ${response.message}")
}
}
// Example usage:
val url = "https://example.com/api"
sendHttpRequest(url)
In this example:
- The
header
method is used to set individual headers on theRequest.Builder
. - You can set multiple headers by calling
header
multiple times. - The headers set in the
Request.Builder
are included in the HTTP request when it is sent.
In the example, we set the “Authorization” header with a bearer token and the “Content-Type” header with the value “application/json”. Adjust the headers based on your specific requirements.
This is applicable for both GET and POST requests. If you are making a POST request with a request body, make sure to include the appropriate “Content-Type” header for the type of data you are sending (e.g., “application/json” for JSON data).
Now our sensitive data is only accessible if someone knows our username and password. But what if someone is listening on the network and tries to hijack our requests with a man-in-the-middle attack and fabricated certificates?
OkHttp gives you an easy way to trust only your own certificate by using certificate pinner.
Setting up certificate pinner in OkHttp
Certificate pinning is a security measure used to ensure that the server’s SSL certificate matches a predefined set of cryptographic values. In OkHttp, you can implement certificate pinning using a CertificatePinner
. Here's how you can set up certificate pinning in an OkHttp request using Kotlin:
import okhttp3.CertificatePinner
import okhttp3.OkHttpClient
import okhttp3.Request
fun makePinnedRequest() {
// Define the hostname and its associated pins (SHA-256 hashes of public keys)
val hostname = "example.com"
val pins = listOf(
"sha256/AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=",
"sha256/BBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBB="
)
// Create a CertificatePinner
val certificatePinner = CertificatePinner.Builder()
.add(hostname, *pins.toTypedArray())
.build()
// Create an OkHttpClient with the CertificatePinner
val client = OkHttpClient.Builder()
.certificatePinner(certificatePinner)
.build()
// Make a request using the OkHttpClient
val request = Request.Builder()
.url("https://$hostname/path")
.build()
val response = client.newCall(request).execute()
// Handle the response
if (response.isSuccessful) {
// Successfully pinned the certificate
val responseBody = response.body?.string() ?: ""
println("Response: $responseBody")
} else {
// Certificate pinning failed
println("Certificate pinning failed. Response code: ${response.code}")
}
}
// Example usage:
makePinnedRequest()
OkHttp configuration
We’ve already covered some usage of OkHttpClient.Builder
. This class is useful if we would like to alter the default OkHttp client behavior.
There are some parameters worth mentioning:
var client: OkHttpClient = Builder()
.cache(cache) // configure cache, see above
.proxy(proxy) // configure proxy, see above
.certificatePinner(certificatePinner) // certificate pinning, see above
.addInterceptor(interceptor) // app level interceptor, see above
.addNetworkInterceptor(interceptor) // network level interceptor, see above
.authenticator(authenticator) // authenticator for requests (it supports similar use-cases as "Authorization header" earlier
.callTimeout(10000) // default timeout for complete calls
.readTimeout(10000) // default read timeout for new connections
.writeTimeout(10000) // default write timeout for new connections
.dns(dns) // DNS service used to lookup IP addresses for hostnames
.followRedirects(true) // follow requests redirects
.followSslRedirects(true) // follow HTTP tp HTTPS redirects
.connectionPool(connectionPool) // connection pool used to recycle HTTP and HTTPS connections
.retryOnConnectionFailure(true) // retry or not when a connectivity problem is encountered
.cookieJar(cookieJar) // cookie manager
.dispatcher(dispatcher) // dispatcher used to set policy and execute asynchronous requests
.build()
WebSocket
If you are done with the WebSocket server-side implementation, you can connect to that endpoint and get real-time messaging up and running from an OkHttp client.
OkHttpClient client = new OkHttpClient();
String socketServerUrl = "ws://mytodoserver.com/realtime";
Request request = new Request.Builder().url(socketServerUrl).build();
// connecting to a socket and receiving messages
client.newWebSocket(request, new WebSocketListener() {
@Override
public void onClosed(@NotNull WebSocket webSocket, int code, @NotNull String reason) {
super.onClosed(webSocket, code, reason);
//TODO: implement your own event handling
}
@Override
public void onClosing(@NotNull WebSocket webSocket, int code, @NotNull String reason) {
super.onClosing(webSocket, code, reason);
//TODO: implement your own event handling
}
@Override
public void onFailure(@NotNull WebSocket webSocket, @NotNull Throwable t, @Nullable Response response) {
super.onFailure(webSocket, t, response);
//TODO: implement your own event handling
}
@Override
public void onMessage(@NotNull WebSocket webSocket, @NotNull String text) {
super.onMessage(webSocket, text);
//TODO: implement your own event handling for incoming messages
}
@Override
public void onMessage(@NotNull WebSocket webSocket, @NotNull ByteString bytes) {
super.onMessage(webSocket, bytes);
//TODO: implement your own event handling for incoming messages
}
@Override
public void onOpen(@NotNull WebSocket webSocket, @NotNull Response response) {
super.onOpen(webSocket, response);
//TODO: implement your own event handling
}
});
// sending message
webSocket.send("new_todo_added");
Testing
We can’t forget about testing. OkHttp delivers its own MockWebServer to help test HTTP and HTTPS network calls. It lets us specify which response to return to which request and verifies every part of that request.
To start, we need to import it via Gradle:
testImplementation("com.squareup.okhttp3:mockwebserver:4.9.1")
Here are some important APIs:
MockWebServer.start()
: starts the mock web server on the local hostMockWebServer.enqueue(mockResponse)
: queues aMockResponse
. This is a FIFO queue that ensures the requests will receive responses in order as there were queuedMockResponse
: a scriptable OkHttp responseRecordRequest
: an HTTP request that was received by theMockWebServer
MockWebServer.takeRequest()
: takes the next request arrived to theMockWebServer