Spring Cloud Gateway (1) Начало работы

Java

1. Как развивался шлюз?

  • После разделения одного приложения на несколько служб требуется единая внешняя запись, чтобы отделить клиент от внутренних служб.
    在这里插入图片描述

2. Основные функции шлюза

  • Основной функцией шлюза является маршрутизация и переадресация, поэтому не нужно выполнять трудоемкие операции на шлюзе, чтобы запрос можно было быстро перенаправить на серверную службу.
  • Шлюз также может выполнять унифицированное слияние, ограничение тока, аутентификацию, мониторинг журналов и т. д.
    在这里插入图片描述

3. О Spring Cloud Gateway

Spring Cloud Gateway - это шлюз, разработанный официальным представителем Spring на основе Spring 5.0, Spring Boot 2.0, Project Reactor и других технологий. Он использует неблокирующий API и поддерживает веб-сокеты. Цель состоит в том, чтобы заменить Spring Cloud Netfilx Zuul в В настоящее время Netfilx является Zuul2.0 с открытым исходным кодом, но Spring не рассматривал интеграцию, а запустил собственную разработку Spring Cloud GateWay.Здесь нужно обратить внимание на реализацию netty+webflux, используемую шлюзом, не добавлять веб-зависимости (не ссылаться на webmvc), иначе инициализация сообщит об ошибке, нужно добавить зависимости webflux.

Простое сравнение между gateway и zuul: gateway использует асинхронный запрос, zuul — синхронный запрос, данные gateway инкапсулированы в ServerWebExchange, zuul инкапсулирован в RequestContext, синхронизация удобна для отладки, а данные можно инкапсулировать в ThreadLocal для передачи.

Spring Cloud Gateway имеет три основных понятия: маршрутизация, утверждения, фильтры.
Фильтр: Существует два типа фильтров для шлюза: GlobalFilter и GatewayFilter.Глобальный фильтр действует для всех маршрутов по умолчанию.
Адрес документа:cloud.spring.IO/spring - уродливый...

Шлюз используется в качестве точки входа для всего трафика запросов. В реальной производственной среде, чтобы обеспечить высокую надежность и высокую доступность, старайтесь избегать перезапуска. Требуется конфигурация динамической маршрутизации, и конфигурация маршрутизации изменяется во время выполнения процесса. шлюза.

4. Практика кодекса

Необходимо использовать 3 проекта: eureka-server, gateway, Consumer-Service.

  • 1. Регистрация службы обнаружения сервера eureka, чтобы шлюз получал IP+порт экземпляра службы при пересылке запросов, используйте пример кода в предыдущем блоге.
  • 2. Создайте новый проект шлюза шлюза, ссылка на проект выглядит следующим образом:
<dependency>
	<groupId>org.springframework.cloud</groupId>
	<artifactId>spring-cloud-starter-gateway</artifactId>
</dependency>
<dependency>
	<groupId>org.springframework.cloud</groupId>
	<artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
</dependency>
<dependency>
	<groupId>org.springframework.boot</groupId>
	<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
123456789101112

Включить аннотацию регистрации службы обнаружения @EnableDiscoveryClient в основном классе
Содержимое конфигурационного файла следующее:

server:
  port: 9999
spring:
  profiles:
    active: dev
  application:
    name: gateway-service
  cloud:
    gateway:
      discovery:
        locator:
          enabled: true
          # 服务名小写
          lower-case-service-id: true
      routes:
      - id: consumer-service
        # lb代表从注册中心获取服务,且已负载均衡方式转发
        uri: lb://consumer-service
        predicates:
        - Path=/consumer/**
        # 加上StripPrefix=1,否则转发到后端服务时会带上consumer前缀
        filters:
        - StripPrefix=1

# 注册中心
eureka:
  instance:
    prefer-ip-address: true
  client:
    service-url:
      defaultZone: http://zy:zy123@localhost:10025/eureka/


# 暴露监控端点
management:
  endpoints:
    web:
      exposure:
        include: '*'
  endpoint:
    health:
      show-details: always
123456789101112131415161718192021222324252627282930313233343536373839404142

Часть кода шлюза завершена выше. Затем создайте новый Consumer-Service

  • 3. потребительская служба потребительской службы, перенаправляемая потребительской службе через маршрутизацию шлюза и возвращающая информацию обратно, поэтому это проект mvc.
    Ссылки на проекты следующие:
<dependency>
	<groupId>org.springframework.boot</groupId>
	<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
	<groupId>org.springframework.cloud</groupId>
	<artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
</dependency>
12345678

Включить аннотацию регистрации службы обнаружения @EnableDiscoveryClient в основном классе
Добавьте конфигурацию в файл конфигурации:

server.port=9700
spring.application.name=consumer-service
eureka.instance.prefer-ip-address=true
# 配置eureka-server security的账户信息
eureka.client.serviceUrl.defaultZone=http://zy:zy123@localhost:10025/eureka/
12345

Создайте новый IndexController, добавьте метод приветствия, передайте параметр имени и верните строку привет + имя после доступа.

@RestController
public class IndexController {

    @RequestMapping("/hello")
    public String hello(String name){
        return "hi " + name;
    }
}
12345678
  • 4. Запустить 3 проекта соответственно, посетитьhttp://localhost:10025Посмотрите, зарегистрированы ли экземпляры шлюза и потребительской службы на eureka, вы можете видеть, что они были зарегистрированы на портах 9700 и 9999 соответственно.
    在这里插入图片描述

Получите доступ к методу приветствия потребительского обслуживания через шлюз,http://localhost:9999/consumer/hello?name=zy, эффект выглядит следующим образом, указывая на то, что запрос был перенаправлен в службу обслуживания потребителей.
在这里插入图片描述

Вышеизложенное завершает базовый код шлюза.Давайте продолжим вводить некоторые часто используемые фильтры и реализовывать унифицированную аутентификацию, журнал, безопасность и другие проверки с помощью фильтров.

  • 5. Добавьте глобальный фильтр GlobalFilter в проект шлюза и напечатайте URL-адрес каждого запроса. Код выглядит следующим образом:
/**
 * 全局过滤器
 * 所有请求都会执行
 * 可拦截get、post等请求做逻辑处理
 */
@Component
public class RequestGlobalFilter implements GlobalFilter,Ordered {

    //执行逻辑
    @Override
    public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
        ServerHttpRequest serverHttpRequest= exchange.getRequest();
        String uri = serverHttpRequest.getURI().toString();
        System.out.println(" uri : " + uri);//打印每次请求的url
        String method = serverHttpRequest.getMethodValue();
        if ("POST".equals(method)) {
            //从请求里获取Post请求体
            String bodyStr = resolveBodyFromRequest(serverHttpRequest);
            //TODO 得到Post请求的请求参数后,做你想做的事
 
            //下面的将请求体再次封装写回到request里,传到下一级,否则,由于请求体已被消费,后续的服务将取不到值
            URI uri = serverHttpRequest.getURI();
            ServerHttpRequest request = serverHttpRequest.mutate().uri(uri).build();
            DataBuffer bodyDataBuffer = stringBuffer(bodyStr);
            Flux<DataBuffer> bodyFlux = Flux.just(bodyDataBuffer);
 
            request = new ServerHttpRequestDecorator(request) {
                @Override
                public Flux<DataBuffer> getBody() {
                    return bodyFlux;
                }
            };
            //封装request,传给下一级
            return chain.filter(exchange.mutate().request(request).build());
        } else if ("GET".equals(method)) {
            Map requestQueryParams = serverHttpRequest.getQueryParams();
            //TODO 得到Get请求的请求参数后,做你想做的事
 
            return chain.filter(exchange);
        }
        return chain.filter(exchange);
    }
	/**
     * 从Flux<DataBuffer>中获取字符串的方法
     * @return 请求体
     */
    private String resolveBodyFromRequest(ServerHttpRequest serverHttpRequest) {
        //获取请求体
        Flux<DataBuffer> body = serverHttpRequest.getBody();
 
        AtomicReference<String> bodyRef = new AtomicReference<>();
        body.subscribe(buffer -> {
            CharBuffer charBuffer = StandardCharsets.UTF_8.decode(buffer.asByteBuffer());
            DataBufferUtils.release(buffer);
            bodyRef.set(charBuffer.toString());
        });
        //获取request body
        return bodyRef.get();
    }
 
    private DataBuffer stringBuffer(String value) {
        byte[] bytes = value.getBytes(StandardCharsets.UTF_8);
 
        NettyDataBufferFactory nettyDataBufferFactory = new NettyDataBufferFactory(ByteBufAllocator.DEFAULT);
        DataBuffer buffer = nettyDataBufferFactory.allocateBuffer(bytes.length);
        buffer.write(bytes);
        return buffer;
    }

    //执行顺序
    @Override
    public int getOrder() {
        return 1;
    }
}
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475

Перезапустите проект шлюза и посетитеhttp://localhost:9999/consumer/hello?name=zy, проверьте консоль, вы можете увидеть, что журнал uri печатается
在这里插入图片描述

  • 6. Добавляем фильтр GatewayFilter в проект шлюза.Добавляем фильтр проверки подлинности токена в сервис-потребитель.Как и глобальный фильтр u, GatewayFilter нужно указать, какой сервис использовать этот фильтр в файле конфигурации.Код выглядит следующим образом :
/**
 * 可对客户端header 中的 Authorization 信息进行认证
 */
@Component
public class TokenAuthenticationFilter extends AbstractGatewayFilterFactory {

    private static final String Bearer_ = "Bearer ";

    @Override
    public GatewayFilter apply(Object config) {
        return (exchange, chain) -> {
            ServerHttpRequest request = exchange.getRequest();
            ServerHttpRequest.Builder mutate = request.mutate();
            ServerHttpResponse response = exchange.getResponse();
            try {
                //String token = exchange.getRequest().getQueryParams().getFirst("authToken");
                //1.获取header中的Authorization
                String header = request.getHeaders().getFirst("Authorization");
                if (header == null || !header.startsWith(Bearer_)) {
                    throw new RuntimeException("请求头中Authorization信息为空");
                }
                //2.截取Authorization Bearer
                String token = header.substring(7);
                //可把token存到redis中,此时直接在redis中判断是否有此key,有则校验通过,否则校验失败
                if(!StringUtils.isEmpty(token)){
                    System.out.println("验证通过");
                    //3.有token,把token设置到header中,传递给后端服务
                    mutate.header("userDetails",token).build();
                }else{
                    //4.token无效
                    System.out.println("token无效");
                    DataBuffer bodyDataBuffer = responseErrorInfo(response , HttpStatus.UNAUTHORIZED.toString() ,"无效的请求");
                    return response.writeWith(Mono.just(bodyDataBuffer));
                }
            }catch (Exception e){
                //没有token
                DataBuffer bodyDataBuffer = responseErrorInfo(response , HttpStatus.UNAUTHORIZED.toString() ,e.getMessage());
                return response.writeWith(Mono.just(bodyDataBuffer));
            }
            ServerHttpRequest build = mutate.build();
            return chain.filter(exchange.mutate().request(build).build());
        };
    }

    /**
     * 自定义返回错误信息
     * @param response
     * @param status
     * @param message
     * @return
     */
    public DataBuffer responseErrorInfo(ServerHttpResponse response , String status ,String message){
        HttpHeaders httpHeaders = response.getHeaders();
        httpHeaders.add("Content-Type", "application/json; charset=UTF-8");
        httpHeaders.add("Cache-Control", "no-store, no-cache, must-revalidate, max-age=0");

        response.setStatusCode(HttpStatus.UNAUTHORIZED);
        Map<String,String> map = new HashMap<>();
        map.put("status",status);
        map.put("message",message);
        DataBuffer bodyDataBuffer = response.bufferFactory().wrap(map.toString().getBytes());
        return bodyDataBuffer;
    }
}
12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364

Укажите службу обслуживания потребителей для использования TokenAuthenticationFilter в файле конфигурации, и конфигурация выглядит следующим образом:

routes:
- id: consumer-service
  uri: lb://consumer-service
  predicates:
  - Path=/consumer/**
  filters:
  # 进行token过滤
  - TokenAuthenticationFilter
  - StripPrefix=1
123456789

Запустите проект и посетите сноваhttp://localhost:9999/consumer/hello?name=zy
在这里插入图片描述

  • 7. Проекты разделения внешнего и внутреннего интерфейса решают проблему междоменного шлюза.Добавьте следующий код в основной класс шлюза:
@Bean
	public WebFilter corsFilter() {
		return (ServerWebExchange ctx, WebFilterChain chain) -> {
			ServerHttpRequest request = ctx.getRequest();
			if (!CorsUtils.isCorsRequest(request)) {
				return chain.filter(ctx);
			}

			HttpHeaders requestHeaders = request.getHeaders();
			ServerHttpResponse response = ctx.getResponse();
			HttpMethod requestMethod = requestHeaders.getAccessControlRequestMethod();
			HttpHeaders headers = response.getHeaders();
			headers.add(HttpHeaders.ACCESS_CONTROL_ALLOW_ORIGIN, requestHeaders.getOrigin());
			headers.addAll(HttpHeaders.ACCESS_CONTROL_ALLOW_HEADERS, requestHeaders.getAccessControlRequestHeaders());
			if (requestMethod != null) {
				headers.add(HttpHeaders.ACCESS_CONTROL_ALLOW_METHODS, requestMethod.name());
			}
			headers.add(HttpHeaders.ACCESS_CONTROL_ALLOW_CREDENTIALS, "true");
			headers.add(HttpHeaders.ACCESS_CONTROL_EXPOSE_HEADERS, "all");
			headers.add(HttpHeaders.ACCESS_CONTROL_MAX_AGE, "3600");
			if (request.getMethod() == HttpMethod.OPTIONS) {
				response.setStatusCode(HttpStatus.OK);
				return Mono.empty();
			}
			return chain.filter(ctx);
		};
	}
123456789101112131415161718192021222324252627

Код был загружен в Code Cloud,исходный код, информация о версии, используемая проектом, выглядит следующим образом:

- SpringBoot 2.0.6.RELEASE
- SpringCloud Finchley.SR2

Связь:blog.CSDN.net/Тема 199110…