2

I plan to migrate a project from Zuul to Spring Cloud Gateway. I have a "checksum code" and I don't know how to migrate it.

In the zuul code i get the url parameter and json body, I then do some checks.

HttpServletRequest request = requestContext.getRequest();
Map<String, String[]> parameterMap = getURLParamter(request);
String json = getBody(request);

if(securityCheck(parameterMap, json) == true) {
    requestContext.addZuulRequestHeader("check-success-then-next-filter", "1");
} else {
    requestContext.setResponseBody("{ msg:: check error }");
}

I have limited experience with Spring gateway please help me find what the equivalent code is in Spring Gateway,

3 Answers 3

5

Spring Cloud gateway has filters to modify request body and response body.

ModifyResponseBody
ModifyRequestBody

As mentioned in the specs, for using these filters, we need to use DSL approach rather than YAML file for configuring routes. So essentially you will have a RouteBuilder like below -

@Bean
public RouteLocator myRoutes(RouteLocatorBuilder builder) {
    RouteLocatorBuilder.Builder routeLocator = builder.routes().route(
    p -> {
      p.method("POST").path("/<path>").filters(f -> {
         f.modifyRequestBody(String.class, 
                             String.class,
                             (exchange, reqMessage) -> {
                try {
                    log.info(">>> INCOMING REQUEST <<< - {}", reqMessage);
                    //Get query params
                    exchange.getRequest().getQueryParams();
                    // In case of any validation errors, throw an exception so that 
                    // it can be handled by a global exception handler
                    return Mono.just(reqMessage);
                } catch (Exception e) {
                    log.error("Exception while modifying request body", e);
                    throw new RuntimeException(e.getMessage());
                }
            });
        })
    });
}

A global exception handler could then send a standard response back -

public class GlobalExceptionHandler extends AbstractErrorWebExceptionHandler {
    @Override
    protected RouterFunction<ServerResponse> getRoutingFunction (ErrorAttributes errorAttributes) {
       return RouterFunctions.route(RequestPredicates.all(),this::renderErrorResponse);
    }
    
    private Mono<ServerResponse> renderErrorResponse (ServletRequest request) {
        Map<String,Object> errorPropertiesMap = getErrorAttributes (request,ErrorAttributeOptions.defaults());
        String customErrMsg = errorPropertiesMap.get("message") != null ? errorPropertiesMap.get("message").toString() : null;
    
        if(customErrMsg != null) {
            return ServerResponse.status(HttpStatus.BAD_REQUEST)
                                 .contentType(MediaType.APPLICATION_JSON)
                                 .body(BodyInserters.fromValue(errorPropertiesMap.get("message")));
        } else {
            return ServerResponse.status(HttpStatus.BAD_REQUEST)
                                 .contentType(MediaType.APPLICATION_JSON)
                                 .body(BodyInserters.fromValue(errorPropertiesMap));
        }
    }
}
2
  • Thank you, Cloud you provide the complete code ? I looked over this document and found it very abstract. I try to used cacheRequestBody to read data . It looks like a @Bean has been injected Spring .but how can I handle the return result ?
    – zy_sun
    Commented Jan 14, 2022 at 9:56
  • This solution solves the part of accessing request body, but not the part of adding a headers depending of that content. Any tip for that? Maybe chaining two filters: - ModifyRequestBodyGatewayFilterFactory - AddRequestHeaderGatewayFilterFactory
    – Basa
    Commented Sep 15, 2022 at 8:17
2

GlobalFilter can be used to modify the request.

public class RequestTransaformationFilter implements GlobalFilter, Ordered {

  @Autowired 
  private ModifyRequestBodyGatewayFilterFactory modifyRequestBodyFilter;
  @Autowired 
  private RequestBodyRewrite requestBodyRewrite;

  @Override
  public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
    return modifyRequestBodyFilter
        .apply(
            new ModifyRequestBodyGatewayFilterFactory.Config()
                .setRewriteFunction(String.class, String.class, requestBodyRewrite))
        .filter(exchange, chain);
  }

 
}

RequestRewrite bean can be configured to modify request:

public class RequestBodyRewrite implements RewriteFunction<String, String> {

  @Override
  public Publisher<String> apply(ServerWebExchange exchange, String body) {
    Gson gson = new Gson();
   
      Map<String, Object> map = gson.fromJson(body, Map.class);
      map.put("NewField", "value");
      return Mono.just(gson.toJson(map, Map.class));
    
  }
}
2
0

With the latest additions to Spring-Cloud-Gateway v4 you can utilize a CacheRequestBody to much more simply access request body without blocking operations.

In your application.yml

spring:
  cloud:
    gateway:
      routes:
        - id: my-id
          uri: https://my-host.com
          predicates:
            - Path=/path/**
          filters:
            - name: CacheRequestBody
              args:
                bodyClass: org.springframework.util.MultiValueMap

In your filter or apply method, you can now access your body using:

MultiValueMap<String, String> formData = exchange.getAttribute(ServerWebExchangeUtils.CACHED_REQUEST_BODY_ATTR);

For my case, I am expecting to recieve x-www-form-urlencoded so my bodyClass and form are MultiValueMap. It should be possible to do any format you expect.

Not the answer you're looking for? Browse other questions tagged or ask your own question.