Welcome to my GitHub

Github.com/zq2599/blog…

Content: all original article classification summary and supporting source code, involving Java, Docker, Kubernetes, DevOPS, etc.;

This paper gives an overview of

  • This is part 9 of the Spring Cloud Gateway In Action series. Let’s talk about how to use Spring Cloud Gateway to modify the original request and response content, and the problems encountered during the modification process

  • First, modify the request body, as shown in the following figure. The browser is the initiator of the request, and the real parameter is only user-ID. When passing through the gateway, the field user-name is inserted, so that the background service receives the request with the field user-name

  • The second is to modify the response, as shown in the figure below. The original response of the service provider Provider-Hello only has the Response-tag field, which is inserted into the gateway-Response-Tag field when passing through the gateway. The response-tag and gateway-response-tag fields are the two fields received by the browser:

  • In general, the specific things to do today are as follows:
  1. Preparation: Add a Web interface to the service provider’s code to verify that the Gateway’s actions are valid
  2. This section describes how to modify the request body and response body
  3. Develop a filter that modifies the body of the request
  4. Develop a filter that modifies the body of the response
  5. Think and try: How do I return an error from the Gateway?
  • In the actual combat process, let’s make clear two questions incidentally:
  1. How to add multiple filters to a route when configuring the route code?
  2. Can code configuration routing and YML configuration be mixed and matched? Is there any conflict between the two?

Download the source code

  • The full source code for this article can be downloaded at GitHub with the following address and link information (github.com/zq2599/blog…
The name of the link note
Project home page Github.com/zq2599/blog… The project’s home page on GitHub
Git repository address (HTTPS) Github.com/zq2599/blog… The project source warehouse address, HTTPS protocol
Git repository address (SSH) [email protected]:zq2599/blog_demos.git The project source warehouse address, SSH protocol
  • The git project has multiple folders. The source code for this project is in the spring-cloud-tutorials folder, as shown in the red box below:

  • There are many sub-projects under the spring-cloud-tutorials folder. This code is gateway-change-body, as shown in the red box below:

The preparatory work

  • To see if the Gateway can modify the body of the request and response as expected, we add an interface to the service provider provider-Hello in hello.java as follows:
    @PostMapping("/change")
    public Map<String, Object> change(@RequestBody Map<String, Object> map) {
        map.put("response-tag", dateStr());
        return map;
    }
Copy the code
  • The new Web interface is simple: it takes the received request data as the return value, adds a key-value pair, and returns it to the requester. With this interface, we can observe the return value to determine whether the Gateway’s actions on the request and response are effective

  • To try it out, start nacos (provider-Hello required)

  • Run provider-Hello again and send a request to it using Postman.

  • Preparation is complete, let’s start development

Modify the request body routine

  • How do I use the Spring Cloud Gateway to modify the body of a request? Here’s how it works:
  1. Modifying the request body is done through a custom filter
  2. When configuring routes and filters, there are two ways to configure routes: YML configuration file and code configuration. The demo provided by the official document is code configuration, so today we also refer to the official practice and configure routes and filters by code
  3. When the code configures the route, it calls the filters method, whose input parameter is a lambda expression
  4. This lambda expression permanently calls the modifyRequestBody method. All we need to do is define the three incoming arguments to the modifyRequestBody method
  5. The first entry to the modifyRequestBody method is the input type
  6. The second entry is the return type
  7. The third is the implementation of the RewriteFunction interface. This code needs to be written by you. The content is to convert input data into return type data.
@Bean
public RouteLocator routes(RouteLocatorBuilder builder) {
    return builder.routes()
        .route("rewrite_request_obj", r -> r.host("*.rewriterequestobj.org")
            .filters(f -> f.prefixPath("/httpbin")
                .modifyRequestBody(String.class, Hello.class, MediaType.APPLICATION_JSON_VALUE,
                    (exchange, s) -> return Mono.just(new Hello(s.toUpperCase())))).uri(uri))
        .build();
}
Copy the code

Modify the response body routine

  • Using the Spring Cloud Gateway to modify the response body follows the same pattern as the previous request body
  1. Configure routing and filters through code
  2. When the code configures the route, it calls the filters method, whose input parameter is a lambda expression
  3. This lambda expression permanently calls the modifyResponseBody method. All we need to do is define the three entries to the modifyResponseBody method
  4. The first entry to the modifyRequestBody method is the input type
  5. The second entry is the return type
  6. The third is the implementation of the RewriteFunction interface. This code needs to be written by you. The content is to convert input data into return type data.
@Bean
public RouteLocator routes(RouteLocatorBuilder builder) {
    return builder.routes()
        .route("rewrite_response_upper", r -> r.host("*.rewriteresponseupper.org")
            .filters(f -> f.prefixPath("/httpbin")
                .modifyResponseBody(String.class, String.class,
                    (exchange, s) -> Mono.just(s.toUpperCase()))).uri(uri))
        .build();
}
Copy the code
  • Routines summed up, next, let’s do the code?

Develop a filter that modifies the body of the request.

  • Without further discussion, its parent project, spring-cloud-tutorials, would create a child project, gateway-change-body. Pom.xml is nothing special, so rely on spring-cloud-starter-gateway

  • The startup category is nothing new:

package com.bolingcavalry.changebody;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;

@SpringBootApplication
public class ChangeBodyApplication {
    public static void main(String[] args) { SpringApplication.run(ChangeBodyApplication.class,args); }}Copy the code
  • The configuration files are the same:
server:
  # service port
  port: 8081
spring:
  application:
    name: gateway-change-body
Copy the code
  • Then the core logic: change the code of the request body, which is the implementation class of RewriteFunction. The code is very simple. Parse the original request body into a Map object, extract the user-ID field, generate the user-name field and put it back into the Map.
package com.bolingcavalry.changebody.function;

import com.fasterxml.jackson.databind.ObjectMapper;
import lombok.extern.slf4j.Slf4j;
import org.reactivestreams.Publisher;
import org.springframework.cloud.gateway.filter.factory.rewrite.RewriteFunction;
import org.springframework.web.server.ServerWebExchange;
import reactor.core.publisher.Mono;
import java.util.Map;


@Slf4j
public class RequestBodyRewrite implements RewriteFunction<String.String> {

    private ObjectMapper objectMapper;

    public RequestBodyRewrite(ObjectMapper objectMapper) {
        this.objectMapper = objectMapper;
    }

    /** * The method to get the user name based on the user ID can be implemented internally, for example by searching the library or cache, or by calling * remotely@param userId
     * @return* /
    private  String mockUserName(int userId) {
        return "user-" + userId;
    }

    @Override
    public Publisher<String> apply(ServerWebExchange exchange, String body) {
        try {
            Map<String, Object> map = objectMapper.readValue(body, Map.class);

            / / id
            int userId = (Integer)map.get("user-id");

            // Get nanme and write map
            map.put("user-name", mockUserName(userId));

            // Add a key/value
            map.put("gateway-request-tag", userId + "-" + System.currentTimeMillis());

            return Mono.just(objectMapper.writeValueAsString(map));
        } catch (Exception ex) {
            log.error("1. json process fail", ex);
            // Handle exceptions in json operations
            return Mono.error(new Exception("1. json process fail", ex)); }}}Copy the code
  • This is followed by a step by step code-based route configuration, with the lambda expression executing the modifyRequestBody method and passing RequestBodyRewrite as a parameter:
package com.bolingcavalry.changebody.config;

import com.bolingcavalry.changebody.function.RequestBodyRewrite;
import com.bolingcavalry.changebody.function.ResponseBodyRewrite;
import com.fasterxml.jackson.databind.ObjectMapper;
import org.springframework.cloud.gateway.route.RouteLocator;
import org.springframework.cloud.gateway.route.builder.RouteLocatorBuilder;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.http.MediaType;
import reactor.core.publisher.Mono;

@Configuration
public class FilterConfig {
    @Bean
    public RouteLocator routes(RouteLocatorBuilder builder, ObjectMapper objectMapper) {
        return builder
                .routes()
                .route("path_route_change",
                        r -> r.path("/hello/change")
                                .filters(f -> f
                                        .modifyRequestBody(String.class,String.class,new RequestBodyRewrite(objectMapper))
                                        )
                        .uri("http://127.0.0.1:8082")) .build(); }}Copy the code
  • After the code is written, run the project gateway-change-body, initiate the request in postman, and get the response as shown in the following figure. The red box shows that the content added by gateway is successful:

  • Now that the request body has been successfully modified, it is time to modify the body of the service provider response

Modify the response body

  • Next, develop the code that modifies the response body

  • Added responseBodyrewrite.java to implement the RewriteFunction interface

package com.bolingcavalry.changebody.function;

import com.fasterxml.jackson.databind.ObjectMapper;
import lombok.extern.slf4j.Slf4j;
import org.reactivestreams.Publisher;
import org.springframework.cloud.gateway.filter.factory.rewrite.RewriteFunction;
import org.springframework.web.server.ServerWebExchange;
import reactor.core.publisher.Mono;
import java.util.Map;

@Slf4j
public class ResponseBodyRewrite implements RewriteFunction<String.String> {

    private ObjectMapper objectMapper;

    public ResponseBodyRewrite(ObjectMapper objectMapper) {
        this.objectMapper = objectMapper;
    }

    @Override
    public Publisher<String> apply(ServerWebExchange exchange, String body) {
        try {
            Map<String, Object> map = objectMapper.readValue(body, Map.class);

            / / id
            int userId = (Integer)map.get("user-id");

            // Add a key/value
            map.put("gateway-response-tag", userId + "-" + System.currentTimeMillis());

            return Mono.just(objectMapper.writeValueAsString(map));
        } catch (Exception ex) {
            log.error("2. json process fail", ex);
            return Mono.error(new Exception("2. json process fail", ex)); }}}Copy the code
  • In the route configuration code, in the lambda expression, the filters method is called modifyResponseBody, and the third entry is ResponseBodyRewrite:
package com.bolingcavalry.changebody.config;

import com.bolingcavalry.changebody.function.RequestBodyRewrite;
import com.bolingcavalry.changebody.function.ResponseBodyRewrite;
import com.fasterxml.jackson.databind.ObjectMapper;
import org.springframework.cloud.gateway.route.RouteLocator;
import org.springframework.cloud.gateway.route.builder.RouteLocatorBuilder;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.http.MediaType;
import reactor.core.publisher.Mono;

@Configuration
public class FilterConfig {

    @Bean
    public RouteLocator routes(RouteLocatorBuilder builder, ObjectMapper objectMapper) {
        return builder
                .routes()
                .route("path_route_change",
                        r -> r.path("/hello/change")
                                .filters(f -> f
                                        .modifyRequestBody(String.class,String.class,new RequestBodyRewrite(objectMapper))
                                        .modifyResponseBody(String.class, String.class, new ResponseBodyRewrite(objectMapper))
                                        )
                        .uri("http://127.0.0.1:8082")) .build(); }}Copy the code
  • Remember our first question? From the above code, you should already see the answer: when configuring routes in code, you configure multiple filters by repeatedly calling the built-in filter API in the filters method, as shown in the red box below:

  • Run the service and verify the effect with Postman. In the red box below, the Gateway successfully adds a key&value to the response body:

Can code configuration routing and YML configuration be mixed?

  • Add a routing configuration to application.yml:
server:
  # service port
  port: 8081
spring:
  application:
    name: gateway-change-body
  cloud:
    gateway:
      routes:
        - id: path_route_str
          uri: http://127.0.0.1:8082
          predicates:
            - Path=/hello/str
Copy the code
  • Start the gateway-change-body service. Now you have two route configurations, one in code and one in YML. Try this one in YML.

  • Try code configured routing again, as shown below, and the conclusion is that code configured routing and YML configuration can be mixed and matched

How to Handle exceptions

  • There is another problem that you must face: what should the code do when you modify the body of a request or response and find that you need to return an error in advance (for example, a required field does not exist)?

  • Let’s change the request body code to focus on requestBodyrewrite.java and add the following red box:

  • This time, the request parameter does not contain user-id, and the Gateway returns an error message as shown in the following figure:

  • If you look at the console, you can see the exception message thrown in the code:

  • At this point, you are smart enough to see the problem: We want to tell the client the specific error, but the client actually receives the content processed by the Gateway framework

  • Due to space constraints, I will leave the process from analysis to solution of the above problems to the next article

  • At the end of this article, please allow Hsin Chen to say a few words about why the gateway needs to modify the content of the request and response body. If you are not interested, please ignore it

Why does Gateway do this?

  • Looking at the first two diagrams, you must be smart enough to see the problem: why destroy the raw data, and how to locate the service provider or gateway if the system fails?

  • According to Hsin Chen’s previous experience, although the gateway would destroy the original data, it only did some simple and fixed processing, generally adding data. The gateway did not understand the business, and the most common operations were authentication, adding identities or labels

  • It is true that the function of the gateway is not felt in the previous figure, but if there are multiple service providers behind the gateway, as shown in the following figure, operations such as authentication and account information acquisition are completed by the gateway in a unified manner, which is more efficient than the implementation of each background separately, and the background can focus more on its own business:

  • If you are experienced, you may not appreciate my argument that the gateway authenticates and obtains identity, usually puts the identity information in the header of the request, and does not modify the content of the request and response.

  • Well, I’m going to put my cards on the table for you: This is just a technical demonstration of how the Spring Cloud Gateway modifies the request and response content, so don’t couple this technology to the actual back-end business;

You are not alone, Xinchen original accompany all the way

  1. Java series
  2. Spring series
  3. The Docker series
  4. Kubernetes series
  5. Database + middleware series
  6. The conversation series

Welcome to pay attention to the public number: programmer Xin Chen

Wechat search “programmer Xin Chen”, I am Xin Chen, looking forward to enjoying the Java world with you…

Github.com/zq2599/blog…