In the previous sections, I introduced the problems and solutions that arise when a system is split into microservices: how services are discovered and managed (Nacos Registry practice), how services communicate with services (Feign practice)
Today we are going to talk about another question: how do clients access?
In a single architecture, our system has only one entry point, which is very simple for the front end personnel to call.
But when we split it into a microservice system, each service has its own IP and port number, and we can’t say to the front end: Hey, you use this address when you call this interface.
Front end:
If that doesn’t work, can we use what we know to come up with a solution?
Not really a solution that works
In fact, we can easily imagine that our services have the ability to discover and communicate with each other. So, can we build a unified gateway service, where the front end only requests the service and the service invokes the Feign interface of the real service?
Here’s an example:
- Get goods: localhost:8080/get/goods
- The order placing interface for the order service: localhost:8081/order
There is now a gateway service with two interfaces: localhost:5555/ GET /goods and localhost:5555/order
When the front-end invokes the get goods interface, it visits: localhost:5555/ GET /goods, and then the gateway service invokes the Feign interface of the goods service
To place an order: visit: localhost:5555/order, and then the gateway service invokes the Feign interface of the order service
To summarize:
Does this solution solve the problem of service entry unification: yes
Does it work: Yes, but not completely
The problem is that for every interface the service writes, it needs to give a Feign interface to our gateway service call.
Real solutions
Spring Cloud provides a solution: the Spring Cloud Gateway
The Spring Cloud Gateway provides an API Gateway built on top of the Spring ecosystem that provides a simple and efficient way to route through the API and provide functions such as security and monitoring in a filter-based manner.
Spring Cloud Gateway is implemented by Spring Boot 2.x, Spring WebFlux and Reactor, and requires the Netty operating environment provided by Spring Boot and Spring WebFlux. It does not work in a traditional Servlet container, nor when built as a WAR.
IO /spring-clou…
concept
Route: The basic building block of a gateway, defined by an ID, a destination URI, a collection of assertions, and a collection of filters. If the collection assertion is true, the route is matched.
Predicate: Java 8 Predicate functions. The parameter type is Spring Framework ServerWebExchange. It allows developers to match anything in an HTTP request, such as header files or parameters.
Filter: An instance of a GatewayFilter built by a particular factory that, like a traditional Filter, can be processed before and after the request.
The working principle of
The client makes a request to the Spring Cloud Gateway. If the Gateway handler map determines that a request matches the route, it is sent to the Gateway Web handler. The handler runs the request through a chain of filters specific to the request.
Filters can run pre and POST logic before and after a proxy request is sent.
Simple to use
To prepare
Pre-prepare a service to test routes
Here I prepared a goods and services, and provides an interface: http://localhost:8082/goods/get-goods
Now, start writing the gateway service
Introduction of depend on
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-gateway</artifactId>
</dependency>
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
</dependency>
Copy the code
Write the configuration
bootstrap.yaml
server:
port: 5555
spring:
application:
name: my-gateway
cloud:
nacos:
discovery:
server-addr: 127.0. 01.: 8848
namespace: public
username: nacos
password: nacos
logging:
level:
org.springframework.cloud.gateway: info
com.alibaba.nacos.client.naming: warn
Copy the code
application.yaml
spring:
cloud:
gateway:
# Route configuration
routes:
# route ID ensures uniqueness
- id: my-goods
// Service name lb: Load balance, my-goods: service name
uri: lb://my-goods
# assertion
predicates:
Match the request at the beginning of goods
- Path=/goods/**
Copy the code
Start the class
package com.my.micro.service.gateway;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
/ * * *@author Zijian Liao
* @since1.0.0 * /
@SpringBootApplication
public class GatewayApplication {
public static void main(String[] args) { SpringApplication.run(GatewayApplication.class, args); }}Copy the code
test
Start the service, and visit: http://localhost:5555/goods/get-goods
As you can see, the service was successfully routed
A simple gateway service is completed in this way. Have you seen the concept of gateway more profound?
assertions
In the example above, we used an assertion factory: Path
In the Spring Cloud Gateway and all assertions factory is inherited in AbstractRoutePredicateFactory, and naming rules for: XxxRoutePredicateFactory, such as the Path class called: PathRoutePredicateFactory
So what assertion factories does Spring Cloud Gateway give us built-in?
Documents: the docs. Spring. IO/spring – clou…
Here are some examples of assertion factories that I think are commonly used
After
Matches requests after a certain time (ZonedDateTime)
spring:
cloud:
gateway:
# Route configuration
routes:
# route ID ensures uniqueness
- id: my-goods
// Service name lb: Load balance, my-goods: service name
uri: lb://my-goods
# assertion
predicates:
Match the request at the beginning of goods
- Path=/goods/**
Match requests made after 23:05 minutes
- After = 2021-08-08 T23:05:13. 605 + 08:00 Asia/Shanghai
Copy the code
We test at 23:03
Access failed
Before
Matches requests before a certain time (ZonedDateTime)
Similar to After, no longer demonstrated
Between
Matches requests at a certain time period (ZonedDateTime)
spring:
cloud:
gateway:
# Route configuration
routes:
# route ID ensures uniqueness
- id: my-goods
// Service name lb: Load balance, my-goods: service name
uri: lb://my-goods
# assertion
predicates:
Match the request at the beginning of goods
- Path=/goods/**
Match request 23:05-23:10
- Between = 2021-08-08 T23:05:13. 605 + 08:00 Asia/Shanghai, the 2021-08-08 T23: "yet. 605 + 08:00 Asia/Shanghai
Copy the code
Host
Matches a request to a Host
spring:
cloud:
gateway:
# Route configuration
routes:
# route ID ensures uniqueness
- id: my-goods
// Service name lb: Load balance, my-goods: service name
uri: lb://my-goods
# assertion
predicates:
Match the request at the beginning of goods
- Path=/goods/**
Set host to 192.168.1.105
- The Host = 192.168.1.105
Copy the code
Note that you need to change the port number to 80 when testing
Try making the call using 127.0.0.1
Call instead at 192.168.1.105
RemoteAddr
Matches the specified remote source address
spring:
cloud:
gateway:
# Route configuration
routes:
# route ID ensures uniqueness
- id: my-goods
// Service name lb: Load balance, my-goods: service name
uri: lb://my-goods
# assertion
predicates:
Match the request at the beginning of goods
- Path=/goods/**
Set RemoteAddr to 192.168.1
- RemoteAddr = 192.168.1.1/24
Copy the code
test
Enable Intranet penetration test
Access failed
The filter
I’ll give you an example about filters. For more information, please refer to the documentation
IO /spring-clou…
Here’s an example of a filter that’s used a lot:
StripPrefix
As the name implies, a prefixed filter removes the prefix from the matched request and forwards the removed request to the downstream service
spring:
cloud:
gateway:
# Route configuration
routes:
# route ID ensures uniqueness
- id: my-goods
// Service name lb: Load balance, my-goods: service name
uri: lb://my-goods
# assertion
predicates:
Match the request at the beginning of goods
- Path=/api/goods/**
filters:
# 1 removes a prefix
- StripPrefix=1
Copy the code
Combination, the meaning is initiated when a client requests: http://localhost:5555/api/goods/get-goods, matching chain by, then remove the first prefix API, and then forwarded to goods and services, forwarding path for: working/goods/get – goods
test
Custom assertion factory
Mentioned above: that all factories are inherited in AbstractRoutePredicateFactory, and naming rules for: XxxRoutePredicateFactory, such as the Path class called: PathRoutePredicateFactory
Let’s now try to implement a custom request header assertion factory
Write the code
package com.my.micro.service.gateway.filter;
import org.springframework.cloud.gateway.handler.predicate.AbstractRoutePredicateFactory;
import org.springframework.cloud.gateway.handler.predicate.GatewayPredicate;
import org.springframework.stereotype.Component;
import org.springframework.web.server.ServerWebExchange;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
import java.util.function.Predicate;
/ * * *@author Zijian Liao
* @since1.0.0 * /
@Component
public class MyHeaderRoutePredicateFactory extends AbstractRoutePredicateFactory<MyHeaderRoutePredicateFactory.Config> {
/** * Header key. */
public static final String HEADER_KEY = "header";
/** * Regexp key. */
public static final String REGEXP_KEY = "regexp";
public MyHeaderRoutePredicateFactory(a) {
super(MyHeaderRoutePredicateFactory.Config.class);
}
@Override
public List<String> shortcutFieldOrder(a) {
return Arrays.asList(HEADER_KEY, REGEXP_KEY);
}
@Override
public Predicate<ServerWebExchange> apply(MyHeaderRoutePredicateFactory.Config config) {
return new GatewayPredicate() {
@Override
public boolean test(ServerWebExchange exchange) {
// Get the request header
List<String> values = exchange.getRequest().getHeaders()
.getOrDefault(config.header, Collections.emptyList());
if (values.isEmpty()) {
return false;
}
// Determine if the value in the request header matches the configuration
return values.stream()
.anyMatch(value -> value.matches(config.regexp));
}
@Override
public String toString(a) {
return String.format("Header: %s=%s ", config.header, config.regexp); }}; }public static class Config {
private String header;
private String regexp;
public String getHeader(a) {
return header;
}
public void setHeader(String header) {
this.header = header;
}
public String getRegexp(a) {
return regexp;
}
public void setRegexp(String regexp) {
this.regexp = regexp; }}}Copy the code
Write the configuration
spring:
cloud:
gateway:
# Route configuration
routes:
# route ID ensures uniqueness
- id: my-goods
// Service name lb: Load balance, my-goods: service name
uri: lb://my-goods
# assertion
predicates:
Match the request at the beginning of goods
- Path=/api/goods/**
# match request whose header is name=aljian
- MyHeader=name,ajian
filters:
# 1 removes a prefix
- StripPrefix=1
Copy the code
test
Access directly from the browser
Use Postman access instead
Custom filter
You customize filters in much the same way you customize assertion factories, So filter inheritance in AbstractGatewayFilterFactory or AbstractNameValueGatewayFilterFactory, naming rules for XxxGatewayFilterFactory
Such as the built-in add request header filter
public class AddRequestHeaderGatewayFilterFactory
extends AbstractNameValueGatewayFilterFactory {
@Override
public GatewayFilter apply(NameValueConfig config) {
return new GatewayFilter() {
@Override
public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
// Get the header value to be added
String value = ServerWebExchangeUtils.expand(exchange, config.getValue());
// Add header to request
ServerHttpRequest request = exchange.getRequest().mutate()
.header(config.getName(), value).build();
// Rebuild an Exchange
return chain.filter(exchange.mutate().request(request).build());
}
@Override
public String toString(a) {
return filterToStringCreator(AddRequestHeaderGatewayFilterFactory.this) .append(config.getName(), config.getValue()).toString(); }}; }}Copy the code
Global filter
While the above is for each router, the Spring Cloud Gateway provides a global filter for all routers
The implementation is as follows
package com.my.micro.service.gateway.filter;
import lombok.extern.slf4j.Slf4j;
import org.springframework.cloud.gateway.filter.GatewayFilterChain;
import org.springframework.cloud.gateway.filter.GlobalFilter;
import org.springframework.stereotype.Component;
import org.springframework.web.server.ServerWebExchange;
import reactor.core.publisher.Mono;
/ * * *@author Zijian Liao
* @since1.0.0 * /
@Slf4j
@Component
public class MyGlobalFilter implements GlobalFilter {
@Override
public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
String path = exchange.getRequest().getURI().getPath();
log.info("Enter global filter, request path: {}", path);
Write any logic you want to implement, such as permissions verification
returnchain.filter(exchange); }}Copy the code
test
Custom exception handlers
As you may have noticed, the Spring Cloud Gateway does not return an elegant exception to the client when it encounters an error, so we need to customize the exception handling
Write custom exception handlers
package com.my.micro.service.gateway.exception;
import com.my.micro.service.gateway.result.BaseResult;
import lombok.extern.slf4j.Slf4j;
import org.springframework.boot.autoconfigure.web.ErrorProperties;
import org.springframework.boot.autoconfigure.web.ResourceProperties;
import org.springframework.boot.autoconfigure.web.reactive.error.DefaultErrorWebExceptionHandler;
import org.springframework.boot.web.reactive.error.ErrorAttributes;
import org.springframework.context.ApplicationContext;
import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType;
import org.springframework.lang.NonNull;
import org.springframework.web.reactive.function.BodyInserters;
import org.springframework.web.reactive.function.server.RequestPredicates;
import org.springframework.web.reactive.function.server.RouterFunction;
import org.springframework.web.reactive.function.server.RouterFunctions;
import org.springframework.web.reactive.function.server.ServerRequest;
import org.springframework.web.reactive.function.server.ServerResponse;
import reactor.core.publisher.Mono;
/ * * *@author Zijian Liao
*/
@Slf4j
public class JsonExceptionHandler extends DefaultErrorWebExceptionHandler {
/**
* Create a new {@code DefaultErrorWebExceptionHandler} instance.
*
* @param errorAttributes the error attributes
* @param resourceProperties the resources configuration properties
* @param errorProperties the error configuration properties
* @param applicationContext the current application context
*/
public JsonExceptionHandler(ErrorAttributes errorAttributes, ResourceProperties resourceProperties, ErrorProperties errorProperties, ApplicationContext applicationContext) {
super(errorAttributes, resourceProperties, errorProperties, applicationContext);
}
@Override
protected RouterFunction<ServerResponse> getRoutingFunction(ErrorAttributes errorAttributes) {
return RouterFunctions.route(RequestPredicates.all(), this::renderErrorResponse);
}
@NonNull
@Override
protected Mono<ServerResponse> renderErrorResponse(ServerRequest request) {
Throwable throwable = getError(request);
return ServerResponse.status(HttpStatus.OK)
.contentType(MediaType.APPLICATION_JSON)
.body(BodyInserters.fromValue(this.handleError(throwable)));
}
private BaseResult<Void> handleError(Throwable throwable){
returnBaseResult.failure(throwable.getMessage()); }}Copy the code
BaseResult
package com.my.micro.service.gateway.result;
import lombok.Data;
/ * * *@author Zijian Liao
* @since1.0.0 * /
@Data
public class BaseResult<T> {
private Integer code;
private String message;
public BaseResult(Integer code, String message){
this.code = code;
this.message = message;
}
public static <T> BaseResult<T> failure(String message){
return new BaseResult<>(-1, message); }}Copy the code
Writing configuration classes
package com.my.micro.service.gateway.exception;
import org.springframework.beans.factory.ObjectProvider;
import org.springframework.boot.autoconfigure.web.ResourceProperties;
import org.springframework.boot.autoconfigure.web.ServerProperties;
import org.springframework.boot.autoconfigure.web.reactive.error.DefaultErrorWebExceptionHandler;
import org.springframework.boot.web.reactive.error.ErrorAttributes;
import org.springframework.boot.web.reactive.error.ErrorWebExceptionHandler;
import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Primary;
import org.springframework.core.Ordered;
import org.springframework.core.annotation.Order;
import org.springframework.http.codec.ServerCodecConfigurer;
import org.springframework.web.reactive.result.view.ViewResolver;
import java.util.stream.Collectors;
/ * * *@author Zijian Liao
* @since1.0.0 * /
@Configuration
public class ExceptionConfiguration {
@Primary
@Bean
@Order(Ordered.HIGHEST_PRECEDENCE)
public ErrorWebExceptionHandler errorWebExceptionHandler(ErrorAttributes errorAttributes, ServerProperties serverProperties, ResourceProperties resourceProperties, ObjectProvider
viewResolversProvider, ServerCodecConfigurer serverCodecConfigurer, ApplicationContext applicationContext)
{
DefaultErrorWebExceptionHandler exceptionHandler = new JsonExceptionHandler(errorAttributes,
resourceProperties, serverProperties.getError(), applicationContext);
exceptionHandler.setViewResolvers(viewResolversProvider.orderedStream().collect(Collectors.toList()));
exceptionHandler.setMessageWriters(serverCodecConfigurer.getWriters());
exceptionHandler.setMessageReaders(serverCodecConfigurer.getReaders());
returnexceptionHandler; }}Copy the code
test
summary
This part introduces a solution for how clients access microservices architecture: Spring Cloud Gateway
It introduces the three core concepts of Gateway: Route, Predicate, and Filter. I showed you how to configure and use them, and I also showed you how to customize Predicate and Filter.
Finally, we introduced the Spring Cloud Gateway’s global filters and how to implement custom exception handling.
The above
If you want to know more exciting content, welcome to pay attention to the public account: programmer AH Jian, AH Jian in the public account welcome your arrival ~
Personal blog space: zijiancode. Cn/archives/ga…
Gittee: gitee.com/lzj960515/m…