从零搭建Spring Cloud Gateway网关(一)
- 2020 年 3 月 18 日
- 筆記
新建Spring Boot项目
怎么新建Spring Boot项目这里不再具体赘述,不会的可以翻看下之前的博客或者直接百度。这里直接贴出对应的pom文件。
pom依赖如下:
<?xml version="1.0" encoding="UTF-8"?> <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd"> <modelVersion>4.0.0</modelVersion> <parent> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-parent</artifactId> <version>2.2.5.RELEASE</version> <relativePath/> <!-- lookup parent from repository --> </parent> <groupId>com.lifengdi</groupId> <artifactId>gateway</artifactId> <version>0.0.1-SNAPSHOT</version> <name>gateway</name> <description>Demo project for Spring Boot</description> <properties> <java.version>1.8</java.version> <spring-cloud.version>Hoxton.SR3</spring-cloud.version> </properties> <dependencies> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-gateway</artifactId> </dependency> <dependency> <groupId>org.projectlombok</groupId> <artifactId>lombok</artifactId> <version>1.18.12</version> <optional>true</optional> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-test</artifactId> <scope>test</scope> <exclusions> <exclusion> <groupId>org.junit.vintage</groupId> <artifactId>junit-vintage-engine</artifactId> </exclusion> </exclusions> </dependency> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-netflix-hystrix</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-actuator</artifactId> </dependency> <!-- <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-data-redis-reactive</artifactId> </dependency> <dependency> <groupId>org.springframework.session</groupId> <artifactId>spring-session-data-redis</artifactId> </dependency>--> <dependency> <groupId>org.apache.commons</groupId> <artifactId>commons-pool2</artifactId> </dependency> <dependency> <groupId>commons-io</groupId> <artifactId>commons-io</artifactId> <version>2.5</version> <scope>compile</scope> </dependency> <dependency> <groupId>com.alibaba</groupId> <artifactId>fastjson</artifactId> <version>1.2.58</version> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-mail</artifactId> </dependency> </dependencies> <dependencyManagement> <dependencies> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-dependencies</artifactId> <version>${spring-cloud.version}</version> <type>pom</type> <scope>import</scope> </dependency> </dependencies> </dependencyManagement> <build> <plugins> <plugin> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-maven-plugin</artifactId> </plugin> </plugins> </build> </project>
由于是网关项目,所以不需要spring-boot-starter-web
相关的依赖。
配置文件如下:
server: port: 8080 spring: application: name: spring-cloud-gateway-demo cloud: gateway: discovery: locator: enabled: true #启用路由访问 routes: - id: path_route # 指定域名 uri: http://localhost:8081 predicates: - Path=/jar/** filters: # 熔断配置 - name: Hystrix args: name: default fallbackUri: forward:/fallback - id: path_route2 # 指定域名 uri: http://localhost:8082 predicates: - Path=/war/** filters: # 熔断配置 - name: Hystrix args: name: hystrix1 fallbackUri: forward:/fallback mvc: throw-exception-if-no-handler-found: true # 默认熔断超时时间30s hystrix: command: default: execution: isolation: thread: timeoutInMilliseconds: 3000 hystrix1: execution: isolation: thread: timeoutInMilliseconds: 1000
熔断(接口或者项目)
熔断相关jar包如下:
<dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-netflix-hystrix</artifactId> </dependency>
默认的熔断回调接口:
package com.lifengdi.gateway.hystrix; import com.lifengdi.gateway.exception.BaseException; import com.lifengdi.gateway.response.ResponseResult; import lombok.extern.slf4j.Slf4j; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; /** * @author: Li Fengdi * @date: 2020-03-18 16:35 */ @RestController @Slf4j public class DefaultHystrixController { @RequestMapping("/fallback") public ResponseResult<Object> fallback(){ log.error("触发熔断......"); return ResponseResult.fail(BaseException.DEFAULT_HYSTRIX.build()); } }
具体配置文件说明如下:
routes: - id: path_route # 指定域名 uri: http://localhost:8081 predicates: - Path=/jar/** filters: # 熔断配置 - name: Hystrix args: name: default fallbackUri: forward:/fallback - id: path_route2 # 指定域名 uri: http://localhost:8082 predicates: - Path=/war/** filters: # 熔断配置 - name: Hystrix args: name: hystrix1 fallbackUri: forward:/fallback mvc: throw-exception-if-no-handler-found: true # 默认熔断超时时间30s hystrix: command: default: execution: isolation: thread: timeoutInMilliseconds: 3000 hystrix1: execution: isolation: thread: timeoutInMilliseconds: 1000
default
、hystrix1
为自定义的参数,可以配置多个熔断策略,不同的接口、服务可以单独配置对应的超时时间,不需要额外的进行开发,不过需要增加额外的配置文件。
全局session共享
依赖jar包:
<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-data-redis-reactive</artifactId> </dependency> <dependency> <groupId>org.springframework.session</groupId> <artifactId>spring-session-data-redis</artifactId> </dependency> <dependency> <groupId>org.apache.commons</groupId> <artifactId>commons-pool2</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-security</artifactId> </dependency>
相关yml配置:
spring: redis: database: 0 host: localhost port: 6379 password: 123456 lettuce: pool: max-active: 300 max-idle: 8 max-wait: -1ms min-idle: 0 session: store-type: redis
spring.session.store-type
Spring默认就是redis实现的,也有其他的,配置不同罢了。
增加代码如下:
权限相关,这里默认全部放行:
package com.lifengdi.gateway.config; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.security.config.annotation.web.reactive.EnableWebFluxSecurity; import org.springframework.security.config.web.server.ServerHttpSecurity; import org.springframework.security.web.server.SecurityWebFilterChain; @Configuration @EnableWebFluxSecurity public class GatewaySecurityConfig { @Bean SecurityWebFilterChain springWebFilterChain(ServerHttpSecurity serverHttpSecurity) throws Exception { serverHttpSecurity .csrf().disable() .authorizeExchange().pathMatchers("/**").permitAll() .anyExchange() .authenticated(); return serverHttpSecurity.build(); } }
session相关:
package com.lifengdi.gateway.config; import com.lifengdi.gateway.resolver.MyCookieWebSessionIdResolver; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.http.ResponseCookie; import org.springframework.session.data.redis.config.annotation.web.server.EnableRedisWebSession; import org.springframework.web.server.session.CookieWebSessionIdResolver; import org.springframework.web.server.session.WebSessionIdResolver; import java.util.function.Consumer; @Configuration @EnableRedisWebSession(maxInactiveIntervalInSeconds = 10*60*60, redisNamespace = "my:spring:session") public class WebSessionConfig { @Bean public WebSessionIdResolver webSessionIdResolver() { CookieWebSessionIdResolver resolver = new MyCookieWebSessionIdResolver(); resolver.setCookieName("SESSIONID"); Consumer<ResponseCookie.ResponseCookieBuilder> consumer = responseCookieBuilder -> { responseCookieBuilder.path("/"); }; resolver.addCookieInitializer(consumer); return resolver; } }
注意这里使用的是@EnableRedisWebSession
注解,而不是@EnableRedisHttpSession
,这个是和zuul不一样的地方。
用zuul做网关的时候,直接使用@EnableRedisHttpSession
在配置里面就可以通过redis共享session信息
Spring同时提供了@EnableRedisWebSession
来对WebFlux的支持。
值得一提的是这两个注解内部实现并不相同,需要自定义的配置也不一样。
这里自定义cookieName、path等是自定义了webSessionIdResolver
来实现的,而不是cookieSerializer
。如果使用cookieSerializer
的话,对@EnableRedisWebSession
来说是不起作用的。这个坑之前坑了好半天!
MyCookieWebSessionIdResolver代码如下:
package com.lifengdi.gateway.resolver; import lombok.extern.slf4j.Slf4j; import org.springframework.http.HttpCookie; import org.springframework.session.web.http.DefaultCookieSerializer; import org.springframework.util.MultiValueMap; import org.springframework.web.server.ServerWebExchange; import org.springframework.web.server.session.CookieWebSessionIdResolver; import java.util.Base64; import java.util.Collections; import java.util.List; import java.util.stream.Collectors; /** * 自定义WebSessionId解析器,以兼容{@link org.springframework.session.data.redis.config.annotation.web.http.EnableRedisHttpSession} * <p> * 使用EnableRedisHttpSession时{@link DefaultCookieSerializer}中useBase64Encoding默认为true,将cookie中的sessionId使用base64 * 加密,但是如果使用{@link org.springframework.session.data.redis.config.annotation.web.server.EnableRedisWebSession},默认 * 的解析器没有将sessionId解密,导致获取不到正确的session * </p> * * @author: Li Fengdi * @date: 2020/3/16 15:41 */ @Slf4j public class MyCookieWebSessionIdResolver extends CookieWebSessionIdResolver { @Override public List<String> resolveSessionIds(ServerWebExchange exchange) { MultiValueMap<String, HttpCookie> cookieMap = exchange.getRequest().getCookies(); List<HttpCookie> cookies = cookieMap.get(getCookieName()); if (cookies == null) { return Collections.emptyList(); } return cookies.stream().map(HttpCookie::getValue).map(this::base64Decode).collect(Collectors.toList()); } /** * base64解码 * * @param base64Value base64Value * @return 解码后的字符串 */ private String base64Decode(String base64Value) { try { byte[] decodedCookieBytes = Base64.getDecoder().decode(base64Value); return new String(decodedCookieBytes); } catch (Exception ex) { log.debug("Unable to Base64 decode value: " + base64Value); return null; } } }
其实这段代码本就是参考了cookieSerializer
中的代码来实现的。
如果指定了useBase64Encoding为false,即不加密sessionId,那么就不需要这一段代码了。
代码已上传到git上,需要的可以去看看。
git代码地址:https://github.com/lifengdi/spring-cloud-gateway-demo
原文地址:https://www.lifengdi.com/archives/article/1776