Skip to content

ZHttp is a Kotlin-based, type-safe, asynchronous/synchronous, and thread-safe HTTP Client Library built for the JVM.

Notifications You must be signed in to change notification settings

muhammadzkralla/ZHttp

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

ZHttp Client Library

Introduction

ZHttp is a Kotlin-based HTTP Client Library offering various use cases. I've abstracted away all the technical details of making an asynchronous (non-blocking) HTTP request, making it very easy to use and beginner-friendly.

This does not mean that you cannot customize your request. In fact, you can ignore all my threading and serialization/deserialization logic and make a completely raw request, where you can handle reactive programming (blocking or non-blocking) and the deserialization process. In manual mode, you take complete control over the whole process.

ZHttp is not built on any high-level or low-level networking or threading libraries. The only third-party library used is Google's Gson library for serialization/deserialization.

There are some useful settings you can customize in ZHttp. You can set default headers, connection and read time out periods, buffer size for uploading files, enable logging, intercept requests with auth headers, and create request retry mechanisms.

• Refer to this Demo for a simple example.
• Refer to this Demo for an actual app example.

Each feature is tested with some unit tests.

Features

• Supports all JVM projects like Android, Spring Boot, console apps, etc. ✅
• Beginner-friendly & simple to use. ✅
• Thread-safe & type-safe. ✅
• Supports both asynchronous (non-blocking) and synchronous (blocking) requests. ✅
• Leverages Kotlin Coroutines for callback or suspended requests. ✅
• Handles serialization and deserialization. ✅
• Has cancellation strategy. ✅
• Has request-retry strategy. ✅
• Auto-injects Basic/Bearer authentication headers. ✅
• Highly customizable. ✅
• Supports GET, POST, DELETE, PUT, PATCH, and MULTIPART requests. ✅

Installation

To start using ZHttp, you should add the dependencies to your project :

If you are using Gradle :

// Add this part to your settings.gradle.kts :
dependencyResolutionManagement {
       repositoriesMode.set(RepositoriesMode.FAIL_ON_PROJECT_REPOS)
       repositories {
            mavenCentral()
            maven("https://jitpack.io")
       }
}
// Add this dependency to your build.gradle.kts (module) :
dependencies {
      implementation("com.github.muhammadzkralla:ZHttp:2.8.9")
}

If you are using Maven :

<!-- Add this part to the repositories in pom.xml : -->
<repositories>
	<repository>
		<id>jitpack.io</id>
		<url>https://jitpack.io</url>
	</repository>
</repositories>
<!-- Add this to the dependencies in pom.xml : -->
<dependency>
	<groupId>com.github.muhammadzkralla</groupId>
	<artifactId>ZHttp</artifactId>
	<version>2.8.9</version>
</dependency>

How to use

As stated above, ZHttp supports the GET, POST, DELETE, PUT, PATCH, and MULTIPART requests. We will illustrate how to use each one of them below.

ZHttpClient


ZHttpClient is your gate to make any request. To get started, we make an instance of the client :

// Creating a client instance.
val client = ZHttpClient.Builder()
            .baseUrl(BASE_URL)
            .build()

You can also specify the connection and the read time out periods and allow logging in console :

// Setting the connection and the read time out periods to 20 seconds.
val client = ZHttpClient.Builder()
            .baseUrl(BASE_URL)
            .connectionTimeout(20000)
            .readTimeout(20000)
            .allowLogging(true)
            .build()

Note: There's no logger configured for now. Logging is just println() and System.err.println() for now until adding a logger for all platforms, and it's disabled by default.

You can also add some default headers, these headers will be automatically included on each request made with this client instance :

// Setting the connection and the read time out periods to 20 seconds.
// Assigning some default headers to be included in each request.
val defaultHeaders = listOf(
            Header("Content-Type", "application/json; charset=UTF-8"),
            Header("Authorization", "Bearer $token")
)

val client = ZHttpClient.Builder()
            .baseUrl(BASE_URL)
            .connectionTimeout(20000)
            .readTimeout(20000)
            .allowLogging(true)
            .defaultHeaders(defaultHeaders)
            .build()

You can also specify the buffer size for file uploading in MULTIPART requests :

// Setting the connection and the read time out periods to 20 seconds.
// Assigning some default headers to be included in each request.
// Setting the buffer size for file uploading in MULTIPART requests to 8 KB.
val defaultHeaders = listOf(
            Header("Content-Type", "application/json; charset=UTF-8"),
            Header("Authorization", "Bearer $token")
)

val client = ZHttpClient.Builder()
            .baseUrl(BASE_URL)
            .connectionTimeout(20000)
            .readTimeout(20000)
            .allowLogging(true)
            .defaultHeaders(defaultHeaders)
            .filesBufferSize(8 * 1024)
            .build()

You can also specify authentication headers to automatically inject them into each request with the client :

// Setting the connection and the read time out periods to 20 seconds.
// Assigning some default headers to be included in each request.
// Setting the buffer size for file uploading in MULTIPART requests to 8 KB.
val defaultHeaders = listOf(
            Header("Content-Type", "application/json; charset=UTF-8"),
            Header("Authorization", "Bearer $token")
)

val client = ZHttpClient.Builder()
            .baseUrl(BASE_URL)
            .connectionTimeout(20000)
            .readTimeout(20000)
            .allowLogging(true)
            .defaultHeaders(defaultHeaders)
            .filesBufferSize(8 * 1024)
            .authenticated(Bearer("token")) // Bearer auth
            .authenticated(Basic("foo", "bar")) // Basic auth
            .build()

And you can specify a request-retry strategy, customizing when to retry a request, the number of retry attempts, and the interval between each attempt.

// Setting the connection and the read time out periods to 20 seconds.
// Assigning some default headers to be included in each request.
// Setting the buffer size for file uploading in MULTIPART requests to 8 KB.
val defaultHeaders = listOf(
            Header("Content-Type", "application/json; charset=UTF-8"),
            Header("Authorization", "Bearer $token")
)

val client = ZHttpClient.Builder()
            .baseUrl(BASE_URL)
            .connectionTimeout(20000)
            .readTimeout(20000)
            .allowLogging(true)
            .defaultHeaders(defaultHeaders)
            .filesBufferSize(8 * 1024)
            .authenticated(Bearer("token")) // Bearer auth
            .authenticated(Basic("foo", "bar")) // Basic auth
            .requestRetryMechanism(
                RequestRetryMechanism(
                    retryCount = 5,
                    retryDelay = 6000L,
                    retryOnExceptions = listOf(
                        NullPointerException::class,
                        SocketTimeoutException::class,
                        JsonParseException::class
                    ),
                    retryOnCode = HttpStatusInterval.SERVER_ERROR
                )
            )
            .build()

Please Note That: For each property of the above, there's a default value used if you do not specify them explicitly.

• baseUrl: is an empty string by default.
• connectionTimeout & readTimeout: are 20 seconds by default.
• allowLogging: is false by default.
• defaultHeaders: are "Content-Type:application/json" by default.
• filesBufferSize: is 8 KB by default.
• authenticated: is null by default.
• requestRetryMechanism: is null by default.

Finally, you can set the default headers after building the client by this function :

// Setting the default headers of the client.
client.setDefaultHeaders(headers)

Or removing them by this function :

// Removing the default headers of the client.
client.removeDefaultHeaders()

Asynchronous Coroutine GET


To make a suspended GET request using ZHttp, here's an example of the syntax :

// The syntax of a GET request.
val response = client.get<TYPE>(ENDPOINT, QUERIES, HEADERS)

It will return you a response of the specified TYPE

This response is a data class that contains the response code, the deserialized body, response headers, permissions, and exceptions of the HTTP request.

IMPORTANT: TYPE is generic, that means that it can be of type string, data class, list of objects, map of any object to any object..etc It's totally type-safe.

Note: QUERIES, HEADERS arguments can be null but, if you want to add HEADERS or QUERIES to the request :

// Adding custom headers and queries to the request.
val HEADERS = listOf(
            Header("Content-Type", "application/json; charset=UTF-8"),
            Header("Authorization", "Bearer $token")
)
        
val QUERIES = listOf(
            Query("param1", "value"),
            Query("param2", "value")
)

Asynchronous Lambda GET


To make a callback GET request using ZHttp, here's an example of the syntax :

// The syntax of a GET request.
val getRequest = client.get<TYPE>(ENDPOINT, QUERIES, HEADERS) { success, failure ->
	
}

To cancel the request :

// Cancelling the request to free up resources.
getRequest.cancel()

Asynchronous Coroutine POST


To make a suspended POST request using ZHttp, here's an example of the syntax :

// The syntax of a POST request.
val response = client.post<TYPE>(ENDPOINT, BODY, QUERIES, HEADERS)

It will return you a response of the specified TYPE

IMPORTANT: BODY is generic, that means that it can be a string, data class object, list of objects, map of any object to any object..etc It's totally type-safe.

Asynchronous Lambda POST


To make a callback POST request using ZHttp, here's an example of the syntax :

// The syntax of a POST request.
val postRequest = client.post<TYPE>(ENDPOINT, BODY, QUERIES, HEADERS) { success, failure ->
	
}

Note: HEADERS, QUERIES, TYPE, logging messages, and cancellation strategy follow the same rules as in the GET request.

Asynchronous Coroutine DELETE


To make a suspended DELETE request using ZHttp, here's an example of the syntax :

// The syntax of a DELETE request.
val response = client.delete<TYPE>(ENDPOINT, QUERIES, HEADERS)

It will return you a response of the specified TYPE

Asynchronous Lambda DELETE


To make a callback DELETE request using ZHttp, here's an example of the syntax :

// The syntax of a DELETE request.
val deleteRequest = client.delete<TYPE>(ENDPOINT, QUERIES, HEADERS) { success, failure ->
	
}

Note: HEADERS, QUERIES, TYPE, logging messages, and cancellation strategy follow the same rules as in the GET request.

Asynchronous Coroutine PUT


To make a suspended PUT request using ZHttp, here's an example of the syntax :

// The syntax of a PUT request.
val response = client.put<TYPE>(ENDPOINT, BODY, QUERIES, HEADERS)

It will return you a response of the specified TYPE

Asynchronous Lambda PUT


To make a callback PUT request using ZHttp, here's an example of the syntax :

// The syntax of a PUT request.
client.put<TYPE>(ENDPOINT, BODY, QUERIES, HEADERS) { success, failure ->
	
}

IMPORTANT: BODY is generic, that means that it can be a string, data class object, list of objects, map of any object to any object..etc It's totally type-safe.

Note: HEADERS, QUERIES, TYPE, logging messages, and cancellation strategy follow the same rules as in the GET request.

Asynchronous Coroutine PATCH


To make a suspended PATCH request using ZHttp, here's an example of the syntax :

// The syntax of a PATCH request.
val ARG = JsonObject().apply {
            addProperty("arg", "New Value!")
}
// Or you can do it as a data class if you don't want to use Gson:
data class UpdateArg(val arg: Any? = null)
val ARG = UpdateArg(arg = "New Value!")

val response = client.patch<TYPE>(ENDPOINT, ARG, QUERIES, HEADERS)

It will return you a response of the specified TYPE

Asynchronous Lambda PATCH


To make a callback PATCH request using ZHttp, here's an example of the syntax :

// The syntax of a PATCH request.
val ARG = JsonObject().apply {
            addProperty("arg", "New Value!")
}
// Or you can do it as a data class if you don't want to use Gson:
data class UpdateArg(val arg: Any? = null)
val ARG = UpdateArg(arg = "New Value!")

val patchRequest = client.patch<TYPE>(ENDPOINT, ARG, QUERIES, HEADERS) { success, failure ->
	
}

Note: HEADERS, QUERIES, TYPE, logging messages, and cancellation strategy follow the same rules as in the GET request.

Asynchronous MULTIPART


To make a MULTIPART request using ZHttp, here's an example of the syntax :

In case of an object part, you should make a MultipartBody of the object :

// Making a MultipartBody of a serializable object.
val objectMultiPartBody = MultipartBody(
                    name = "NAME (REQUIRED)",
                    body = obj,
                    contentType = "application/json"
)

In case of a file part, you should make a MultipartBody of the file :

// Making a MultipartBody of a file.
val imageMultipartBody = MultipartBody(
                        name = "FILE_NAME (REQUIRED)",
                        filePath = "FILE_PATH",
                        contentType = "image/*"
)

Note: The name is not optional, it must be provided as it identifies the part sent to the server side, however, it is not required to have a specific value, if it is not important to you or you just don't know what to name it, use any descriptive name.

After you make all your MultipartBody objects, you should now add them in a list :

// Add all your parts in a list.
val PARTS = listOf(objectMultiPartBody, imageMultipartBody)

Finally create the MULTIPART request itself :

Asynchronous Coroutine MULTIPART


// The syntax of a MULTIPART request.
val response = client.multiPart<TYPE>(ENDPOINT, PARTS, QUERIES, HEADERS)

It will return you a response of the specified TYPE

Asynchronous Lambda MULTIPART


// The syntax of a MULTIPART request.
val multipart = client.multiPart<TYPE>(ENDPOINT, PARTS, QUERIES, HEADERS) { success, failure ->
	
}

Note: HEADERS, QUERIES, TYPE, logging messages, and cancellation strategy follow the same rules as in the GET request.

CRUCIAL: Please do not attempt to make a MultipartBody with both body and filePath in the same object, for example :

// DO NOT MAKE THIS :
val badMultiPartBody = MultipartBody(
                    name = "NAME (REQUIRED)",
                    body = obj,
                    filePath = "FILE_PATH",
                    contentType = "application/json"
)

As MultipartBody is designed to contain the data of only one part, either an object, or a file.

Manual Mode


As stated earlier, ZHttp is engineered for all developers, not just beginners, as it supports full customization for your HTTP request. The manual mode is designed to make you take complete control over your HTTP request.

The request made with manual mode is just like using a manual car, you specify everything. You must handle the threading yourself using for example: Kotlin Coroutines / RxJava / AsyncTasks..etc and handle the response deserialization.

An instance of the client is required in the manual mode as the base url, connection / read time out periods, default headers, and buffer size values are applied to the synchronous request too.

Note: All the synchronous functions are annotated with the synchronized annotation meaning that they are all thread-safe.

Synchronous GET


To make a synchronous GET request using ZHttp, here's an example of the syntax :

// The syntax of a synchronous GET request.
val response = ZGet(client).doGet(END_POINT, QUERIES, HEADERS)

The response variable is a data class that contains the response code, the serialized body as a raw string, response headers, permissions, and exceptions. Please remember that the body is the raw body string that is received from the HTTP request so, you need to deserialize the response yourself.

Note: The code above should not be called from the main thread, if you do so, an android.os.NetworkOnMainThreadException will be thrown.

Synchronous POST


To make a synchronous POST request using ZHttp, here's an example of the syntax :

// The syntax of a synchronous POST request.
val response = ZPost(client).doPost(END_POINT, BODY, QUERIES, HEADERS)

Synchronous DELETE


To make a synchronous DELETE request using ZHttp, here's an example of the syntax :

// The syntax of a synchronous DELETE request.
val response = ZDelete(client).doDelete(END_POINT, QUERIES, HEADERS)

Synchronous PUT


To make a synchronous PUT request using ZHttp, here's an example of the syntax :

// The syntax of a synchronous PUT request.
val response = ZPut(client).doPut(END_POINT, BODY, QUERIES, HEADERS)

Synchronous PATCH


To make a synchronous PATCH request using ZHttp, here's an example of the syntax :

// The syntax of a synchronous PATCH request.
val ARG = JsonObject().apply {
                addProperty("arg", "New Value!")
}
// Or you can do it as a data class :
data class UpdateArg(val arg: Any? = null)
val ARG = UpdateArg(arg = "New Value!")

val response = ZPatch(client).doPatch(END_POINT, ARG, QUERIES, HEADERS)

Synchronous MULTIPART


To make a synchronous MULTIPART request using ZHttp, here's an example of the syntax :

// The syntax of a synchronous MULTIPART request.
val response = ZMultipart(client).doMultipart(END_POINT, PARTS, QUERIES, HEADERS)

ZHttp supports both HTTP and HTTPS websites. To enable communication with HTTP websites for Android, you can add the following attribute to your AndroidManifest.xml file:

<application
    android:usesCleartextTraffic="true">
</application>

Note: Please note that while this configuration allows communication with HTTP websites, it is generally not recommended due to security concerns. It's recommended to use HTTPS whenever possible to ensure data integrity and confidentiality during transit.