Home Using declarative HTTP client in Spring Shell project with Kotlin
Post
Cancel

Using declarative HTTP client in Spring Shell project with Kotlin

Introduction

Since January 25, 2023, we got new version of Spring Shell - 3.0.0.

This release uses Spring Boot 3.0.2. Hence, Spring 6. Hence, we can use declarative HTTP client.

Project setup

You can generate a new Spring Shell project using start.spring.io.

It does not matter how you create a project. It is essential to have declared dependencies:

1
2
    implementation("org.springframework.shell:spring-shell-starter") // version above 3.0.0
    implementation("org.springframework.boot:spring-boot-starter-webflux") // version above 3.0.2

Creating a sample declarative HTTP client

For example, we create an HTTP client for GitHub to fetch a list of repositories.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
@HttpExchange(url = "https://api.github.com")
internal interface GithubClient {
    @GetExchange("/users/{username}/repos")
    fun getUserRepos(
        @PathVariable username: String,
        @RequestParam page: Int = 1,
        @RequestParam per_page: Int = 100,
        @RequestHeader("Authorization") token: String,
        @RequestHeader("Accept") accept: String = "application/vnd.github+json",
        @RequestHeader("X-GitHub-Api-Version") githubApiVersion: String = "2022-11-28",
    ): List<Repo>
}

data class Repo(val id: String, val name: String)

As you can see, Spring 6 allows us to create an interface with a declaration of requests we want to send. We can define essential things for an HTTP request, such as URL, path, path variables, query params, and headers.

If you are interested in this topic, I encourage you to watch the video: 🚀 New in Spring Framework 6: HTTP Interfaces.

Now, we have to create a bean from that interface.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
@Configuration
internal class GithubClientConfig {

    @Bean(name = ["githubWebClient"])
    fun webClient(): WebClient = WebClient.builder()
      .exchangeStrategies(exchangeStrategies())
      .build()

    @Bean
    fun githubClient(@Qualifier("githubWebClient") webClient: WebClient): GithubClient = HttpServiceProxyFactory
            .builder(WebClientAdapter.forClient(webClient))
            .build()
            .createClient(GithubClient::class.java)

    private fun exchangeStrategies(): ExchangeStrategies {
        val objectMapper = ObjectMapper()
            .configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false)
            .registerModule(KotlinModule.Builder().build())
        return ExchangeStrategies
            .builder()
            .codecs { configurer: ClientCodecConfigurer ->
                configurer.defaultCodecs().jackson2JsonEncoder(Jackson2JsonEncoder(objectMapper))
                configurer.defaultCodecs().jackson2JsonDecoder(Jackson2JsonDecoder(objectMapper))
            }.build()
    }
}

For declarative HTTP clients, it is essential to use HttpServiceProxyFactory with WebClient to create a new instance of GithubClient.

ExchangeStrategies are to support Kotlin to JSON conversion.

We must remember that WebFlux project is quite big and has some requirements. For example, we will see log `Netty started on port 8080 after starting the shell.

I may limit the required dependency for declarative HTTP clients in the future.

Create a sample Shell Command

Now, we can create a command in our project:

1
2
3
4
5
6
7
8
9
@ShellComponent
internal class GithubSampleCommand(private val githubClient: GithubClient) {

    @ShellMethod
    fun userRepos(username: String): List<Repo> = githubClient.getUserRepos(
        username = username,
        token = "Bearer {your_github_token}"
    )
}

And that’s all.

We can run our project. Using help command in shell we can see the following:

1
2
3
4
...

Github Sample Command
       user-repos:

And after running the command user-repos --username jakubpradzynski, we will see the output:

1
[Repo(id=226322355, name=advent-of-code-2019), Repo(id=139764856, name=azariah), Repo(id=124221129, name=crispus), Repo(id=192063330, name=digits-predictor), Repo(id=377232244, name=fake-news-predictor), Repo(id=238491138, name=google_tasks_cli), Repo(id=242139319, name=google_translate_cli), Repo(id=515534896, name=jakubpradzynski.github.io), Repo(id=545125921, name=jvm-bloggers), Repo(id=549828075, name=mongodb-spring-kurs-dla-poczatkujacych), Repo(id=461206656, name=valuable-study-materials-it)]

If you want to know more about Spring Shell, I recommend reading documentation.

What’s next?

I’ve been testing Spring Shell recently. When I saw that it supports Spring 6 it seemed interesting to me.

I will probably use more of it and describe it in future posts. Stay tuned.

This post is licensed under CC BY 4.0 by the author.

Setting readPreference option in Spring Data MongoDB projects

Do you write scripts? Consider using Spring Shell for it!

Comments powered by Disqus.