Search code examples
routesspring-cloud-function

Vanilla Spring Cloud Function with Routing


Being a new bee in Spring cloud, I am developing multi-function spring cloud function application which is working fine with spring-cloud-function-starter-web (3.0.9.RELEASE) dependency. Note: I have different function in different package and with below configuration it is working fine.

cloud:
    function:
        scan:
            packages: zoo.app1.vanilla

For example, with [POST] localhost:8080/func1 it is invoking Func1 implements Function<I, O>. Now I want to introduce routing. For that I have only changed the below in application.yml

cloud:
    function:
        definition: functionRouter
        routing-expression: headers['function.name']
        scan:
            packages: zoo.app1.vanilla

Now when I invoke using

curl --location --request POST 'http://localhost:8080/functionRouter' \
--header 'function.name: func1' \
--header 'Content-Type: text/plain' \
--data-raw '1'

The exception is

org.springframework.expression.spel.SpelEvaluationException: EL1008E: Property or field 'headers' cannot be found on object of type 'reactor.core.publisher.FluxMapFuseable' - maybe not public or not valid?
at org.springframework.expression.spel.ast.PropertyOrFieldReference.readProperty(PropertyOrFieldReference.java:217) ~[spring-expression-5.2.8.RELEASE.jar:5.2.8.RELEASE]
at org.springframework.expression.spel.ast.PropertyOrFieldReference.getValueInternal(PropertyOrFieldReference.java:104) ~[spring-expression-5.2.8.RELEASE.jar:5.2.8.RELEASE]
at org.springframework.expression.spel.ast.PropertyOrFieldReference.getValueInternal(PropertyOrFieldReference.java:91) ~[spring-expression-5.2.8.RELEASE.jar:5.2.8.RELEASE]
at org.springframework.expression.spel.ast.CompoundExpression.getValueRef(CompoundExpression.java:55) ~[spring-expression-5.2.8.RELEASE.jar:5.2.8.RELEASE]
at org.springframework.expression.spel.ast.CompoundExpression.getValueInternal(CompoundExpression.java:91) ~[spring-expression-5.2.8.RELEASE.jar:5.2.8.RELEASE]
at org.springframework.expression.spel.ast.SpelNodeImpl.getTypedValue(SpelNodeImpl.java:117) ~[spring-expression-5.2.8.RELEASE.jar:5.2.8.RELEASE]
at org.springframework.expression.spel.standard.SpelExpression.getValue(SpelExpression.java:375) ~[spring-expression-5.2.8.RELEASE.jar:5.2.8.RELEASE]
at org.springframework.cloud.function.context.config.RoutingFunction.functionFromExpression(RoutingFunction.java:173)

Now when I look into the code I found, RequestProcessor with below function

private Object getTargetIfRouting(FunctionWrapper wrapper, Object function) {
    if (function instanceof RoutingFunction) {
        String name = wrapper.headers.get("function.name").iterator().next();
        function = this.functionCatalog.lookup(name);
    }
    return function;
}

It seems like, by default it expects "function.name" in message header in order to route, hence I thought to comment out routing-expression line in application.yml, it is getting into infinite loop resulting in stackoverflow error

2020-08-13 11:47:16.454 DEBUG 85560 --- [nio-8080-exec-1] o.s.c.f.c.c.SimpleFunctionRegistry       : Applying function: functionRouter
2020-08-13 11:47:16.454  INFO 85560 --- [nio-8080-exec-1] o.s.c.f.c.c.SimpleFunctionRegistry       : Looking up function 'functionRouter' with acceptedOutputTypes: []
2020-08-13 11:47:16.454  INFO 85560 --- [nio-8080-exec-1] o.s.c.f.context.config.RoutingFunction   : Resolved function from provided [definition] property functionRouter
2020-08-13 11:47:16.454 DEBUG 85560 --- [nio-8080-exec-1] o.s.c.f.c.c.SimpleFunctionRegistry       : Applying function: functionRouter
2020-08-13 11:47:16.454  INFO 85560 --- [nio-8080-exec-1] o.s.c.f.c.c.SimpleFunctionRegistry       : Looking up function 'functionRouter' with acceptedOutputTypes: []
2020-08-13 11:47:16.454  INFO 85560 --- [nio-8080-exec-1] o.s.c.f.context.config.RoutingFunction   : Resolved function from provided [definition] property functionRouter
2020-08-13 11:47:16.454 DEBUG 85560 --- [nio-8080-exec-1] o.s.c.f.c.c.SimpleFunctionRegistry       : Applying function: functionRouter
2020-08-13 11:47:16.454  INFO 85560 --- [nio-8080-exec-1] o.s.c.f.c.c.SimpleFunctionRegistry       : Looking up function 'functionRouter' with acceptedOutputTypes: []
2020-08-13 11:47:16.454  INFO 85560 --- [nio-8080-exec-1] o.s.c.f.context.config.RoutingFunction   : Resolved function from provided [definition] property functionRouter
2020-08-13 11:47:16.454 DEBUG 85560 --- [nio-8080-exec-1] o.s.c.f.c.c.SimpleFunctionRegistry       : Applying function: functionRouter
2020-08-13 11:47:16.468 ERROR 85560 --- [nio-8080-exec-1] o.a.c.c.C.[.[.[/].[dispatcherServlet]    : Servlet.service() for servlet [dispatcherServlet] in context with path [] threw exception [Handler dispatch failed; nested exception is java.lang.StackOverflowError] with root cause

Am I doing anything wrong? Even if this works, will it recognize the different Functions that I have as per spring.cloud.function.scan.packages property? Please help. Also I have two more questions,

  1. in some blog/posts/documents it seems I can pass the spring.cloud.function.definition as http header. If this holds true for this 3.0.9.RELEASE, then do I need to mention same property in application.yml?

  2. can I use spring.cloud.function.definition=func1;func2 without using routingFunction and expect the routing behaviour working properly? Or this is intended for something other feature?

I have never tested/played around different configuration option because of the above issue. Please forgive my little knowledge on spring cloud or if I have asked any childish question.

Edit

After debugging and with help of Spring documentation I found the right configuration

cloud:
    function:
        scan:
            packages: zoo.app1.vanilla
    stream:
        function:
            routing:
                enabled: true

With this configuration it is able to route the message to my function, but only for the first time. Now that is something that made me completely confused. Once I start the application and hit from postman, it is able to identify the actual function and converts the input into GenericMessage as expected (though failing to parse request body later). But when I hit second time (and onwards) it is not able to even parse my input to GenericMessage and gives me different error. And this is repeatable behavior.

For reference Please find the log of two consecutive requests (along with postman curl)

First request : proper routing Second request : routing failure


Solution

  • The issue got resolved in 3.1.0.RELEASE, Thanks Oleg Zhurakousky & Spring team. Ref: Issue Tracker