微服务Springcloud——Gateway
文章目录
前言
现网上gateway多跟eurake或者数据库连用配置动态路由,但我只用到了gateway的转发功能,并不需要进行注册接口服务,所以在Filter里面进行了逻辑处理,例子比较少,记录一下,也方便小伙伴一起码,直接上代码喽;
提示:数据库中直接保存了要调用接口的整个url,无法配置动态路由。
一、gateway是什么?
pring Cloud Gateway 是 Spring Cloud 新推出的网关框架,之前是 Netflix Zuul。网关通常在项目中为了简化前端的调用逻辑,同时也简化内部服务之间互相调用的复杂度;具体作用就是转发服务,接收并转发所有内外部的客户端调用;其他常见的功能还有权限认证,限流控制等等。
简而言之,就是统一转发的框架 ;
二、基础gateway
1.引入pom
pom代码如下:
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.0.4.RELEASE</version>
<relativePath />
</parent>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
</dependency>
<!-- gateway -->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-gateway</artifactId>
<version>2.2.2.RELEASE</version>
</dependency>
<!-- 热部署 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-devtools</artifactId>
<version>2.0.6.RELEASE</version>
</dependency>
<!-- https://mvnrepository.com/artifact/org.springframework.boot/spring-boot-starter-data-redis-reactive -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis-reactive</artifactId>
<version>2.2.2.RELEASE</version>
</dependency>
<!-- fastjson -->
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>fastjson</artifactId>
<version>1.2.56</version>
</dependency>
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-boot-starter</artifactId>
<version>3.0.6</version>
</dependency>
<dependency>
<groupId>oracle</groupId>
<artifactId>ojdbc6</artifactId>
<version>11.2.0.3</version>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>5.1.38</version>
<scope>runtime</scope>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>
<!-- https://mvnrepository.com/artifact/log4j/log4j -->
<dependency>
<groupId>log4j</groupId>
<artifactId>log4j</artifactId>
<version>1.2.17</version>
</dependency>
<!-- https://mvnrepository.com/artifact/org.apache.commons/commons-io -->
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-io</artifactId>
<version>1.3.2</version>
</dependency>
</dependencies>
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-dependencies</artifactId>
<version>Finchley.SR2</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
特别声明,springcloud的版本需要跟springboot匹配;
2.配置信息
代码如下:
server:
port: 80
spring:
application:
name: api-gateway
security:
user:
name: cmd
password: 123456
datasource:
driver-class-name: oracle.jdbc.OracleDriver
url: jdbc:oracle:thin:@ip:1526/user
username: root
password: 123456
redis:
host: ip
port: 6379
password: 123456
jedis:
pool:
max-active: 8
max-wait: -1
max-idle: 500
min-idle: 0
lettuce:
shutdown-timeout: 0
cloud:
gateway:
discovery:
locator:
enable:true
httpclient:
websocket:
max-frame-payload-length: 1024*1024*10
routes:
- id: gateway-service
uri: http://ip:8080
predicates:
- After=2018-12-25T14:33:47.789+08:00
mybatis-plus:
configuration:
log-impl: org.apache.ibatis.logging.stdout.StdOutImpl
mapper-locations: classpath:mapper/*.xml
3.启动类
@EnableDiscoveryClient
@SpringBootApplication
@MapperScan(basePackages= {"com.cmd.mapper"})
@EnableConfigurationProperties({EnvironmentPro.class})
public class GatewayApplication
{
public static void main( String[] args )
{
SpringApplication.run(GatewayApplication.class, args);
}
}
操作完就可启动查看gateway配置是否成功
三、晋级gateway
1.与数据库连用实现动态路由
1.1 定义路由对象
package com.cmd.sys;
import java.util.ArrayList;
import java.util.List;
/**
* @author cmd
*路由对象
*/
public class GatewayRouteDefinition {
/*** 路由的Id ***/
private String id;
/*** 路由断言集合配置 ***/
private List<GatewayPredicateDefinition> predicates = new ArrayList<>();
/*** 路由过滤器集合配置 ***/
private List<GatewayFilterDefinition> filters = new ArrayList<>();
/*** 路由规则转发的目标uri ***/
private String uri;
/*** 路由执行的顺序 ***/
private int order = 0;
public String getId() {
return id;
}
public void setId(String id) {
this.id = id;
}
public List<GatewayPredicateDefinition> getPredicates() {
return predicates;
}
public void setPredicates(List<GatewayPredicateDefinition> predicates) {
this.predicates = predicates;
}
public List<GatewayFilterDefinition> getFilters() {
return filters;
}
public void setFilters(List<GatewayFilterDefinition> filters) {
this.filters = filters;
}
public String getUri() {
return uri;
}
public void setUri(String uri) {
this.uri = uri;
}
public int getOrder() {
return order;
}
public void setOrder(int order) {
this.order = order;
}
}
1.2 定义断言对象
@Data
public class GatewayPredicateDefinition {
//断言对应的Name
private String name;
//配置的断言规则
private Map<String, String> args = new LinkedHashMap<>();
}
1.3 定义路由模型
@Data
public class GatewayFilterDefinition {
//Filter Name
private String name;
//对应的路由规则
private Map<String, String> args = new LinkedHashMap<>();
}
1.4 业务处理
package com.cmd.sys;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.cloud.gateway.event.RefreshRoutesEvent;
import org.springframework.cloud.gateway.route.RouteDefinition;
import org.springframework.cloud.gateway.route.RouteDefinitionWriter;
import org.springframework.cloud.gateway.support.NotFoundException;
import org.springframework.context.ApplicationEventPublisher;
import org.springframework.context.ApplicationEventPublisherAware;
import org.springframework.http.ResponseEntity;
import org.springframework.stereotype.Service;
import reactor.core.publisher.Mono;
@Service
public class DynamicRouteService implements ApplicationEventPublisherAware{
@Autowired
private RouteDefinitionWriter routeDefinitionWriter;
private ApplicationEventPublisher publisher;
@Override
public void setApplicationEventPublisher(ApplicationEventPublisher applicationEventPublisher) {
this.publisher = applicationEventPublisher;
}
/**
* 增加路由
*
*/
public String add(RouteDefinition definition) {
routeDefinitionWriter.save(Mono.just(definition)).subscribe();
this.publisher.publishEvent(new RefreshRoutesEvent(this));
return "添加成功!";
}
/**
* 更新路由
*
*/
public String update(RouteDefinition definition) {
try {
// delete(definition.getId());
} catch (Exception e) {
return "update fail,not find route routeId: "+definition.getId();
}
try {
routeDefinitionWriter.save(Mono.just(definition)).subscribe();
this.publisher.publishEvent(new RefreshRoutesEvent(this));
return "路由修改成功!";
} catch (Exception e) {
return "更新失败!";
}
}
/**
* 删除路由
*
*/
public Mono<ResponseEntity<Object>> delete(String id) {
return this.routeDefinitionWriter.delete(Mono.just(id)).then(Mono.defer(() -> {
return Mono.just(ResponseEntity.ok().build());
})).onErrorResume((t) -> {
return t instanceof NotFoundException;
}, (t) -> {
return Mono.just(ResponseEntity.notFound().build());
});
}
}
1.4 控制层
package com.cmd.sys;
import java.net.URI;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.cloud.gateway.filter.FilterDefinition;
import org.springframework.cloud.gateway.handler.predicate.PredicateDefinition;
import org.springframework.cloud.gateway.route.RouteDefinition;
import org.springframework.cloud.gateway.route.RouteDefinitionLocator;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.DeleteMapping;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.util.UriComponentsBuilder;
import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;
@RestController
@RequestMapping("/route")
public class DynamicRouteController {
@Autowired
private RouteDefinitionLocator routeDefinitionLocator;//读取路由的配置信息
@Autowired
private DynamicRouteService dynamicRouteService;
/**
* 获取网关所有的路由信息
*
*/
@GetMapping("/routes")
public Flux<RouteDefinition> getRouteDefinitions(){
return routeDefinitionLocator.getRouteDefinitions();
}
//增加路由
@GetMapping("/add")
public String add() {
RouteDefinition definition = new RouteDefinition();
System.out.println("111");
definition.setId("111");
URI uri = null;
uri = URI.create("url");
definition.setUri(uri);
List<PredicateDefinition> pdList=new ArrayList<PredicateDefinition>();
PredicateDefinition pre=new PredicateDefinition();
pre.setName("After");
Map<String, String> map=new HashMap<String, String>();
map.put("args", "2018-12-25T14:33:47.789+08:00");
pre.setArgs(map);
pdList.add(pre);
definition.setPredicates(pdList);
String flag = "fail";
try {
flag = this.dynamicRouteService.add(definition);
} catch (Exception e) {
e.printStackTrace();
}
return flag;
}
//更新路由
@PostMapping("/update")
public String update(@RequestBody GatewayRouteDefinition gwdefinition) {
RouteDefinition definition = assembleRouteDefinition(gwdefinition);
return this.dynamicRouteService.update(definition);
}
//删除路由
@DeleteMapping("/routes/{id}")
public Mono<ResponseEntity<Object>> delete(@PathVariable String id) {
try {
return this.dynamicRouteService.delete(id);
}catch (Exception e){
e.printStackTrace();
}
return null;
}
//把前端传递的参数转换成路由对象
private RouteDefinition assembleRouteDefinition(GatewayRouteDefinition gwdefinition) {
RouteDefinition definition = new RouteDefinition();
definition.setId(gwdefinition.getId());
definition.setOrder(gwdefinition.getOrder());
//设置断言
List<PredicateDefinition> pdList=new ArrayList<PredicateDefinition>();
List<GatewayPredicateDefinition> gatewayPredicateDefinitionList=gwdefinition.getPredicates();
for (GatewayPredicateDefinition gpDefinition: gatewayPredicateDefinitionList) {
PredicateDefinition predicate = new PredicateDefinition();
predicate.setArgs(gpDefinition.getArgs());
predicate.setName(gpDefinition.getName());
pdList.add(predicate);
}
definition.setPredicates(pdList);
//设置过滤器
List<FilterDefinition> filters = new ArrayList();
List<GatewayFilterDefinition> gatewayFilters = gwdefinition.getFilters();
for(GatewayFilterDefinition filterDefinition : gatewayFilters){
FilterDefinition filter = new FilterDefinition();
filter.setName(filterDefinition.getName());
filter.setArgs(filterDefinition.getArgs());
filters.add(filter);
}
definition.setFilters(filters);
URI uri = null;
if(gwdefinition.getUri().startsWith("http")){
uri = UriComponentsBuilder.fromHttpUrl(gwdefinition.getUri()).build().toUri();
}else{
uri = URI.create(gwdefinition.getUri());
}
definition.setUri(uri);
return definition;
}
}
2.禁用Security
禁用网关的登录认证,添加如下代码即可
package com.cmd.sys;
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;
/**
* @author cmd
*gateway登录认证
*/
@Configuration
@EnableWebFluxSecurity
public class WebSecurityConfig {
@Bean
SecurityWebFilterChain webFluxSecurityFilterChain(ServerHttpSecurity http) throws Exception {
http.authorizeExchange()
.anyExchange()
.permitAll();
// 一些配置
http
.csrf().disable()
.httpBasic().disable()
.logout().disable()
.formLogin().disable();
return http.build();
}
}
3.拦截器
抛弃动态路由,在拦截器中实现接口url的跳转,即强制更改要跳转的url请求,代码如下:
package com.cmd.sys;
import java.net.URI;
import java.nio.CharBuffer;
import java.nio.charset.StandardCharsets;
import java.util.Collections;
import java.util.List;
import java.util.Objects;
import java.util.concurrent.atomic.AtomicReference;
import java.util.stream.Collectors;
import java.util.stream.Stream;
import javax.annotation.Resource;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.cloud.client.discovery.DiscoveryClient;
import org.springframework.cloud.gateway.filter.GatewayFilterChain;
import org.springframework.cloud.gateway.filter.GlobalFilter;
import org.springframework.cloud.gateway.filter.NettyWriteResponseFilter;
import org.springframework.cloud.gateway.route.Route;
import org.springframework.cloud.gateway.support.BodyInserterContext;
import org.springframework.cloud.gateway.support.ServerWebExchangeUtils;
import org.springframework.core.Ordered;
import org.springframework.core.ResolvableType;
import org.springframework.core.io.buffer.DataBuffer;
import org.springframework.core.io.buffer.DataBufferFactory;
import org.springframework.core.io.buffer.DataBufferUtils;
import org.springframework.core.io.buffer.NettyDataBufferFactory;
import org.springframework.data.repository.query.parser.Part;
import org.springframework.http.HttpHeaders;
import org.springframework.http.MediaType;
import org.springframework.http.ReactiveHttpOutputMessage;
import org.springframework.http.codec.HttpMessageReader;
import org.springframework.http.codec.multipart.FormFieldPart;
import org.springframework.http.server.reactive.ServerHttpRequest;
import org.springframework.http.server.reactive.ServerHttpRequestDecorator;
import org.springframework.http.server.reactive.ServerHttpResponse;
import org.springframework.stereotype.Component;
import org.springframework.util.MultiValueMap;
import org.springframework.web.reactive.function.BodyInserter;
import org.springframework.web.reactive.function.BodyInserters;
import org.springframework.web.reactive.function.server.HandlerStrategies;
import org.springframework.web.reactive.function.server.ServerRequest;
import org.springframework.web.server.ServerWebExchange;
import org.springframework.web.util.UriComponentsBuilder;
import com.cmd.bean.Interface;
import com.cmd.service.InterfaceService;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
import io.netty.buffer.ByteBufAllocator;
import lombok.extern.slf4j.Slf4j;
import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;
@Slf4j
@Component("requestGlobalFilter")
public class RequestGlobalFilter implements GlobalFilter,Ordered {
@Resource
private DiscoveryClient disClient;
@Autowired
private InterfaceService interfaceService;
@Autowired
private ObjectMapper objectMapper;
@Autowired
private EnvironmentPro environmentPro;
private static final List<HttpMessageReader<?>> MESSAGE_READERS = HandlerStrategies.withDefaults().messageReaders();
@Override
public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
ServerHttpRequest req = exchange.getRequest();
ServerHttpResponse res = exchange.getResponse();
//自定义配置文件中添加url,为测试使用
String enurl=environmentPro.getUrl();
// String bodyStr = resolveBodyFromRequest(req);
HttpHeaders headers = req.getHeaders();
// logtrace(exchange, bodyStr);
//獲取請求頭中id信息
String id = headers.getFirst("id");
//随便写了个规则来改变路由地址
if(Objects.equals(id,"1")){
//获取域名后的path
String rawPath = req.getURI().getRawPath();
URI uri = UriComponentsBuilder.fromHttpUrl(enurl+"/test/getId").build().toUri();
//重新封装request对象
ServerHttpRequest request = req.mutate().uri(uri).build();
Route oldroute = exchange.getAttribute(ServerWebExchangeUtils.GATEWAY_ROUTE_ATTR);
//从新设置Route地址
Route route =
Route.async().asyncPredicate(oldroute.getPredicate()).filters(oldroute.getFilters()).id(oldroute.getId())
.order(oldroute.getOrder()).uri(uri).build();
URI mergedUrl = UriComponentsBuilder.fromUri(uri)
.scheme(uri.getScheme())
.host(uri.getHost())
.port("8080")
.build()
.toUri();
//NettyRoutingFilter 最终会从GATEWAY_REQUEST_URL_ATTR 取出uri对象进行http请求,所以这里需要将新的对象覆盖进去
exchange.getAttributes().put(ServerWebExchangeUtils.GATEWAY_REQUEST_URL_ATTR,route);
exchange.getAttributes().put(ServerWebExchangeUtils.GATEWAY_ROUTE_ATTR,route);
return chain.filter(exchange.mutate().request(request).build()).then(Mono.fromRunnable(() -> {
//请求完成回调方法 可以再此完成计算请求耗时等操作
}));
}
else if(Objects.equals(id,"3")) {
//从数据库拉去url
Interface inter = interfaceService.getInterface("ffff-1618987519204-10772443-0157");
String url = inter.getUrl();
//获取域名后的path
String rawPath = req.getURI().getRawPath();
URI uri = UriComponentsBuilder.fromHttpUrl(url).build().toUri();
// //重新封装request对象
// ServerHttpRequest request = req.mutate().uri(uri).build();
Route oldroute = exchange.getAttribute(ServerWebExchangeUtils.GATEWAY_ROUTE_ATTR);
//从新设置Route地址
Route route =
Route.async().asyncPredicate(oldroute.getPredicate()).filters(oldroute.getFilters()).id(oldroute.getId())
.order(oldroute.getOrder()).uri(uri).build();
URI mergedUrl = UriComponentsBuilder.fromUri(uri)
.scheme(uri.getScheme())
.host(uri.getHost())
.port("8080")
.build()
.toUri();
String bodyStr = resolveBodyFromRequest(req);
DataBuffer bodyDataBuffer = stringBuffer(bodyStr);
byte[] bytes = new byte[bodyDataBuffer.readableByteCount()];
bodyDataBuffer.read(bytes);
String bodyString = new String(bytes, StandardCharsets.UTF_8);
logtrace(exchange, bodyString);
exchange.getAttributes().put("POST_BODY", bodyString);
DataBufferUtils.release(bodyDataBuffer);
Flux<DataBuffer> cachedFlux = Flux.defer(() -> {
DataBuffer buffer = exchange.getResponse().bufferFactory()
.wrap(bytes);
return Mono.just(buffer);
});
ServerHttpRequest request = new ServerHttpRequestDecorator(req) {
@Override
public Flux<DataBuffer> getBody() {
return cachedFlux;
}
@Override
public URI getURI() {
return uri;
}
};
//NettyRoutingFilter 最终会从GATEWAY_REQUEST_URL_ATTR 取出uri对象进行http请求,所以这里需要将新的对象覆盖进去
exchange.getAttributes().put(ServerWebExchangeUtils.GATEWAY_REQUEST_URL_ATTR,route);
exchange.getAttributes().put(ServerWebExchangeUtils.GATEWAY_ROUTE_ATTR,route);
return chain.filter(exchange.mutate().request(request).build()).then(Mono.fromRunnable(() -> {
//请求完成回调方法 可以再此完成计算请求耗时等操作
}));
}else if(Objects.equals(id,"4")) {
//从redis中获取url数据
String url = interfaceService.getRedisInter("Url");
//获取域名后的path
String rawPath = req.getURI().getRawPath();
URI uri = UriComponentsBuilder.fromHttpUrl(url).build().toUri();
// //重新封装request对象
// ServerHttpRequest request = req.mutate().uri(uri).build();
Route oldroute = exchange.getAttribute(ServerWebExchangeUtils.GATEWAY_ROUTE_ATTR);
//从新设置Route地址
Route route =
Route.async().asyncPredicate(oldroute.getPredicate()).filters(oldroute.getFilters()).id(oldroute.getId())
.order(oldroute.getOrder()).uri(uri).build();
URI mergedUrl = UriComponentsBuilder.fromUri(uri)
.scheme(uri.getScheme())
.host(uri.getHost())
.port("8080")
.build()
.toUri();
String bodyStr = resolveBodyFromRequest(req);
DataBuffer bodyDataBuffer = stringBuffer(bodyStr);
byte[] bytes = new byte[bodyDataBuffer.readableByteCount()];
bodyDataBuffer.read(bytes);
String bodyString = new String(bytes, StandardCharsets.UTF_8);
logtrace(exchange, bodyString);
exchange.getAttributes().put("POST_BODY", bodyString);
DataBufferUtils.release(bodyDataBuffer);
Flux<DataBuffer> cachedFlux = Flux.defer(() -> {
DataBuffer buffer = exchange.getResponse().bufferFactory()
.wrap(bytes);
return Mono.just(buffer);
});
ServerHttpRequest request = new ServerHttpRequestDecorator(req) {
@Override
public Flux<DataBuffer> getBody() {
return cachedFlux;
}
@Override
public URI getURI() {
return uri;
}
};
//NettyRoutingFilter 最终会从GATEWAY_REQUEST_URL_ATTR 取出uri对象进行http请求,所以这里需要将新的对象覆盖进去
exchange.getAttributes().put(ServerWebExchangeUtils.GATEWAY_REQUEST_URL_ATTR,uri);
exchange.getAttributes().put(ServerWebExchangeUtils.GATEWAY_ROUTE_ATTR,route);
return chain.filter(exchange.mutate().request(request).build()).then(Mono.fromRunnable(() -> {
//请求完成回调方法 可以再此完成计算请求耗时等操作
}));
}else if(Objects.equals(id,"5")) {
//base64
String url = enurl+"/test/gets";
//获取域名后的path
String rawPath = req.getURI().getRawPath();
URI uri = UriComponentsBuilder.fromHttpUrl(url).build().toUri();
// //重新封装request对象
// ServerHttpRequest request = req.mutate().uri(uri).build();
Route oldroute = exchange.getAttribute(ServerWebExchangeUtils.GATEWAY_ROUTE_ATTR);
//从新设置Route地址
Route route =
Route.async().asyncPredicate(oldroute.getPredicate()).filters(oldroute.getFilters()).id(oldroute.getId())
.order(oldroute.getOrder()).uri(uri).build();
URI routeUri = route.getUri();
URI mergedUrl = UriComponentsBuilder.fromUri(uri)
.scheme(routeUri.getScheme())
.host(routeUri.getHost())
.port("8080")
.build()
.toUri();
String bodyStr = resolveBodyFromRequest(req);
DataBuffer bodyDataBuffer = stringBuffer(bodyStr);
byte[] bytes = new byte[bodyDataBuffer.readableByteCount()];
bodyDataBuffer.read(bytes);
String bodyString = new String(bytes, StandardCharsets.UTF_8);
logtrace(exchange, bodyString);
exchange.getAttributes().put("POST_BODY", bodyString);
DataBufferUtils.release(bodyDataBuffer);
Flux<DataBuffer> cachedFlux = Flux.defer(() -> {
DataBuffer buffer = exchange.getResponse().bufferFactory()
.wrap(bytes);
return Mono.just(buffer);
});
ServerHttpRequest request = new ServerHttpRequestDecorator(req) {
@Override
public Flux<DataBuffer> getBody() {
return cachedFlux;
}
@Override
public URI getURI() {
return uri;
}
};
exchange.getAttributes().put(ServerWebExchangeUtils.GATEWAY_REQUEST_URL_ATTR,uri);
exchange.getAttributes().put(ServerWebExchangeUtils.GATEWAY_ROUTE_ATTR,route);
return chain.filter(exchange.mutate().request(request).build()).then(Mono.fromRunnable(() -> {
//请求完成回调方法 可以再此完成计算请求耗时等操作
}));
}else if(Objects.equals(id,"6")) {
//从redis中获取url数据
String url = enurl+"/test/download";
//获取域名后的path
String rawPath = req.getURI().getRawPath();
URI uri = UriComponentsBuilder.fromHttpUrl(url).build().toUri();
// //重新封装request对象
// ServerHttpRequest request = req.mutate().uri(uri).build();
Route oldroute = exchange.getAttribute(ServerWebExchangeUtils.GATEWAY_ROUTE_ATTR);
//从新设置Route地址
Route route =
Route.async().asyncPredicate(oldroute.getPredicate()).filters(oldroute.getFilters()).id(oldroute.getId())
.order(oldroute.getOrder()).uri(uri).build();
URI mergedUrl = UriComponentsBuilder.fromUri(uri)
.scheme(uri.getScheme())
.host(uri.getHost())
.port("8080")
.build()
.toUri();
ServerHttpRequest request = new ServerHttpRequestDecorator(req) {
@Override
public URI getURI() {
return uri;
}
};
exchange.getAttributes().put(ServerWebExchangeUtils.GATEWAY_REQUEST_URL_ATTR,uri);
exchange.getAttributes().put(ServerWebExchangeUtils.GATEWAY_ROUTE_ATTR,route);
return chain.filter(exchange.mutate().request(request).build()).then(Mono.fromRunnable(() -> {
//请求完成回调方法 可以再此完成计算请求耗时等操作
}));
}
else if(Objects.equals(id,"7")){
//MultipartFile
String url = enurl+"/test/upload";
//获取域名后的path
String rawPath = req.getURI().getRawPath();
URI uri = UriComponentsBuilder.fromHttpUrl(url).build().toUri();
// //重新封装request对象
// ServerHttpRequest request = req.mutate().uri(uri).build();
Route oldroute = exchange.getAttribute(ServerWebExchangeUtils.GATEWAY_ROUTE_ATTR);
//从新设置Route地址
Route route =
Route.async().asyncPredicate(oldroute.getPredicate()).filters(oldroute.getFilters()).id(oldroute.getId())
.order(oldroute.getOrder()).uri(uri).build();
URI mergedUrl = UriComponentsBuilder.fromUri(uri)
.scheme(uri.getScheme())
.host(uri.getHost())
.port("8080")
.build()
.toUri();
ServerHttpRequest request = new ServerHttpRequestDecorator(req) {
@Override
public URI getURI() {
return uri;
}
};
exchange.getAttributes().put(ServerWebExchangeUtils.GATEWAY_REQUEST_URL_ATTR,uri);
exchange.getAttributes().put(ServerWebExchangeUtils.GATEWAY_ROUTE_ATTR,route);
return chain.filter(exchange.mutate().request(request).build()).then(Mono.fromRunnable(() -> {
//请求完成回调方法 可以再此完成计算请求耗时等操作
}));
}
// else{
// //模拟调用B服务 B服务无法掉通
System.out.println("模拟调用B服务 B服务无法调通");
// }
return chain.filter(exchange);
}
@SuppressWarnings("unchecked")
private Mono<Void> cacheBody(ServerWebExchange exchange, GatewayFilterChain chain, GatewayContext gatewayContext) {
final HttpHeaders headers = exchange.getRequest().getHeaders();
if (headers.getContentLength() == 0) {
return chain.filter(exchange);
}
final ResolvableType resolvableType;
if (MediaType.MULTIPART_FORM_DATA.isCompatibleWith(headers.getContentType())) {
resolvableType = ResolvableType.forClassWithGenerics(MultiValueMap.class, String.class, Part.class);
} else {
resolvableType = ResolvableType.forClass(String.class);
}
return MESSAGE_READERS.stream().filter(reader -> reader.canRead(resolvableType, exchange.getRequest().getHeaders().getContentType())).findFirst()
.orElseThrow(() -> new IllegalStateException("no suitable HttpMessageReader.")).readMono(resolvableType, exchange.getRequest(), Collections.emptyMap()).flatMap(resolvedBody -> {
if (resolvedBody instanceof MultiValueMap) {
final Part partInfo = (Part) ((MultiValueMap) resolvedBody).getFirst("info");
if (partInfo instanceof FormFieldPart) {
gatewayContext.setRequestBody(((FormFieldPart) partInfo).value());
}
} else {
gatewayContext.setRequestBody((String) resolvedBody);
}
return chain.filter(exchange);
});
}
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() {
//WRITE_RESPONSE_FILTER 之前执行
return NettyWriteResponseFilter.WRITE_RESPONSE_FILTER_ORDER - 1;
}
/**
* 日志信息
*
* @param exchange
* @param param 请求参数
*/
private void logtrace(ServerWebExchange exchange, String param) {
ServerHttpRequest serverHttpRequest = exchange.getRequest();
String path = serverHttpRequest.getURI().getPath();
String method = serverHttpRequest.getMethodValue();
String headers = serverHttpRequest.getHeaders().entrySet()
.stream()
.map(entry -> " " + entry.getKey() + ": [" + String.join(";", entry.getValue()) + "]")
.collect(Collectors.joining("\n"));
log.info("\n" + "---------------- ---------------- ---------------->>\n" +
"HttpMethod : {}\n" +
"Uri : {}\n" +
"Param : {}\n" +
"Headers : \n" +
"{}\n" +
"\"<<---------------- ---------------- ----------------"
, method, path, param, headers);
}
private Mono<Void> fileRequest(ServerWebExchange exchange, GatewayFilterChain chain){
return DataBufferUtils.join(exchange.getRequest().getBody())
.flatMap(dataBuffer -> {
byte[] bytes = new byte[dataBuffer.readableByteCount()];
dataBuffer.read(bytes);
DataBufferUtils.release(dataBuffer);
Flux<DataBuffer> cachedFlux = Flux.defer(() -> {
DataBuffer buffer = exchange.getResponse().bufferFactory().wrap(bytes);
DataBufferUtils.retain(buffer);
return Mono.just(buffer);
});
ServerHttpRequest mutatedRequest = new ServerHttpRequestDecorator(exchange.getRequest()) {
@Override
public Flux<DataBuffer> getBody() {
return cachedFlux;
}
};
ServerWebExchange mutatedExchange = exchange.mutate().request(mutatedRequest).build();
return ServerRequest.create(mutatedExchange, MESSAGE_READERS)
.bodyToMono(byte[].class)
.then(chain.filter(mutatedExchange));
});
}
private Mono<Void> processRequest(ServerWebExchange exchange, GatewayFilterChain chain) {
ServerRequest serverRequest =ServerRequest.create(exchange, MESSAGE_READERS);
DataBufferFactory bufferFactory = exchange.getResponse().bufferFactory();
Mono<String> rawBody = serverRequest.bodyToMono(String.class).map(s -> s);
BodyInserter<Mono<String>, ReactiveHttpOutputMessage> bodyInserter = BodyInserters.fromPublisher(rawBody, String.class);
HttpHeaders tempHeaders = new HttpHeaders();
tempHeaders.putAll(exchange.getRequest().getHeaders());
tempHeaders.remove(HttpHeaders.CONTENT_LENGTH);
MyCachedBodyOutputMessage outputMessage = new MyCachedBodyOutputMessage(exchange, tempHeaders);
return bodyInserter.insert(outputMessage, new BodyInserterContext()).then(Mono.defer(() -> {
Flux<DataBuffer> body = outputMessage.getBody();
DataBufferHolder holder = new DataBufferHolder();
body.subscribe(dataBuffer -> {
int len = dataBuffer.readableByteCount();
holder.length = len;
byte[] bytes = new byte[len];
dataBuffer.read(bytes);
DataBufferUtils.release(dataBuffer);
String text = new String(bytes, StandardCharsets.UTF_8);
//读取jsonc串
JsonNode jsonNode = readNode(text);
//直接内存
DataBuffer data = bufferFactory.allocateBuffer();
//把数据写到直接内存中
data.write(jsonNode.toString().getBytes(StandardCharsets.UTF_8));
holder.dataBuffer = data;
});
ServerHttpRequestDecorator requestDecorator = new ServerHttpRequestDecorator(exchange.getRequest()) {
@Override
public HttpHeaders getHeaders() {
long contentLength = tempHeaders.getContentLength();
HttpHeaders httpHeaders = new HttpHeaders();
httpHeaders.putAll(super.getHeaders());
if (contentLength > 0) {
httpHeaders.setContentLength(contentLength);
} else {
httpHeaders.set(HttpHeaders.TRANSFER_ENCODING, "chunked");
}
return httpHeaders;
}
@Override
public Flux<DataBuffer> getBody() {
return Flux.just(holder.dataBuffer);
}
};
return chain.filter(exchange.mutate().request(requestDecorator).build());
}));
}
//json读取格式
private JsonNode readNode(String in) {
try {
return objectMapper.readTree(in);
} catch (Exception e) {
throw new IllegalStateException(e);
}
}
class DataBufferHolder {
DataBuffer dataBuffer;
int length;
}
}
上述代码多重复,自行优化
四.常见错误
1.Only one connection receive subscriber allowed
这种错误,一般pom中版本不对,另一种就是拦截器中代码出错,可以详细检查就可解决,特别注意exchage中的attributes。
2.如果调用的接口为https的,直接更改拦截器中ServerHttpRequest为ServerHttpsRequest。
总结
常用的springcloud gateway 还是跟注册中心连用,将接口服务注册到注册中心,gateway直接调用注册服务接口。有空可以看看gateway源码,毕竟源码才是进阶的根本,一起搬砖哟。