kotlinmicronautmicronaut-rest

Micronaut-Core: How to create dynamic endpoints


Simple question. Is it possible to create endpoints without @Endpoint? I want to create rather dynamic endpoints by a file and depending on the content to its context.

Thanks!


Update about my idea. I would to create something like a plugin system, to make my application more extensible for maintenance and future features.

It is worth to be mentioned I am using Micronaut with Kotlin. Right now I've got fixed defined Endpoints, which matches my command scripts.

My description files will be under /src/main/resources

enter image description here

I've got following example description file how it might look like.

ENDPOINT: GET /myapi/customendpoint/version
COMMAND: """
#!/usr/bin/env bash

# This will be executed via SSH and streamed to stdout for further handling
echo "1.0.0"
"""
# This is a template JSON which will generate a JSON as production on the endpoint
OUTPUT: """
{
  "version": "Server version: $RESULT"
}
"""

How I would like to make it work with the application.

import io.micronaut.docs.context.events.SampleEvent
import io.micronaut.context.event.StartupEvent
import io.micronaut.context.event.ShutdownEvent
import io.micronaut.runtime.event.annotation.EventListener

@Singleton
class SampleEventListener {
    /*var invocationCounter = 0

    @EventListener
    internal fun onSampleEvent(event: SampleEvent) {
        invocationCounter++
    }*/

    @EventListener
    internal fun onStartupEvent(event: StartupEvent) {
        // 1. I read all my description files
        // 2. Parse them (for what I created a parser)
        // 3. Now the tricky part, how to add those information to Micronaut Runtime
        
        val do = MyDescription() // After I parsed
        // Would be awesome if it is that simple! :)
        Micronaut.addEndpoint(
          do.getEndpoint(), do.getHttpOption(),
          MyCustomRequestHandler(do.getCommand()) // Maybe there is a base class for inheritance?
        )
    }

    @EventListener
    internal fun onShutdownEvent(event: ShutdownEvent) {
        // shutdown logic here
    }
}

Solution

  • It was actually pretty easy. The solution for me was to implement a HttpServerFilter.

    @Filter("/api/sws/custom/**")
    class SwsRouteFilter(
        private val swsService: SwsService
    ): HttpServerFilter {
    
        override fun doFilter(request: HttpRequest<*>?, chain: ServerFilterChain?): Publisher<MutableHttpResponse<*>> {
            return Flux.from(Mono.fromCallable {
                runBlocking {
                    swsService.execute(request)
                }
            }.subscribeOn(Schedulers.boundedElastic()).flux())
        }
    }
    

    And the service can process with the HttpRequest object:

    suspend fun execute(request: HttpRequest<*>?): MutableHttpResponse<Feedback> {
            val path = request!!.path.split("/api/sws/custom")[1]
            val httpMethod = request.method
            val parameters: Map<String, List<String>> = request.parameters.asMap()
            // TODO: Handle request body
    
            // and do your stuff ...
    }