{"id":34531639,"url":"https://github.com/incept5/http-lib","last_synced_at":"2026-05-26T21:01:17.665Z","repository":{"id":285563533,"uuid":"958502553","full_name":"Incept5/http-lib","owner":"Incept5","description":"A Http Client library for accessing REST APIs within your services","archived":false,"fork":false,"pushed_at":"2026-01-13T09:17:04.000Z","size":118,"stargazers_count":0,"open_issues_count":0,"forks_count":0,"subscribers_count":6,"default_branch":"main","last_synced_at":"2026-01-13T11:43:39.908Z","etag":null,"topics":["http-client","kotlin"],"latest_commit_sha":null,"homepage":"","language":"Kotlin","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":null,"status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/Incept5.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":null,"code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null}},"created_at":"2025-04-01T09:54:48.000Z","updated_at":"2026-01-13T09:14:44.000Z","dependencies_parsed_at":"2025-04-01T13:26:57.971Z","dependency_job_id":"582f4a4a-dd9a-4d0f-8b93-d32ab86edcab","html_url":"https://github.com/Incept5/http-lib","commit_stats":null,"previous_names":["incept5/http-lib"],"tags_count":1,"template":false,"template_full_name":null,"purl":"pkg:github/Incept5/http-lib","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Incept5%2Fhttp-lib","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Incept5%2Fhttp-lib/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Incept5%2Fhttp-lib/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Incept5%2Fhttp-lib/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/Incept5","download_url":"https://codeload.github.com/Incept5/http-lib/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Incept5%2Fhttp-lib/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":33538660,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-05-26T15:22:16.424Z","status":"ssl_error","status_checked_at":"2026-05-26T15:22:15.568Z","response_time":63,"last_error":"SSL_connect returned=1 errno=0 peeraddr=140.82.121.5:443 state=error: unexpected eof while reading","robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":false,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["http-client","kotlin"],"created_at":"2025-12-24T05:43:20.264Z","updated_at":"2026-05-26T21:01:17.653Z","avatar_url":"https://github.com/Incept5.png","language":"Kotlin","funding_links":[],"categories":[],"sub_categories":[],"readme":"# Http Lib\n\nBase classes for implementing http gateway services\n\n## Installation\n\n### Gradle (Kotlin DSL)\n\nAdd the JitPack repository to your build file:\n\n```kotlin\nrepositories {\n    mavenCentral()\n    maven { url = uri(\"https://jitpack.io\") }\n}\n```\n\nAdd the dependency:\n\n```kotlin\ndependencies {\n    implementation(\"com.github.incept5:http-lib:1.0.0\") // Replace with the latest version\n}\n```\n\n### Gradle (Groovy DSL)\n\nAdd the JitPack repository to your build file:\n\n```groovy\nrepositories {\n    mavenCentral()\n    maven { url 'https://jitpack.io' }\n}\n```\n\nAdd the dependency:\n\n```groovy\ndependencies {\n    implementation 'com.github.incept5:http-lib:1.0.0' // Replace with the latest version\n}\n```\n\n### Maven\n\nAdd the JitPack repository to your pom.xml:\n\n```xml\n\u003crepositories\u003e\n    \u003crepository\u003e\n        \u003cid\u003ejitpack.io\u003c/id\u003e\n        \u003curl\u003ehttps://jitpack.io\u003c/url\u003e\n    \u003c/repository\u003e\n\u003c/repositories\u003e\n```\n\nAdd the dependency:\n\n```xml\n\u003cdependency\u003e\n    \u003cgroupId\u003ecom.github.incept5\u003c/groupId\u003e\n    \u003cartifactId\u003ehttp-lib\u003c/artifactId\u003e\n    \u003cversion\u003e1.0.0\u003c/version\u003e \u003c!-- Replace with the latest version --\u003e\n\u003c/dependency\u003e\n```\n\n## Usage\n\n### Basic Gateway functionality\n\nTo create a basic gateway you just need to subclass HttpClient like this:\n\n    open class ExampleHttpGateway (baseUri: String) : HttpClient(baseUri) {\n\n        /**\n         * Get something by id and marshal the response into an ExamplePayload\n         */\n        fun getSomethingById (id: UUID) : ExamplePayload {\n            return super.get(\"/something/$id\", null)\n        }\n    \n        fun getSomethingByQuery (query: String) : ExamplePayload {\n            return super.get(\"/something\", query)\n        }\n    \n        fun postSomething (payload: ExamplePayload) {\n            super.postJson\u003cExamplePayload\u003e(\"/something\", payload)\n        }\n    \n        fun putSomething (id: UUID, payload: ExamplePayload) {\n            super.putJson\u003cExamplePayload\u003e(\"/something/$id\", payload)\n        }\n    \n        fun deleteSomething (id: UUID) {\n            super.delete(\"/something/$id\")\n        }\n    \n        fun patchSomething (id: UUID, payload: ExamplePayload) {\n            super.patchJson\u003cExamplePayload\u003e(\"/something/$id\", payload)\n        }\n    }\n\nThis gives you Json friendly marshalling and unmarshalling of payloads for all the http methods.\n\n### Doing Something special\n\nIf you need to set specific headers or handle a response in a special way you can call the execute method and access the lower level okhttp3 classes like:\n\n    open class ExampleHttpGateway (baseUri: String) : HttpClient(baseUri) {\n\n        fun doSomethingSpecial (payload: MyPayload) : ExamplePayload {\n            val url = url(\"/my-special-thing\", null)\n            val body = Json.toJson(payload).toRequestBody(\"application/json\".toMediaType())\n            val request = Request.Builder()\n                .url(url)\n                .post(body)\n                .header(\"Special-Header\", \"special-value\")\n                .build()\n            return execute(request) { response -\u003e\n                // do something special with the response etc\n            }\n        }\n    }    \n\n### Authentication\n\nIf you are hitting an API that is authenticated then most likely you will just need to provide\nan implementation of AuthTokenFetcher that is responsible for fetching a new token whenever needed.\n\nThe library includes built-in support for OIDC Client Credentials grant through `ClientCredentialsTokenFetcher`:\n\n#### Basic Client Credentials Authentication\n\n```kotlin\n// Create configuration\nval config = StdClientCredentialsConfig(\n    tokenEndpoint = \"https://auth.example.com/oauth/token\",\n    clientId = \"your-client-id\",\n    clientSecret = \"your-client-secret\"\n)\n\n// Create token fetcher\nval tokenFetcher = ClientCredentialsTokenFetcher(config)\n\n// Create gateway with authentication\nclass ExampleGatewayWithAuth(\n    baseUri: String, \n    tokenFetcher: AuthTokenFetcher\n) : HttpClient(baseUri, tokenFetcher = tokenFetcher) {\n\n    fun getSomethingById(id: UUID): ExamplePayload {\n        return super.get(\"/something/$id\", null)\n    }\n}\n```\n\n#### Client Credentials with Scopes\n\nYou can specify OAuth scopes when requesting access tokens:\n\n```kotlin\nval config = StdClientCredentialsConfig(\n    tokenEndpoint = \"https://auth.example.com/oauth/token\",\n    clientId = \"your-client-id\",\n    clientSecret = \"your-client-secret\",\n    scope = \"payment:create payment:read webhook:read webhook:write\"\n)\n\nval tokenFetcher = ClientCredentialsTokenFetcher(config)\nval gateway = ExampleGatewayWithAuth(baseUri, tokenFetcher)\n```\n\n#### Form-Based Client Credentials\n\nIf your OAuth provider requires credentials in the request body instead of Basic authentication:\n\n```kotlin\nval config = StdClientCredentialsConfig(\n    tokenEndpoint = \"https://auth.example.com/oauth/token\",\n    clientId = \"your-client-id\",\n    clientSecret = \"your-client-secret\",\n    scope = \"read write\" // optional\n)\n\nval tokenFetcher = FormBasedClientCredentialsTokenFetcher(config)\nval gateway = ExampleGatewayWithAuth(baseUri, tokenFetcher)\n```\n\n#### Custom Authentication\n\nFor custom authentication flows, implement the `AuthTokenFetcher` interface:\n\n```kotlin\nclass CustomTokenFetcher : AuthTokenFetcher {\n    override fun fetchNewToken(): TokenResponse {\n        // Your custom token fetching logic\n        return TokenResponse(access_token = \"your-token\")\n    }\n}\n```\n\n### Error Handling\n\nBy default the gateway will use the default failure handler which will map http status codes to ErrorCategories\nand will throw an HttpRequestFailedException with the appropriate category and message.\n409 and 5XX errors are marked as retryable.\n\n    class DefaultHttpFailureHandler : HttpFailureHandler {\n        override fun handleFailedResponse(response: Response) {\n            if ( !response.isSuccessful ) {\n                logger.warn { \"http request returned code: ${response.code} with body: ${response.body?.string()}\" }\n                val category = when (response.code) {\n                    401 -\u003e ErrorCategory.AUTHENTICATION\n                    403 -\u003e ErrorCategory.AUTHORIZATION\n                    404 -\u003e ErrorCategory.NOT_FOUND\n                    409 -\u003e ErrorCategory.CONFLICT\n                    else -\u003e ErrorCategory.UNEXPECTED\n                }\n                val message = \"http request failed with code: ${response.code}\"\n                // retry on 409 and 5XX\n                val retryable = response.code == 409 || response.code \u003e= 500\n                throw HttpRequestFailedException(category, message, retryable)\n            }\n        }\n    }\n    \nYou can configure an alternate failure handler via the constructor of HttpClient:\n\n    class ExampleGatewayWithCustomFailureHandler (baseUri: String) : HttpClient(baseUri, failureHandler = CustomHttpFailureHandler()) {\n    }\n\n### Retries\n\nThe library includes a built-in retry mechanism through the `RetryInterceptor` that automatically handles transient failures in HTTP requests. By default, this interceptor will:\n\n- Retry requests that return HTTP 409 (Conflict) or any 5XX (Server Error) status codes\n- Perform up to 3 retry attempts\n- Wait 500ms between retry attempts\n\n#### Basic Usage\n\nThe `RetryInterceptor` is automatically included with the default `HttpClient` configuration, so you don't need to do anything special to enable basic retry functionality.\n\n#### Customizing Retry Behavior\n\nYou can customize the retry behavior by providing your own `RetryInterceptor` instance when creating your gateway:\n\n```kotlin\nclass ExampleGatewayWithRetries(baseUri: String) : HttpClient(\n    baseUri = baseUri,\n    retryInterceptor = RetryInterceptor(\n        maxRetries = 5,                     // Increase max retries to 5\n        pauseBetweenRetriesMs = 1000,       // Wait 1 second between retries\n        retryPolicy = MyCustomRetryPolicy() // Use custom retry logic\n    )\n) {\n    // Gateway methods\n}\n```\n\n#### Custom Retry Policies\n\nYou can implement your own retry policy by implementing the `RetryPolicy` interface:\n\n```kotlin\nclass MyCustomRetryPolicy : RetryPolicy {\n    override fun shouldRetry(response: Response): Boolean {\n        // Custom logic to determine if a retry should be attempted\n        // For example, retry on 429 (Too Many Requests) in addition to default cases\n        return response.code == 409 || response.code == 429 || response.code in 500..599\n    }\n}\n```\n\n#### Retry Logging\n\nThe `RetryInterceptor` logs retry attempts at the INFO level. Each retry will log:\n- The URL being retried\n- The response code that triggered the retry\n- The current retry count\n- The maximum number of retries configured\n\n#### When to Use Custom Retry Configuration\n\nConsider customizing the retry configuration when:\n\n1. **Working with rate-limited APIs**: Increase pause time between retries and add 429 status code handling\n2. **Critical operations**: Increase the number of retry attempts for important operations\n3. **Specific error handling**: Create custom policies to handle specific API response patterns\n4. **Performance optimization**: Adjust pause times based on operation importance and expected recovery time\n\n#### Disabling Retries\n\nIf you need to disable the retry mechanism for a specific gateway, you can pass `null` for the `retryInterceptor` parameter:\n\n```kotlin\nclass GatewayWithoutRetries(baseUri: String) : HttpClient(\n    baseUri = baseUri,\n    retryInterceptor = null\n) {\n    // Gateway methods\n}\n```\n    \n### Logging\n\nBy default, we will use our own JsonLoggingInterceptor to log logbook style JSON for requests and response.\n\nTo see this in your logs you will need to turn on trace logging for the org.incept5.http.interceptors package.\n\n    \u003clogger name=\"org.incept5.http.interceptors\" level=\"TRACE\"/\u003e\n\nor in your application.yaml:\n\n    quarkus:\n      log:\n        category:\n          \"org.incept5.http.interceptors\":\n            level: TRACE\n\nIt will automatically redact the Authorization header if it is present in the request.\n\nIf you need to redact things from your request or response then you can customise as follows:\n\n    class MyGateway(baseUri: String) : HttpClient(\n        baseUri = baseUri,\n        loggingInterceptor = JsonLoggingInterceptor(\n            RedactConfig(\n                requestBodyElements = listOf(\"name\", \"secret\"),\n                responseBodyElements = listOf(\"name\", \"secret\"),\n                headers = listOf(\"My-Secret-Header\"),\n                queryParameters = listOf(\"myspecialqueryparam\")\n            )\n        )\n    )\n\nAnd the output will be like:\n\n    2024-04-09T19:38:05.586 TRACE c.v.h.i.JsonLoggingInterceptor - {\"id\":\"a43d269362db\",\"type\":\"request\",\"method\":\"POST\",\"url\":\"http://localhost:51460/something\",\"headers\":{\"X-Correlation-ID\":\"c41e98e8-2164-4a42-881b-a32cf171da35\"},\"body\":{\"id\":\"9ab2d459-5236-47f4-8c61-1ee866f6f9fc\",\"name\":\"xxxx\",\"age\":42,\"components\":[{\"secret\":\"xxxx\"}]}}\n    2024-04-09T19:38:05.588 TRACE c.v.h.i.JsonLoggingInterceptor - {\"id\":\"a43d269362db\",\"type\":\"response\",\"status\":201,\"headers\":{\"Content-Type\":\"application/json\",\"Content-Length\":\"103\"},\"body\":{\"id\":\"9ab2d459-5236-47f4-8c61-1ee866f6f9fc\",\"name\":\"xxxx\",\"age\":42,\"components\":[{\"secret\":\"xxxx\"}]}}\n\n\n#### Unredacted Logging in Tests\n\nIf you want to turn off the redacting you can switch the logger to this one:\n\n    quarkus:\n      log:\n        category:\n          \"org.incept5.http.interceptors-without-redaction\":\n            level: TRACE\n\n### Unit Testing\nIf you want to unit test your gateway class you can use Mockwebserver by adding the following to your toml:\n    \n    [versions]\n    okhttp3 = \"4.12.0\"\n\n    [libraries]\n    mockwebserver = { module = \"com.squareup.okhttp3:mockwebserver\", version.ref = \"okhttp3\" }\n\nAnd then add a dependency to your build file:\n\n    // test web interactions\n    testImplementation(libs.mockwebserver)\n\nAnd then use it like:\n\n    should(\"get something by id\") {\n        val server = MockWebServer()\n        val gateway = ExampleHttpGateway(server.url(\"/\").toString())\n        val id = UUID.randomUUID()\n        server.enqueue(\n            MockResponse()\n                .setResponseCode(200)\n                .setBody(Json.toJson(ExamplePayload(id, \"get_by_id\", 42)))\n        )\n        val result = gateway.getSomethingById(id)\n        result.id shouldBe id\n        val request = server.takeRequest()\n        request.method shouldBe \"GET\"\n        request.path shouldBe \"/something/$id\"\n        server.shutdown()\n    }\n\nFor more info on how to use Mockwebserver see: https://github.com/square/okhttp/tree/master/mockwebserver\n\n### Integration Testing with Wiremock\n\nFor integration testing your gateway when running inside a Quarkus application you should use Wiremock by adding this to your toml:\n\n    [versions]\n    quarkus-wiremock = \"1.1.1\"\n\n    [libraries]\n    quarkus-wiremock = { module = \"io.quarkiverse.wiremock:quarkus-wiremock\", version.ref = \"quarkus-wiremock\" }\n    quarkus-wiremock-test = { module = \"io.quarkiverse.wiremock:quarkus-wiremock-test\", version.ref = \"quarkus-wiremock\" }\n\nAnd then add 2 dependencies to your build file:\n\n    testImplementation(libs.quarkus.wiremock)\n    testImplementation(libs.quarkus.wiremock.test)\n\nThen configure your gateway yaml config properties in the test application.yaml like:\n\n    # Example of how to use the WireMock DevServices to mock an external HTTP service\n    example:\n        http:\n            base-url: http://localhost:${quarkus.wiremock.devservices.port}\n            token-endpoint: http://localhost:${quarkus.wiremock.devservices.port}/token\n            client-id: test-client\n            client-secret: test-secret\n\nAnd then use it like:\n\n    @QuarkusTest\n    @ConnectWireMock\n    class ExampleGatewayTest {\n    \n        val KNOWN_ID = UUID.fromString(\"08cb06af-bbbf-42c1-af48-5411b1deb388\")\n    \n        @Inject\n        lateinit var exampleGateway: ExampleGatewayWithAuth\n        lateinit var wireMock: WireMock\n\n        /**\n         * The mapping files are located in src/test/resources/mappings\n         */\n        @Test\n        fun `example gateway test using wiremock mappings file`() {\n            val payload = exampleGateway.getSomethingById(KNOWN_ID)\n            assert(payload.id == KNOWN_ID)\n            assert(payload.name == \"Joe Bloggs\")\n            assert(payload.age == 23)\n        }\n    \n        @Test\n        fun `example gateway test using wiremock stubs`() {\n    \n            val id = UUID.randomUUID()\n    \n            wireMock.register(\n                WireMock.get(WireMock.urlEqualTo(\"/something/$id\"))\n                    .willReturn(\n                        WireMock.aResponse()\n                            .withStatus(200)\n                            .withHeader(\"Content-Type\", \"application/json\")\n                            .withBody(\n                                \"\"\"\n                                {\n                                    \"id\": \"$id\",\n                                    \"name\": \"Mary Shelley\",\n                                    \"age\": 42\n                                }\n                                \"\"\".trimIndent()\n                            )\n                    )\n            )\n    \n            val payload = exampleGateway.getSomethingById(id)\n            assert(payload.id == id)\n            assert(payload.name == \"Mary Shelley\")\n            assert(payload.age == 42)\n        }\n    \n    }\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fincept5%2Fhttp-lib","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fincept5%2Fhttp-lib","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fincept5%2Fhttp-lib/lists"}