2

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
    }
}
  • Where does the file come from and how are you intending to specify the endpoints paths and implementation? Could you be more explicit and describe your exact case? – tmarwen Nov 05 '21 at 15:17
  • Hi @tmarwen, it will be just a text file from the resources, where I will describe the endpoint. On the startup I would read all those description files and then that's my question, I would call something from micronaut to add those endpoints. I didn't find a base class where I can add those information at runtime. Like what operation, endpoint and what it should call. – Thraax.Session Nov 08 '21 at 12:21
  • Would you please update your post with an draft example of how your project structure would be (properties and sources) and how you intend to achieve the binding (property file -> method handler).$ – tmarwen Nov 08 '21 at 13:25
  • yes, I've updated my post to help clarify my idea. @tmarwen I hope it helps you to understand me. :) – Thraax.Session Nov 08 '21 at 20:16

2 Answers2

1

You can create a custom RouteBuilder that will register your custom endpoints at runtime:

@Singleton
class CustomRouteBuilder extends DefaultRouteBuilder {

    @PostConstruct
    fun initRoutes() {
        val do = MyDescription();
        val method = do.getMethod();
        val routeUri = do.getEndpoint();
        val routeHandle = MethodExecutionHandle<Object, Object>() {
            // implement the 'MethodExecutionHandle' in a suitable manner to invoke the 'do.getCommand()'
        };
        buildRoute(HttpMethod.parse(method), routeUri, routeHandle);
    }
}

Note that while this would still feasible, it would be better to consider another extension path as the solution defeats the whole Micronaut philosophy of being an AOT compilation framework.

tmarwen
  • 15,750
  • 5
  • 43
  • 62
  • This looks great! I am not sure my plan will work with AOP. But maybe there is a solution for it. Do you have an approach for me how AOP works with RequestHandler? Like a minimal version? – Thraax.Session Nov 09 '21 at 18:57
  • With a custom `RequestHandler` you mean? This will tightly depend on the context and your should not be able to go that far since Micronaut will force you once again to go back to compile time defined dependencies. – tmarwen Nov 09 '21 at 19:21
  • With custom RequestHandler, I mean a way to handle my individual requests with AOP. Do you have an idea? I want to validate your first answer (which already looks neat but not Micronaut like)... maybe the 2nd way is more feasible as you said. :) – Thraax.Session Nov 09 '21 at 20:03
  • If an annotation-driven approach is acceptable as a solution design then yes. You can have a custom annotation that can drive executable points (methods). – tmarwen Nov 09 '21 at 20:26
  • Do you have a small example for me please? – Thraax.Session Nov 09 '21 at 20:27
0

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 ...
}