Welcome to my GitHub
https://github.com/zq2599/blog_demos
Content: Classification and summary of all original articles and supporting source code, involving Java, Docker, Kubernetes, DevOPS, etc.;
Overview of this article
- As the ninth article in the "Spring Cloud Gateway Actual Combat" series, let's talk about how to use Spring Cloud Gateway to modify the original request and response content, as well as the problems encountered during the modification process
- The first is to modify the request body. As shown in the figure below, the browser is the initiator of the request. The real parameter is only <font color="blue">user-id</font>, which is stuffed into the field <font color="blue"> when passing through the gateway. user-name</font>, so the request received by the background service has the <font color="blue">user-name</font> field
- The second is to modify the response. As shown in the figure below, the original response of the service provider <font color="blue">provider-hello</font> only has the <font color="red">response-tag</font> field. When passing through the gateway Is stuffed into the <font color="blue">gateway-response-tag</font> field, the final response received by the browser is <font color="red">response-tag</font> and <font color ="blue">gateway-response-tag</font> two fields:
- In general, the specific things to do today are as follows:
- Preparation: Add a web interface to the code of the service provider to verify whether the Gateway operation is valid
- Introduce how to modify the request body and response body
- Develop a filter according to the routine to modify the body of the request
- Develop a filter according to the routine to modify the body of the response
- Thinking and trying: How to return errors from Gateway?
- In the actual combat process, let's clarify two issues by the way:
- How to add multiple filters to a route when the code configures a route?
- Can code configuration routing and yml configuration be mixed and matched? Is there a conflict between the two?
Source download
- The complete source code in this actual combat can be downloaded on GitHub, the address and link information are shown in the following table ( https://github.com/zq2599/blog_demos):
name | Link | Remark |
---|---|---|
Project homepage | https://github.com/zq2599/blog_demos | The project's homepage on GitHub |
git warehouse address (https) | https://github.com/zq2599/blog_demos.git | The warehouse address of the source code of the project, https protocol |
git warehouse address (ssh) | git@github.com:zq2599/blog_demos.git | The warehouse address of the source code of the project, ssh protocol |
- There are multiple folders in this git project. The source code of this article is under the <font color="blue">spring-cloud-tutorials</font> folder, as shown in the red box below:
- There are multiple sub-projects under the <font color="blue">spring-cloud-tutorials</font> folder. The code for this article is <font color="red">gateway-change-body</font>, as shown below The red box shows:
Ready to work
- In order to observe whether Gateway can modify the request and response body as expected, let's add an interface to the service provider <font color="blue">provider-hello</font>. The code is in Hello.java, as follows:
@PostMapping("/change")
public Map<String, Object> change(@RequestBody Map<String, Object> map) {
map.put("response-tag", dateStr());
return map;
}
- It can be seen that the new web interface is very simple: take the received request data as the return value, add a key-value pair in it, and then return it to the requester. With this interface, we can judge the Gateway pair by observing the return value. Whether the request and response operation takes effect
- Let’s try it, start nacos first (required by provider-hello)
- Then run the <font color="blue">provider-hello</font> application, and try to send a request to it with Postman, as shown in the figure below, which is in line with expectations:
- The preparatory work has been completed, let's start development
Modify the routine of the request body
- How to modify the request body with Spring Cloud Gateway? Take a look at the routines:
- Modifying the request body is achieved through a custom filter
- When configuring routing and its filters, there are two ways to configure routing, yml configuration file and code configuration. The demo given in the official document is configured by code, so today we also refer to the official practice to configure routing and filters through code
- When configuring routing in the code, call the <font color="blue">filters</font> method. The input parameter of this method is a lambda expression
- This lambda expression always calls the modifyRequestBody method, we only need to define the three input parameters of the modifyRequestBody method.
- The first input parameter of the modifyRequestBody method is the input type
- The second input parameter is the return type
- The third is the implementation of the RewriteFunction interface. This code needs to be written by yourself. The content is the specific logic of converting input data into return type data. Let's look at the official Demo, which is the above routine:
@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();
}
Modify the response body routine
- Using Spring Cloud Gateway to modify the response body is exactly the same as the previous request body
- Configure routing and filters through code
- When configuring routing in the code, call the <font color="blue">filters</font> method. The input parameter of this method is a lambda expression
- This lambda expression always calls the modifyResponseBody method, we only need to define the three input parameters of the modifyResponseBody method
- The first input parameter of the modifyRequestBody method is the input type
- The second input parameter is the return type
- The third is the implementation of the RewriteFunction interface. You need to write this code yourself. The content is the specific logic of converting input data into return type data. Let's look at the official Demo, which is actually the above routine:
@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();
}
- The routine is summed up. Next, let's write the code together?
Develop a filter to modify the request body according to the routine (filter)
- Don’t talk nonsense, create a new subproject <font color="red">gateway-change-body</font> under the parent project <font color="blue">spring-cloud-tutorials</font>, no pom.xml For any special features, please pay attention to rely on <font color="blue">spring-cloud-starter-gateway</font>
- The startup class 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);
}
}
- The configuration file is the same:
server:
#服务端口
port: 8081
spring:
application:
name: gateway-change-body
- Then there is the core logic: modify the code of the request body, which is the implementation class of RewriteFunction. The code is very simple. The original request body is parsed into a Map object, the user-id field is taken out, and the user-name field is generated and returned to the map. The apply method returns It's a Mono:
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;
}
/**
* 根据用户ID获取用户名称的方法,可以按实际情况来内部实现,例如查库或缓存,或者远程调用
* @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");
// 得到nanme后写入map
map.put("user-name", mockUserName(userId));
// 添加一个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);
// json操作出现异常时的处理
return Mono.error(new Exception("1. json process fail", ex));
}
}
}
- Then it is a step-by-step implementation of routing configuration based on code. The focus is on lambda expressions to execute the modifyRequestBody method and pass in 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();
}
}
- After the code is written, run the project <font color="blue">gateway-change-body</font>, initiate a request in postman, and get the response as shown in the figure below. It can be seen that the content added by Gateway has been successfully added in the red box:
- Now that the modification request body has been successful, let’s modify the body of the service provider’s response.
Modify the response body
- Next, develop the code to modify the response body
- Added the implementation class ResponseBodyRewrite.java of 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");
// 添加一个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));
}
}
}
- In the routing configuration code, in the lambda expression, the filters method calls modifyResponseBody internally, and the third input parameter 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();
}
}
- Remember our first question? Through the above code, you should have seen the answer: when configuring routing with code, the configuration method for multiple filters is to repeatedly call the built-in filter related API in the filters method. The red box in the following figure can be used:
- Run the service and verify the effect with Postman, as shown in the red box as shown in the figure below. Gateway successfully added a key&value to the response body:
Can code configuration routing and yml configuration be mixed?
- There are two questions in front, and then to answer the second one, let's add a routing configuration in application.yml:
server:
#服务端口
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
- Start the <font color="blue">gateway-change-body</font> service. At this time, there are two routing configurations, one in the code and one in the yml. Try this in yml first. The following picture is no problem:
- Try the code configuration routing again, as shown in the figure below, the conclusion is that <font color="red">code configuration routing and yml configuration can </font>
How to deal with exceptions
- There is another problem that must be faced: in the process of modifying the request or response body, if a problem is found and an error needs to be returned in advance (for example, the necessary field does not exist), how to write the code?
- The code for modifying the request body is concentrated in RequestBodyRewrite.java, and the content of the red box in the following figure is added:
- Try again, this time the request parameter does not include <font color="blue">user-id</font>, and the error message returned by Gateway is as shown below:
- Looking at the console, you can see the exception information thrown in the code:
- At this point, you should be smart enough to find the problem: we want to tell the client the specific error, but in fact what the client receives is the content processed by the Gateway framework
- Due to space limitations, the process from analysis to solution of the above problems will be left to the next article.
- At the end of this article, please allow Xin Chen to talk about why the gateway is required to modify the content of the request and response body. If you are not interested, please ignore it.
Why does the Gateway do this?
- After reading the two pictures at the beginning, you must be wise to find the problem: why destroy the original data, and once the system goes wrong, how to locate the service provider or the gateway?
- According to Xinchen’s previous experience, although the gateway destroys the original data, it only does some simple and fixed processing, generally adding data, the gateway does not understand the business, the most common operations are authentication, adding identities or tags, etc.
- The previous figure does not really feel the role of the gateway, but if there are multiple service providers behind the gateway, as shown in the figure below, at this time, operations such as authentication and obtaining account information are performed by the gateway, which is more than a separate implementation of each backend. Efficient, the back office can focus more on its own business:
- If you are experienced, you may dismiss my sophistry: the gateway unified authentication, obtains identity, generally puts the identity information in the request header, and does not modify the content of the request and response. The first one in Xin Chen The heap explanation still didn't make it clear why the content of the request and response should be modified at the gateway location!
- Well, facing the smart you, I have a showdown: this article is just a technical demonstration of how Spring Cloud Gateway modifies the request and response content, please do not couple this technology with the actual back-end business;
You are not lonely, Xinchen is with you all the way
- Java series
- Spring series
- Docker series
- kubernetes series
- database + middleware series
- DevOps series
Welcome to pay attention to the public account: programmer Xin Chen
Search "Programmer Xin Chen" on WeChat, I am Xin Chen, and I look forward to traveling the Java world with you...
https://github.com/zq2599/blog_demos
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。