自从新项目引入Gateway做网关服务以来,感觉一直未平静过,每天都会发生新的问题,特此记录。
1. 入门坑
搭建时,怎么启动都是各种报错,我只是简简单单引入几个包而已,后来发现springcloud 2.x版本Gateway使用的是webFlux,需排除相关spring-boot-starter-web的包,而后便正常启动了!
2. 研究网关predicates、filters配置
各项配置比较完善,感受到了nginx的能力,因为它默认提供了很多过滤器,熟悉的同学根据名字大概就能理解其功能了。
3. 各种报错问题解决
2019-08-15 17:02:41,176|reactor-http-epoll-2|ERROR|o.s.b.a.w.r.error.AbstractErrorWebExceptionHandler|122|[b6e56c5d] 500 Server Error for HTTP POST "/pay-app/app/pointAccount/balance?userId=3"
java.lang.IllegalStateException: Only one connection receive subscriber allowed.
at reactor.netty.channel.FluxReceive.startReceiver(FluxReceive.java:272)
at reactor.netty.channel.FluxReceive.subscribe(FluxReceive.java:122)
at reactor.core.publisher.FluxMap.subscribe(FluxMap.java:62)
at reactor.netty.ByteBufFlux.subscribe(ByteBufFlux.java:314)
at reactor.core.publisher.FluxPeek.subscribe(FluxPeek.java:83)
at reactor.netty.ByteBufFlux.subscribe(ByteBufFlux.java:314)
at reactor.core.publisher.FluxMap.subscribe(FluxMap.java:62)
at reactor.core.publisher.FluxMap.subscribe(FluxMap.java:62)
at reactor.core.publisher.Flux.subscribe(Flux.java:7921)
at reactor.netty.channel.MonoSendMany.subscribe(MonoSendMany.java:81)
at reactor.core.publisher.MonoIgnoreThen$ThenIgnoreMain.drain(MonoIgnoreThen.java:153)
at reactor.core.publisher.MonoIgnoreThen.subscribe(MonoIgnoreThen.java:56)
at reactor.core.publisher.Mono.subscribe(Mono.java:3848)
at reactor.netty.NettyOutbound.subscribe(NettyOutbound.java:305)
at reactor.core.publisher.MonoSource.subscribe(MonoSource.java:51)
at reactor.core.publisher.MonoDefer.subscribe(MonoDefer.java:52)
at reactor.netty.http.client.HttpClientConnect$HttpIOHandlerObserver.onStateChange(HttpClientConnect.java:441)
at reactor.netty.ReactorNetty$CompositeConnectionObserver.onStateChange(ReactorNetty.java:470)
at reactor.netty.resources.PooledConnectionProvider$DisposableAcquire.run(PooledConnectionProvider.java:563)
at reactor.netty.resources.PooledConnectionProvider$DisposableAcquire.operationComplete(PooledConnectionProvider.java:650)
at io.netty.util.concurrent.DefaultPromise.notifyListener0(DefaultPromise.java:502)
at io.netty.util.concurrent.DefaultPromise.notifyListenersNow(DefaultPromise.java:476)
at io.netty.util.concurrent.DefaultPromise.notifyListeners(DefaultPromise.java:415)
at io.netty.util.concurrent.DefaultPromise.addListener(DefaultPromise.java:152)
at io.netty.util.concurrent.DefaultPromise.addListener(DefaultPromise.java:33)
at reactor.netty.resources.PooledConnectionProvider.disposableAcquire(PooledConnectionProvider.java:208)
at reactor.netty.resources.PooledConnectionProvider.lambda$acquire$2(PooledConnectionProvider.java:162)
at reactor.core.publisher.MonoCreate.subscribe(MonoCreate.java:57)
at reactor.netty.http.client.HttpClientConnect$MonoHttpConnect.lambda$subscribe$0(HttpClientConnect.java:329)
at reactor.core.publisher.MonoCreate.subscribe(MonoCreate.java:57)
at reactor.core.publisher.Mono.subscribe(Mono.java:3848)
2019-08-16 16:23:04,109|reactor-http-client-epoll-9|ERROR|o.s.b.a.w.r.error.DefaultErrorWebExceptionHandler|218|Failed to handle request [GET http://192.168.200.51:8080/aggregation-app/webjars/springfox-swagger-ui/swagger-ui-bundle.js?v=2.9.2]
java.lang.NullPointerException: null
at java.util.concurrent.ConcurrentHashMap.putVal(ConcurrentHashMap.java:1011)
at java.util.concurrent.ConcurrentHashMap.put(ConcurrentHashMap.java:1006)
at org.springframework.cloud.gateway.filter.NettyRoutingFilter.lambda$filter$3(NettyRoutingFilter.java:117)
at reactor.core.publisher.FluxPeek$PeekSubscriber.onNext(FluxPeek.java:177)
at reactor.core.publisher.FluxMap$MapSubscriber.onNext(FluxMap.java:108)
at reactor.core.publisher.FluxRetryPredicate$RetryPredicateSubscriber.onNext(FluxRetryPredicate.java:81)
at reactor.core.publisher.MonoCreate$DefaultMonoSink.success(MonoCreate.java:146)
at reactor.ipc.netty.channel.PooledClientContextHandler.fireContextActive(PooledClientContextHandler.java:87)
at reactor.ipc.netty.http.client.HttpClientOperations.onInboundNext(HttpClientOperations.java:584)
at reactor.ipc.netty.channel.ChannelOperationsHandler.channelRead(ChannelOperationsHandler.java:138)
at io.netty.channel.AbstractChannelHandlerContext.invokeChannelRead(AbstractChannelHandlerContext.java:362)
at io.netty.channel.AbstractChannelHandlerContext.invokeChannelRead(AbstractChannelHandlerContext.java:348)
at io.netty.channel.AbstractChannelHandlerContext.fireChannelRead(AbstractChannelHandlerContext.java:340)
at io.netty.channel.CombinedChannelDuplexHandler$DelegatingChannelHandlerContext.fireChannelRead(CombinedChannelDuplexHandler.java:438)
at io.netty.handler.codec.ByteToMessageDecoder.fireChannelRead(ByteToMessageDecoder.java:310)
at io.netty.handler.codec.ByteToMessageDecoder.channelRead(ByteToMessageDecoder.java:284)
at io.netty.channel.CombinedChannelDuplexHandler.channelRead(CombinedChannelDuplexHandler.java:253)
at io.netty.channel.AbstractChannelHandlerContext.invokeChannelRead(AbstractChannelHandlerContext.java:362)
at io.netty.channel.AbstractChannelHandlerContext.invokeChannelRead(AbstractChannelHandlerContext.java:348)
以下报错是因为requestHeader中缺少content-type字段导致的:
2019-08-21 16:41:36,895|reactor-http-server-epoll-7|ERROR|o.s.b.a.w.r.error.DefaultErrorWebExceptionHandler|218|Failed to handle request [POST http://192.168.200.51:8080/aggregation-app/admin/systemNotice/listPage?pageNum=1&pageSize=10]
java.lang.UnsupportedOperationException: null
at java.util.Collections$UnmodifiableMap.put(Collections.java:1457)
at org.springframework.http.HttpHeaders.set(HttpHeaders.java:1456)
at org.springframework.http.HttpHeaders.setContentType(HttpHeaders.java:854)
at org.springframework.http.codec.EncoderHttpMessageWriter.updateContentType(EncoderHttpMessageWriter.java:133)
at org.springframework.http.codec.EncoderHttpMessageWriter.write(EncoderHttpMessageWriter.java:102)
at org.springframework.web.reactive.function.BodyInserters.lambda$null$9(BodyInserters.java:322)
at java.util.Optional.map(Optional.java:215)
at org.springframework.web.reactive.function.BodyInserters.lambda$bodyInserterFor$12(BodyInserters.java:314)
at com.dzjk.innovation.gateway.filter.JwtCheck2GatewayFilterFactory.just2(JwtCheck2GatewayFilterFactory.java:152)
at com.dzjk.innovation.gateway.filter.JwtCheck2GatewayFilterFactory.lambda$apply$0(JwtCheck2GatewayFilterFactory.java:120)
at org.springframework.cloud.gateway.filter.OrderedGatewayFilter.filter(OrderedGatewayFilter.java:44)
at org.springframework.cloud.gateway.handler.FilteringWebHandler$DefaultGatewayFilterChain.lambda$filter$0(FilteringWebHandler.java:115)
at reactor.core.publisher.MonoDefer.subscribe(MonoDefer.java:45)
// 解决办法:exchange取出来的headers是固定长度不支持操作,new一个新的放进去
HttpHeaders headers = new HttpHeaders();
headers.addAll(exchange.getRequest().getHeaders());
CachedBodyOutputMessage outputMessage = new CachedBodyOutputMessage(exchange, headers);
019-08-21 11:28:25,013|reactor-http-server-epoll-8|ERROR|o.s.b.a.w.r.error.DefaultErrorWebExceptionHandler|218|Failed to handle request [POST http://adminweb-dev.csfl.com/aggregation-app/admin/platformInfo/save]
com.google.gson.JsonSyntaxException: com.google.gson.stream.MalformedJsonException: Unterminated string at line 1 column 295 path $.financingRecord
at com.google.gson.internal.Streams.parse(Streams.java:60)
at com.google.gson.JsonParser.parse(JsonParser.java:84)
at com.google.gson.JsonParser.parse(JsonParser.java:59)
at com.google.gson.JsonParser.parse(JsonParser.java:45)
at com.dzjk.innovation.gateway.filter.JwtCheckGatewayFilterFactory.rebuildBodyStr(JwtCheckGatewayFilterFactory.java:239)
at com.dzjk.innovation.gateway.filter.JwtCheckGatewayFilterFactory.addUserId(JwtCheckGatewayFilterFactory.java:173)
at com.dzjk.innovation.gateway.filter.JwtCheckGatewayFilterFactory.lambda$apply$0(JwtCheckGatewayFilterFactory.java:110)
at org.springframework.cloud.gateway.filter.OrderedGatewayFilter.filter(OrderedGatewayFilter.java:44)
at org.springframework.cloud.gateway.handler.FilteringWebHandler$DefaultGatewayFilterChain.lambda$filter$0(FilteringWebHandler.java:115)
at reactor.core.publisher.MonoDefer.subscribe(MonoDefer.java:45)
关于参数被截断丢失的问题,最终还是参照了org.springframework.cloud.gateway.filter.factory.rewrite.ModifyRequestBodyGatewayFilterFactory 此类的写法,最终解决问题。
起先我也是参照网上gateway修改请求体的写法,是因为没看懂 ModifyRequestBodyGatewayFilterFactory这类,后来又仔细学习了一下,其实核心就是 ModifyRequestBodyGatewayFilterFactory.Config 类,其他的逻辑都会处理好。
看不懂这个类的主要原因还是对webflux不了解。
public static class Config {
private Class inClass = String.class; // 入参解析为此对象
private Class outClass = String.class; // 出参数解析为此对象
private MediaType mediaType;
private GetUserId userId;
@Deprecated
private Map inHints; // 我没用到
@Deprecated
private Map outHints; // 我没用到
// 重写函数,修改请求体就是通过此实现类进行重写
private RewriteFunction rewriteFunction = new RewriteFunction() {
@Override
public Publisher apply(ServerWebExchange exchange, String bodyStr) {
// 修改请求体逻辑
String res = rebuildBodyStr(bodyStr, contentType, userId);
return Mono.just(res);
}
};
}
public final void complete(O v) {
int state = this.state;
for (; ; ) {
if (state == FUSED_EMPTY) {
setValue(v);
STATE.lazySet(this, FUSED_READY);
Subscriber super O> a = actual;
a.onNext(v);
if (this.state != CANCELLED) {
a.onComplete();
}
return;
}
// 有请求没值,直接了,故逻辑直接跳过了
// if state is >= HAS_CANCELLED or bit zero is set (*_HAS_VALUE) case, return
if ((state & ~HAS_REQUEST_NO_VALUE) != 0) {
return;
}
if (state == HAS_REQUEST_NO_VALUE) {
STATE.lazySet(this, HAS_REQUEST_HAS_VALUE);
Subscriber super O> a = actual;
a.onNext(v);
if (this.state != CANCELLED) {
a.onComplete();
}
return;
}
setValue(v);
if (STATE.compareAndSet(this, NO_REQUEST_NO_VALUE, NO_REQUEST_HAS_VALUE)) {
return;
}
state = this.state;
if (state == CANCELLED) {
value = null;
return;
}
}
}