Search code examples
spring-cloud-gateway

Spring Cloud Gateway - how to pass dynamic YAML filter arguments


I've got a Spring Cloud Gateway app configured with YML. I want to create a custom filter that basically combines the responses of two endpoints into one brand new object which is then returned to the client. However, I will have many cases where I want to combine such API calls, and in order to avoid creating a brand new route in my YML config for each of them, I want to somehow dynamically pass the filter arguments in such a way that they are set by the initial request made to this route. Here's how the configuration looks:

        - id: claims-composite-call-test-1
          uri: http://1.2.3.4:1234
          predicates:
            - Path=/claims/**
          filters:
            - name: CompositeClaimAndRequestFilter
              args:
                uris: http://1.2.3.4:1234/claims/v1/2000, http://1.2.3.4:1234/claims/v1/2000/requests/1

Basically what the filter does is when I hit the above route (regardless of what path I use), it makes two separate GET requests towards the two URIs specified in the filter argument uris. It then combines the two responses of those endpoints into a brand new object and returns it to the client.

However, In this case I have hardcoded the IDs of the two objects towards which I'm making the request - it's the 2000 part of both arguments. So now if I want to make the same composite call but towards object with ID 3000, I need to create a brand new route in my configuration, and that's a major inconvenience for obvious reasons.

Is there a way to define the configuration such that it dynamically takes the ID of the object via the initial request made and plugs it into the filter arguments before the filter kicks in?

For example, I make a request towards http://1.2.3.4:1234/claims/composite/5000, and then the configuration would look something like:

        - id: claims-composite-call-test-1
          uri: http://1.2.3.4:1234
          predicates:
            - Path='/claims/composite/' + objectId
          filters:
            - name: CompositeClaimAndRequestFilter
              args:
                uris: 'http://1.2.3.4:1234/claims/v1/' + objectId, 'http://1.2.3.4:1234/claims/v1/' + objectId + '/requests/1'

So the resulting args would be http://1.2.3.4:1234/claims/v1/5000 and http://1.2.3.4:1234/claims/v1/5000/requests/1 respectively.

Is this even possible when the gateway is configured via YAML? I know I can pass the ID as a header or a cookie, but I prefer not having to modify the default initial request. Thanks in advance!


Solution

  • I found a way to do this. Basically in the config, I pass what is known as template variable to the Path predicate, as follows:

            - id: claims-composite-call-test-1
              uri: http://1.2.3.4:1234
              predicates:
                - Path=/claims/v1/{claimId}
              filters:
                - name: CompositeClaimAndRequestFilter
                  args:
                    secondCallUri: http://1.2.3.4:1234/claims/v1/$$$claimId$$$/requests/1
    

    By passing it in curly braces, I have access to it via the server exchange, and can extract it as a string like so:

    exchange    
        .getAttributes()
        .get(URI_TEMPLATE_VARIABLES_ATTRIBUTE)
        .toString()
    

    This was explained in the official Spring Cloud Gateway, in the host route predicate factory section.

    Now in the filter body I can get the secondCallUri argument and replace the $$$claimId$$$ part of it with the template variable I passed in with the request.

    The question I have is if there's a way to do that same thing, but instead of passing a variable in the Path predicate, to do it in the arguments of a filter. So for example can I replace the hardcoded 1 in secondCallUri with a variable that can be somehow set dynamically? I know I can do that via a custom header, but I wish there was a way to do it without having to add a header.