前提
- 本文編寫的時(shí)候使用的
Spring Cloud Gateway
版本為當(dāng)時(shí)最新的版本Greenwich.SR1
抠藕。
我們?cè)谑褂?code>Spring Cloud Gateway的時(shí)候,注意到過濾器(包括GatewayFilter
尘奏、GlobalFilter
和過濾器鏈GatewayFilterChain
)买雾,都依賴到ServerWebExchange
:
public interface GlobalFilter {
Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain);
}
public interface GatewayFilter extends ShortcutConfigurable {
Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain);
}
public interface GatewayFilterChain {
Mono<Void> filter(ServerWebExchange exchange);
}
復(fù)制代碼
這里的設(shè)計(jì)和Servlet
中的Filter
是相似的,當(dāng)前過濾器可以決定是否執(zhí)行下一個(gè)過濾器的邏輯胡岔,由GatewayFilterChain#filter()
是否被調(diào)用來決定。而ServerWebExchange
就相當(dāng)于當(dāng)前請(qǐng)求和響應(yīng)的上下文枷餐。ServerWebExchange
實(shí)例不單存儲(chǔ)了Request
和Response
對(duì)象靶瘸,還提供了一些擴(kuò)展方法,如果想實(shí)現(xiàn)改造請(qǐng)求參數(shù)或者響應(yīng)參數(shù),就必須深入了解ServerWebExchange
怨咪。
理解ServerWebExchange
先看ServerWebExchange
的注釋:
Contract for an HTTP request-response interaction. Provides access to the HTTP request and response and also exposes additional server-side processing related properties and features such as request attributes.
翻譯一下大概是:
ServerWebExchange是一個(gè)HTTP請(qǐng)求-響應(yīng)交互的契約屋剑。提供對(duì)HTTP請(qǐng)求和響應(yīng)的訪問,并公開額外的服務(wù)器端處理相關(guān)屬性和特性诗眨,如請(qǐng)求屬性唉匾。
其實(shí),ServerWebExchange
命名為服務(wù)網(wǎng)絡(luò)交換器匠楚,存放著重要的請(qǐng)求-響應(yīng)屬性肄鸽、請(qǐng)求實(shí)例和響應(yīng)實(shí)例等等,有點(diǎn)像Context
的角色油啤。
ServerWebExchange接口
ServerWebExchange
接口的所有方法:
public interface ServerWebExchange {
// 日志前綴屬性的KEY,值為org.springframework.web.server.ServerWebExchange.LOG_ID
// 可以理解為 attributes.set("org.springframework.web.server.ServerWebExchange.LOG_ID","日志前綴的具體值");
// 作用是打印日志的時(shí)候會(huì)拼接這個(gè)KEY對(duì)飲的前綴值蟀苛,默認(rèn)值為""
String LOG_ID_ATTRIBUTE = ServerWebExchange.class.getName() + ".LOG_ID";
String getLogPrefix();
// 獲取ServerHttpRequest對(duì)象
ServerHttpRequest getRequest();
// 獲取ServerHttpResponse對(duì)象
ServerHttpResponse getResponse();
// 返回當(dāng)前exchange的請(qǐng)求屬性益咬,返回結(jié)果是一個(gè)可變的Map
Map<String, Object> getAttributes();
// 根據(jù)KEY獲取請(qǐng)求屬性
@Nullable
default <T> T getAttribute(String name) {
return (T) getAttributes().get(name);
}
// 根據(jù)KEY獲取請(qǐng)求屬性,做了非空判斷
@SuppressWarnings("unchecked")
default <T> T getRequiredAttribute(String name) {
T value = getAttribute(name);
Assert.notNull(value, () -> "Required attribute '" + name + "' is missing");
return value;
}
// 根據(jù)KEY獲取請(qǐng)求屬性帜平,需要提供默認(rèn)值
@SuppressWarnings("unchecked")
default <T> T getAttributeOrDefault(String name, T defaultValue) {
return (T) getAttributes().getOrDefault(name, defaultValue);
}
// 返回當(dāng)前請(qǐng)求的網(wǎng)絡(luò)會(huì)話
Mono<WebSession> getSession();
// 返回當(dāng)前請(qǐng)求的認(rèn)證用戶幽告,如果存在的話
<T extends Principal> Mono<T> getPrincipal();
// 返回請(qǐng)求的表單數(shù)據(jù)或者一個(gè)空的Map,只有Content-Type為application/x-www-form-urlencoded的時(shí)候這個(gè)方法才會(huì)返回一個(gè)非空的Map -- 這個(gè)一般是表單數(shù)據(jù)提交用到
Mono<MultiValueMap<String, String>> getFormData();
// 返回multipart請(qǐng)求的part數(shù)據(jù)或者一個(gè)空的Map裆甩,只有Content-Type為multipart/form-data的時(shí)候這個(gè)方法才會(huì)返回一個(gè)非空的Map -- 這個(gè)一般是文件上傳用到
Mono<MultiValueMap<String, Part>> getMultipartData();
// 返回Spring的上下文
@Nullable
ApplicationContext getApplicationContext();
// 這幾個(gè)方法和lastModified屬性相關(guān)
boolean isNotModified();
boolean checkNotModified(Instant lastModified);
boolean checkNotModified(String etag);
boolean checkNotModified(@Nullable String etag, Instant lastModified);
// URL轉(zhuǎn)換
String transformUrl(String url);
// URL轉(zhuǎn)換映射
void addUrlTransformer(Function<String, String> transformer);
// 注意這個(gè)方法冗锁,方法名是:改變,這個(gè)是修改ServerWebExchange屬性的方法嗤栓,返回的是一個(gè)Builder實(shí)例冻河,Builder是ServerWebExchange的內(nèi)部類
default Builder mutate() {
return new DefaultServerWebExchangeBuilder(this);
}
interface Builder {
// 覆蓋ServerHttpRequest
Builder request(Consumer<ServerHttpRequest.Builder> requestBuilderConsumer);
Builder request(ServerHttpRequest request);
// 覆蓋ServerHttpResponse
Builder response(ServerHttpResponse response);
// 覆蓋當(dāng)前請(qǐng)求的認(rèn)證用戶
Builder principal(Mono<Principal> principalMono);
// 構(gòu)建新的ServerWebExchange實(shí)例
ServerWebExchange build();
}
}
復(fù)制代碼
注意到ServerWebExchange#mutate()
方法,ServerWebExchange
實(shí)例可以理解為不可變實(shí)例茉帅,如果我們想要修改它叨叙,需要通過mutate()
方法生成一個(gè)新的實(shí)例,例如這樣:
public class CustomGlobalFilter implements GlobalFilter {
@Override
public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
ServerHttpRequest request = exchange.getRequest();
// 這里可以修改ServerHttpRequest實(shí)例
ServerHttpRequest newRequest = ...
ServerHttpResponse response = exchange.getResponse();
// 這里可以修改ServerHttpResponse實(shí)例
ServerHttpResponse newResponse = ...
// 構(gòu)建新的ServerWebExchange實(shí)例
ServerWebExchange newExchange = exchange.mutate().request(newRequest).response(newResponse).build();
return chain.filter(newExchange);
}
}
復(fù)制代碼
ServerHttpRequest接口
ServerHttpRequest
實(shí)例是用于承載請(qǐng)求相關(guān)的屬性和請(qǐng)求體堪澎,Spring Cloud Gateway
中底層使用Netty
處理網(wǎng)絡(luò)請(qǐng)求擂错,通過追溯源碼,可以從ReactorHttpHandlerAdapter
中得知ServerWebExchange
實(shí)例中持有的ServerHttpRequest
實(shí)例的具體實(shí)現(xiàn)是ReactorServerHttpRequest
樱蛤。之所以列出這些實(shí)例之間的關(guān)系钮呀,是因?yàn)檫@樣比較容易理清一些隱含的問題藻三,例如:
-
ReactorServerHttpRequest
的父類AbstractServerHttpRequest
中初始化內(nèi)部屬性headers的時(shí)候把請(qǐng)求的HTTP頭部封裝為只讀的實(shí)例:
public AbstractServerHttpRequest(URI uri, @Nullable String contextPath, HttpHeaders headers) {
this.uri = uri;
this.path = RequestPath.parse(uri, contextPath);
this.headers = HttpHeaders.readOnlyHttpHeaders(headers);
}
// HttpHeaders類中的readOnlyHttpHeaders方法衡楞,其中ReadOnlyHttpHeaders屏蔽了所有修改請(qǐng)求頭的方法朦乏,直接拋出UnsupportedOperationException
public static HttpHeaders readOnlyHttpHeaders(HttpHeaders headers) {
Assert.notNull(headers, "HttpHeaders must not be null");
if (headers instanceof ReadOnlyHttpHeaders) {
return headers;
}
else {
return new ReadOnlyHttpHeaders(headers);
}
}
復(fù)制代碼
所以不能直接從ServerHttpRequest
實(shí)例中直接獲取請(qǐng)求頭HttpHeaders
實(shí)例并且進(jìn)行修改隙疚。
ServerHttpRequest
接口如下:
public interface HttpMessage {
// 獲取請(qǐng)求頭卿城,目前的實(shí)現(xiàn)中返回的是ReadOnlyHttpHeaders實(shí)例菊匿,只讀
HttpHeaders getHeaders();
}
public interface ReactiveHttpInputMessage extends HttpMessage {
// 返回請(qǐng)求體的Flux封裝
Flux<DataBuffer> getBody();
}
public interface HttpRequest extends HttpMessage {
// 返回HTTP請(qǐng)求方法狭莱,解析為HttpMethod實(shí)例
@Nullable
default HttpMethod getMethod() {
return HttpMethod.resolve(getMethodValue());
}
// 返回HTTP請(qǐng)求方法奥裸,字符串
String getMethodValue();
// 請(qǐng)求的URI
URI getURI();
}
public interface ServerHttpRequest extends HttpRequest, ReactiveHttpInputMessage {
// 連接的唯一標(biāo)識(shí)或者用于日志處理標(biāo)識(shí)
String getId();
// 獲取請(qǐng)求路徑,封裝為RequestPath對(duì)象
RequestPath getPath();
// 返回查詢參數(shù)证杭,是只讀的MultiValueMap實(shí)例
MultiValueMap<String, String> getQueryParams();
// 返回Cookie集合田度,是只讀的MultiValueMap實(shí)例
MultiValueMap<String, HttpCookie> getCookies();
// 遠(yuǎn)程服務(wù)器地址信息
@Nullable
default InetSocketAddress getRemoteAddress() {
return null;
}
// SSL會(huì)話實(shí)現(xiàn)的相關(guān)信息
@Nullable
default SslInfo getSslInfo() {
return null;
}
// 修改請(qǐng)求的方法,返回一個(gè)建造器實(shí)例Builder解愤,Builder是內(nèi)部類
default ServerHttpRequest.Builder mutate() {
return new DefaultServerHttpRequestBuilder(this);
}
interface Builder {
// 覆蓋請(qǐng)求方法
Builder method(HttpMethod httpMethod);
// 覆蓋請(qǐng)求的URI镇饺、請(qǐng)求路徑或者上下文,這三者相互有制約關(guān)系送讲,具體可以參考API注釋
Builder uri(URI uri);
Builder path(String path);
Builder contextPath(String contextPath);
// 覆蓋請(qǐng)求頭
Builder header(String key, String value);
Builder headers(Consumer<HttpHeaders> headersConsumer);
// 覆蓋SslInfo
Builder sslInfo(SslInfo sslInfo);
// 構(gòu)建一個(gè)新的ServerHttpRequest實(shí)例
ServerHttpRequest build();
}
}
復(fù)制代碼
如果要修改ServerHttpRequest
實(shí)例奸笤,那么需要這樣做:
ServerHttpRequest request = exchange.getRequest();
ServerHttpRequest newRequest = request.mutate().headers("key","value").path("/myPath").build();
復(fù)制代碼
這里最值得注意的是:ServerHttpRequest
或者說HttpMessage
接口提供的獲取請(qǐng)求頭方法HttpHeaders getHeaders();
返回結(jié)果是一個(gè)只讀的實(shí)例,具體是ReadOnlyHttpHeaders
類型哼鬓,這里提多一次监右,筆者寫這篇博文時(shí)候使用的Spring Cloud Gateway
版本為Greenwich.SR1
。
ServerHttpResponse接口
ServerHttpResponse
實(shí)例是用于承載響應(yīng)相關(guān)的屬性和響應(yīng)體异希,Spring Cloud Gateway
中底層使用Netty
處理網(wǎng)絡(luò)請(qǐng)求健盒,通過追溯源碼,可以從ReactorHttpHandlerAdapter
中得知ServerWebExchange
實(shí)例中持有的ServerHttpResponse
實(shí)例的具體實(shí)現(xiàn)是ReactorServerHttpResponse
称簿。之所以列出這些實(shí)例之間的關(guān)系扣癣,是因?yàn)檫@樣比較容易理清一些隱含的問題,例如:
// ReactorServerHttpResponse的父類
public AbstractServerHttpResponse(DataBufferFactory dataBufferFactory, HttpHeaders headers) {
Assert.notNull(dataBufferFactory, "DataBufferFactory must not be null");
Assert.notNull(headers, "HttpHeaders must not be null");
this.dataBufferFactory = dataBufferFactory;
this.headers = headers;
this.cookies = new LinkedMultiValueMap<>();
}
public ReactorServerHttpResponse(HttpServerResponse response, DataBufferFactory bufferFactory) {
super(bufferFactory, new HttpHeaders(new NettyHeadersAdapter(response.responseHeaders())));
Assert.notNull(response, "HttpServerResponse must not be null");
this.response = response;
}
復(fù)制代碼
可知ReactorServerHttpResponse
構(gòu)造函數(shù)初始化實(shí)例的時(shí)候憨降,存放響應(yīng)Header的是HttpHeaders
實(shí)例父虑,也就是響應(yīng)Header是可以直接修改的。
ServerHttpResponse
接口如下:
public interface HttpMessage {
// 獲取響應(yīng)Header授药,目前的實(shí)現(xiàn)中返回的是HttpHeaders實(shí)例士嚎,可以直接修改
HttpHeaders getHeaders();
}
public interface ReactiveHttpOutputMessage extends HttpMessage {
// 獲取DataBufferFactory實(shí)例,用于包裝或者生成數(shù)據(jù)緩沖區(qū)DataBuffer實(shí)例(創(chuàng)建響應(yīng)體)
DataBufferFactory bufferFactory();
// 注冊(cè)一個(gè)動(dòng)作烁焙,在HttpOutputMessage提交之前此動(dòng)作會(huì)進(jìn)行回調(diào)
void beforeCommit(Supplier<? extends Mono<Void>> action);
// 判斷HttpOutputMessage是否已經(jīng)提交
boolean isCommitted();
// 寫入消息體到HTTP協(xié)議層
Mono<Void> writeWith(Publisher<? extends DataBuffer> body);
// 寫入消息體到HTTP協(xié)議層并且刷新緩沖區(qū)
Mono<Void> writeAndFlushWith(Publisher<? extends Publisher<? extends DataBuffer>> body);
// 指明消息處理已經(jīng)結(jié)束航邢,一般在消息處理結(jié)束自動(dòng)調(diào)用此方法,多次調(diào)用不會(huì)產(chǎn)生副作用
Mono<Void> setComplete();
}
public interface ServerHttpResponse extends ReactiveHttpOutputMessage {
// 設(shè)置響應(yīng)狀態(tài)碼
boolean setStatusCode(@Nullable HttpStatus status);
// 獲取響應(yīng)狀態(tài)碼
@Nullable
HttpStatus getStatusCode();
// 獲取響應(yīng)Cookie骄蝇,封裝為MultiValueMap實(shí)例膳殷,可以修改
MultiValueMap<String, ResponseCookie> getCookies();
// 添加響應(yīng)Cookie
void addCookie(ResponseCookie cookie);
}
復(fù)制代碼
這里可以看到除了響應(yīng)體比較難修改之外,其他的屬性都是可變的九火。
ServerWebExchangeUtils和上下文屬性
ServerWebExchangeUtils
里面存放了很多靜態(tài)公有的字符串KEY值(這些字符串KEY的實(shí)際值是org.springframework.cloud.gateway.support.ServerWebExchangeUtils.
+ 下面任意的靜態(tài)公有KEY)赚窃,這些字符串KEY值一般是用于ServerWebExchange
的屬性(Attribute
,見上文的ServerWebExchange#getAttributes()
方法)的KEY岔激,這些屬性值都是有特殊的含義勒极,在使用過濾器的時(shí)候如果時(shí)機(jī)適當(dāng)可以直接取出來使用,下面逐個(gè)分析虑鼎。
-
PRESERVE_HOST_HEADER_ATTRIBUTE
:是否保存Host屬性辱匿,值是布爾值類型键痛,寫入位置是PreserveHostHeaderGatewayFilterFactory
,使用的位置是NettyRoutingFilter
匾七,作用是如果設(shè)置為true絮短,HTTP請(qǐng)求頭中的Host屬性會(huì)寫到底層Reactor-Netty的請(qǐng)求Header屬性中。 -
CLIENT_RESPONSE_ATTR
:保存底層Reactor-Netty的響應(yīng)對(duì)象昨忆,類型是reactor.netty.http.client.HttpClientResponse
丁频。 -
CLIENT_RESPONSE_CONN_ATTR
:保存底層Reactor-Netty的連接對(duì)象,類型是reactor.netty.Connection
邑贴。 -
URI_TEMPLATE_VARIABLES_ATTRIBUTE
:PathRoutePredicateFactory
解析路徑參數(shù)完成之后席里,把解析完成后的占位符KEY-路徑Path映射存放在ServerWebExchange
的屬性中,KEY就是URI_TEMPLATE_VARIABLES_ATTRIBUTE
拢驾。 -
CLIENT_RESPONSE_HEADER_NAMES
:保存底層Reactor-Netty的響應(yīng)Header的名稱集合奖磁。 -
GATEWAY_ROUTE_ATTR
:用于存放RoutePredicateHandlerMapping
中匹配出來的具體的路由(org.springframework.cloud.gateway.route.Route
)實(shí)例,通過這個(gè)路由實(shí)例可以得知當(dāng)前請(qǐng)求會(huì)路由到下游哪個(gè)服務(wù)繁疤。 -
GATEWAY_REQUEST_URL_ATTR
:java.net.URI
類型的實(shí)例署穗,這個(gè)實(shí)例代表直接請(qǐng)求或者負(fù)載均衡處理之后需要請(qǐng)求到下游服務(wù)的真實(shí)URI。 -
GATEWAY_ORIGINAL_REQUEST_URL_ATTR
:java.net.URI
類型的實(shí)例嵌洼,需要重寫請(qǐng)求URI的時(shí)候,保存原始的請(qǐng)求URI封恰。 -
GATEWAY_HANDLER_MAPPER_ATTR
:保存當(dāng)前使用的HandlerMapping
具體實(shí)例的類型簡(jiǎn)稱(一般是字符串"RoutePredicateHandlerMapping")麻养。 -
GATEWAY_SCHEME_PREFIX_ATTR
:確定目標(biāo)路由URI中如果存在schemeSpecificPart屬性,則保存該URI的scheme在此屬性中诺舔,路由URI會(huì)被重新構(gòu)造鳖昌,見RouteToRequestUrlFilter
。 -
GATEWAY_PREDICATE_ROUTE_ATTR
:用于存放RoutePredicateHandlerMapping
中匹配出來的具體的路由(org.springframework.cloud.gateway.route.Route
)實(shí)例的ID低飒。 -
WEIGHT_ATTR
:實(shí)驗(yàn)性功能(此版本還不建議在正式版本使用)存放分組權(quán)重相關(guān)屬性许昨,見WeightCalculatorWebFilter
。 -
ORIGINAL_RESPONSE_CONTENT_TYPE_ATTR
:存放響應(yīng)Header中的ContentType的值褥赊。 -
HYSTRIX_EXECUTION_EXCEPTION_ATTR
:Throwable
的實(shí)例糕档,存放的是Hystrix執(zhí)行異常時(shí)候的異常實(shí)例,見HystrixGatewayFilterFactory
拌喉。 -
GATEWAY_ALREADY_ROUTED_ATTR
:布爾值速那,用于判斷是否已經(jīng)進(jìn)行了路由,見NettyRoutingFilter
尿背。 -
GATEWAY_ALREADY_PREFIXED_ATTR
:布爾值端仰,用于判斷請(qǐng)求路徑是否被添加了前置部分,見PrefixPathGatewayFilterFactory
田藐。
ServerWebExchangeUtils
提供的上下文屬性用于Spring Cloud Gateway
的ServerWebExchange
組件處理請(qǐng)求和響應(yīng)的時(shí)候荔烧,內(nèi)部一些重要實(shí)例或者標(biāo)識(shí)屬性的安全傳輸和使用吱七,使用它們可能存在一定的風(fēng)險(xiǎn),因?yàn)闆]有人可以確定在版本升級(jí)之后鹤竭,原有的屬性KEY或者VALUE是否會(huì)發(fā)生改變踊餐,如果評(píng)估過風(fēng)險(xiǎn)或者規(guī)避了風(fēng)險(xiǎn)之后,可以安心使用诺擅。例如我們?cè)谧稣?qǐng)求和響應(yīng)日志(類似Nginx的Access Log)的時(shí)候市袖,可以依賴到GATEWAY_ROUTE_ATTR
,因?yàn)槲覀円蛴÷酚傻哪繕?biāo)信息烁涌。舉個(gè)簡(jiǎn)單例子:
@Slf4j
@Component
public class AccessLogFilter implements GlobalFilter {
@Override
public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
ServerHttpRequest request = exchange.getRequest();
String path = request.getPath().pathWithinApplication().value();
HttpMethod method = request.getMethod();
// 獲取路由的目標(biāo)URI
URI targetUri = exchange.getAttribute(ServerWebExchangeUtils.GATEWAY_REQUEST_URL_ATTR);
InetSocketAddress remoteAddress = request.getRemoteAddress();
return chain.filter(exchange.mutate().build()).then(Mono.fromRunnable(() -> {
ServerHttpResponse response = exchange.getResponse();
HttpStatus statusCode = response.getStatusCode();
log.info("請(qǐng)求路徑:{},客戶端遠(yuǎn)程IP地址:{},請(qǐng)求方法:{},目標(biāo)URI:{},響應(yīng)碼:{}",
path, remoteAddress, method, targetUri, statusCode);
}));
}
}
復(fù)制代碼
修改請(qǐng)求體
修改請(qǐng)求體是一個(gè)比較常見的需求苍碟。例如我們使用Spring Cloud Gateway
實(shí)現(xiàn)網(wǎng)關(guān)的時(shí)候,要實(shí)現(xiàn)一個(gè)功能:把存放在請(qǐng)求頭中的JWT解析后撮执,提取里面的用戶ID微峰,然后寫入到請(qǐng)求體中。我們簡(jiǎn)化這個(gè)場(chǎng)景抒钱,假設(shè)我們把userId明文存放在請(qǐng)求頭中的accessToken中蜓肆,請(qǐng)求體是一個(gè)JSON結(jié)構(gòu):
{
"serialNumber": "請(qǐng)求流水號(hào)",
"payload" : {
// ... 這里是有效載荷,存放具體的數(shù)據(jù)
}
}
復(fù)制代碼
我們需要提取accessToken谋币,也就是userId插入到請(qǐng)求體JSON中如下:
{
"userId": "用戶ID",
"serialNumber": "請(qǐng)求流水號(hào)",
"payload" : {
// ... 這里是有效載荷仗扬,存放具體的數(shù)據(jù)
}
}
復(fù)制代碼
這里為了簡(jiǎn)化設(shè)計(jì),用全局過濾器GlobalFilter
實(shí)現(xiàn)蕾额,實(shí)際需要結(jié)合具體場(chǎng)景考慮:
@Slf4j
@Component
public class ModifyRequestBodyGlobalFilter implements GlobalFilter {
private final DataBufferFactory dataBufferFactory = new NettyDataBufferFactory(ByteBufAllocator.DEFAULT);
@Autowired
private ObjectMapper objectMapper;
@Override
public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
ServerHttpRequest request = exchange.getRequest();
String accessToken = request.getHeaders().getFirst("accessToken");
if (!StringUtils.hasLength(accessToken)) {
throw new IllegalArgumentException("accessToken");
}
// 新建一個(gè)ServerHttpRequest裝飾器,覆蓋需要裝飾的方法
ServerHttpRequestDecorator decorator = new ServerHttpRequestDecorator(request) {
@Override
public Flux<DataBuffer> getBody() {
Flux<DataBuffer> body = super.getBody();
InputStreamHolder holder = new InputStreamHolder();
body.subscribe(buffer -> holder.inputStream = buffer.asInputStream());
if (null != holder.inputStream) {
try {
// 解析JSON的節(jié)點(diǎn)
JsonNode jsonNode = objectMapper.readTree(holder.inputStream);
Assert.isTrue(jsonNode instanceof ObjectNode, "JSON格式異常");
ObjectNode objectNode = (ObjectNode) jsonNode;
// JSON節(jié)點(diǎn)最外層寫入新的屬性
objectNode.put("userId", accessToken);
DataBuffer dataBuffer = dataBufferFactory.allocateBuffer();
String json = objectNode.toString();
log.info("最終的JSON數(shù)據(jù)為:{}", json);
dataBuffer.write(json.getBytes(StandardCharsets.UTF_8));
return Flux.just(dataBuffer);
} catch (Exception e) {
throw new IllegalStateException(e);
}
} else {
return super.getBody();
}
}
};
// 使用修改后的ServerHttpRequestDecorator重新生成一個(gè)新的ServerWebExchange
return chain.filter(exchange.mutate().request(decorator).build());
}
private class InputStreamHolder {
InputStream inputStream;
}
}
復(fù)制代碼
測(cè)試一下:
// HTTP
POST /order/json HTTP/1.1
Host: localhost:9090
Content-Type: application/json
accessToken: 10086
Accept: */*
Cache-Control: no-cache
Host: localhost:9090
accept-encoding: gzip, deflate
content-length: 94
Connection: keep-alive
cache-control: no-cache
{
"serialNumber": "請(qǐng)求流水號(hào)",
"payload": {
"name": "doge"
}
}
// 日志輸出
最終的JSON數(shù)據(jù)為:{"serialNumber":"請(qǐng)求流水號(hào)","payload":{"name":"doge"},"userId":"10086"}
復(fù)制代碼
最重要的是用到了ServerHttpRequest
裝飾器ServerHttpRequestDecorator
早芭,主要覆蓋對(duì)應(yīng)獲取請(qǐng)求體數(shù)據(jù)緩沖區(qū)的方法即可,至于怎么處理其他邏輯需要自行考慮诅蝶,這里只是做一個(gè)簡(jiǎn)單的示范退个。一般的代碼邏輯如下:
ServerHttpRequest request = exchange.getRequest();
ServerHttpRequestDecorator requestDecorator = new ServerHttpRequestDecorator(request) {
@Override
public Flux<DataBuffer> getBody() {
// 拿到承載原始請(qǐng)求體的Flux
Flux<DataBuffer> body = super.getBody();
// 這里通過自定義方式生成新的承載請(qǐng)求體的Flux
Flux<DataBuffer> newBody = ...
return newBody;
}
}
return chain.filter(exchange.mutate().request(requestDecorator).build());
復(fù)制代碼
修改響應(yīng)體
修改響應(yīng)體的需求也是比較常見的,具體的做法和修改請(qǐng)求體差不多调炬。例如我們想要實(shí)現(xiàn)下面的功能:第三方服務(wù)請(qǐng)求經(jīng)過網(wǎng)關(guān)语盈,原始報(bào)文是密文,我們需要在網(wǎng)關(guān)實(shí)現(xiàn)密文解密缰泡,然后把解密后的明文路由到下游服務(wù)刀荒,下游服務(wù)處理成功響應(yīng)明文,需要在網(wǎng)關(guān)把明文加密成密文再返回到第三方服務(wù)〖現(xiàn)在簡(jiǎn)化整個(gè)流程照棋,用AES加密算法,統(tǒng)一密碼為字符串"throwable"武翎,假設(shè)請(qǐng)求報(bào)文和響應(yīng)報(bào)文明文如下:
// 請(qǐng)求密文
{
"serialNumber": "請(qǐng)求流水號(hào)",
"payload" : "加密后的請(qǐng)求消息載荷"
}
// 請(qǐng)求明文(僅僅作為提示)
{
"serialNumber": "請(qǐng)求流水號(hào)",
"payload" : "{\"name:\":\"doge\"}"
}
// 響應(yīng)密文
{
"code": 200,
"message":"ok",
"payload" : "加密后的響應(yīng)消息載荷"
}
// 響應(yīng)明文(僅僅作為提示)
{
"code": 200,
"message":"ok",
"payload" : "{\"name:\":\"doge\",\"age\":26}"
}
復(fù)制代碼
為了方便一些加解密或者編碼解碼的實(shí)現(xiàn)烈炭,需要引入Apache
的commons-codec
類庫(kù):
<dependency>
<groupId>commons-codec</groupId>
<artifactId>commons-codec</artifactId>
<version>1.12</version>
</dependency>
復(fù)制代碼
這里定義一個(gè)全局過濾器專門處理加解密,實(shí)際上最好結(jié)合真實(shí)的場(chǎng)景決定是否適合全局過濾器宝恶,這里只是一個(gè)示例:
// AES加解密工具類
public enum AesUtils {
// 單例
X;
private static final String PASSWORD = "throwable";
private static final String KEY_ALGORITHM = "AES";
private static final String SECURE_RANDOM_ALGORITHM = "SHA1PRNG";
private static final String DEFAULT_CIPHER_ALGORITHM = "AES/ECB/PKCS5Padding";
public String encrypt(String content) {
try {
Cipher cipher = Cipher.getInstance(DEFAULT_CIPHER_ALGORITHM);
cipher.init(Cipher.ENCRYPT_MODE, provideSecretKey());
return Hex.encodeHexString(cipher.doFinal(content.getBytes(StandardCharsets.UTF_8)));
} catch (Exception e) {
throw new IllegalArgumentException(e);
}
}
public byte[] decrypt(String content) {
try {
Cipher cipher = Cipher.getInstance(DEFAULT_CIPHER_ALGORITHM);
cipher.init(Cipher.DECRYPT_MODE, provideSecretKey());
return cipher.doFinal(Hex.decodeHex(content));
} catch (Exception e) {
throw new IllegalArgumentException(e);
}
}
private SecretKey provideSecretKey() {
try {
KeyGenerator keyGen = KeyGenerator.getInstance(KEY_ALGORITHM);
SecureRandom secureRandom = SecureRandom.getInstance(SECURE_RANDOM_ALGORITHM);
secureRandom.setSeed(PASSWORD.getBytes(StandardCharsets.UTF_8));
keyGen.init(128, secureRandom);
return new SecretKeySpec(keyGen.generateKey().getEncoded(), KEY_ALGORITHM);
} catch (Exception e) {
throw new IllegalArgumentException(e);
}
}
}
// EncryptionGlobalFilter
@Slf4j
@Component
public class EncryptionGlobalFilter implements GlobalFilter, Ordered {
@Autowired
private ObjectMapper objectMapper;
@Override
public int getOrder() {
return -2;
}
@Override
public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
ServerHttpRequest request = exchange.getRequest();
ServerHttpResponse response = exchange.getResponse();
DataBufferFactory bufferFactory = exchange.getResponse().bufferFactory();
ServerHttpRequestDecorator requestDecorator = processRequest(request, bufferFactory);
ServerHttpResponseDecorator responseDecorator = processResponse(response, bufferFactory);
return chain.filter(exchange.mutate().request(requestDecorator).response(responseDecorator).build());
}
private ServerHttpRequestDecorator processRequest(ServerHttpRequest request, DataBufferFactory bufferFactory) {
Flux<DataBuffer> body = request.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);
JsonNode jsonNode = readNode(text);
JsonNode payload = jsonNode.get("payload");
String payloadText = payload.asText();
byte[] content = AesUtils.X.decrypt(payloadText);
String requestBody = new String(content, StandardCharsets.UTF_8);
log.info("修改請(qǐng)求體payload,修改前:{},修改后:{}", payloadText, requestBody);
rewritePayloadNode(requestBody, jsonNode);
DataBuffer data = bufferFactory.allocateBuffer();
data.write(jsonNode.toString().getBytes(StandardCharsets.UTF_8));
holder.dataBuffer = data;
});
HttpHeaders headers = new HttpHeaders();
headers.putAll(request.getHeaders());
headers.remove(HttpHeaders.CONTENT_LENGTH);
return new ServerHttpRequestDecorator(request) {
@Override
public HttpHeaders getHeaders() {
int contentLength = holder.length;
if (contentLength > 0) {
headers.setContentLength(contentLength);
} else {
headers.set(HttpHeaders.TRANSFER_ENCODING, "chunked");
}
return headers;
}
@Override
public Flux<DataBuffer> getBody() {
return Flux.just(holder.dataBuffer);
}
};
}
private ServerHttpResponseDecorator processResponse(ServerHttpResponse response, DataBufferFactory bufferFactory) {
return new ServerHttpResponseDecorator(response) {
@SuppressWarnings("unchecked")
@Override
public Mono<Void> writeWith(Publisher<? extends DataBuffer> body) {
if (body instanceof Flux) {
Flux<? extends DataBuffer> flux = (Flux<? extends DataBuffer>) body;
return super.writeWith(flux.map(buffer -> {
CharBuffer charBuffer = StandardCharsets.UTF_8.decode(buffer.asByteBuffer());
DataBufferUtils.release(buffer);
JsonNode jsonNode = readNode(charBuffer.toString());
JsonNode payload = jsonNode.get("payload");
String text = payload.toString();
String content = AesUtils.X.encrypt(text);
log.info("修改響應(yīng)體payload,修改前:{},修改后:{}", text, content);
setPayloadTextNode(content, jsonNode);
return bufferFactory.wrap(jsonNode.toString().getBytes(StandardCharsets.UTF_8));
}));
}
return super.writeWith(body);
}
};
}
private void rewritePayloadNode(String text, JsonNode root) {
try {
JsonNode node = objectMapper.readTree(text);
ObjectNode objectNode = (ObjectNode) root;
objectNode.set("payload", node);
} catch (Exception e) {
throw new IllegalStateException(e);
}
}
private void setPayloadTextNode(String text, JsonNode root) {
try {
ObjectNode objectNode = (ObjectNode) root;
objectNode.set("payload", new TextNode(text));
} catch (Exception e) {
throw new IllegalStateException(e);
}
}
private JsonNode readNode(String in) {
try {
return objectMapper.readTree(in);
} catch (Exception e) {
throw new IllegalStateException(e);
}
}
private class DataBufferHolder {
DataBuffer dataBuffer;
int length;
}
}
復(fù)制代碼
先準(zhǔn)備一份密文:
Map<String, Object> json = new HashMap<>(8);
json.put("serialNumber", "請(qǐng)求流水號(hào)");
String content = "{\"name\": \"doge\"}";
json.put("payload", AesUtils.X.encrypt(content));
System.out.println(new ObjectMapper().writeValueAsString(json));
// 輸出
{"serialNumber":"請(qǐng)求流水號(hào)","payload":"144e3dc734743f5709f1adf857bca473da683246fd612f86ac70edeb5f2d2729"}
復(fù)制代碼
模擬請(qǐng)求:
POST /order/json HTTP/1.1
Host: localhost:9090
accessToken: 10086
Content-Type: application/json
User-Agent: PostmanRuntime/7.13.0
Accept: */*
Cache-Control: no-cache
Postman-Token: bda07fc3-ea1a-478c-b4d7-754fe6f37200,634734d9-feed-4fc9-ba20-7618bd986e1c
Host: localhost:9090
cookie: customCookieName=customCookieValue
accept-encoding: gzip, deflate
content-length: 104
Connection: keep-alive
cache-control: no-cache
{
"serialNumber": "請(qǐng)求流水號(hào)",
"payload": "FE49xzR0P1cJ8a34V7ykc9poMkb9YS+GrHDt618tJyk="
}
// 響應(yīng)結(jié)果
{
"serialNumber": "請(qǐng)求流水號(hào)",
"payload": "oo/K1igg2t/S8EExkBVGWOfI1gAh5pBpZ0wyjNPW6e8=" # <--- 解密后:{"name":"doge","age":26}
}
復(fù)制代碼
遇到的問題:
- 必須實(shí)現(xiàn)
Ordered
接口符隙,返回一個(gè)小于-1的order值趴捅,這是因?yàn)?code>NettyWriteResponseFilter的order值為-1,我們需要覆蓋返回響應(yīng)體的邏輯霹疫,自定義的GlobalFilter
必須比NettyWriteResponseFilter
優(yōu)先執(zhí)行拱绑。 - 網(wǎng)關(guān)每次重啟之后,第一個(gè)請(qǐng)求總是無法從原始的
ServerHttpRequest
讀取到有效的Body丽蝎,準(zhǔn)確來說出現(xiàn)的現(xiàn)象是NettyRoutingFilter
調(diào)用ServerHttpRequest#getBody()
的時(shí)候獲取到一個(gè)空的對(duì)象猎拨,導(dǎo)致空指針;奇怪的是從第二個(gè)請(qǐng)求開始就能正常調(diào)用屠阻。筆者把Spring Cloud Gateway
的版本降低到Finchley.SR3
红省,Spring Boot
的版本降低到2.0.8.RELEASE
,問題不再出現(xiàn)国觉,初步確定是Spring Cloud Gateway
版本升級(jí)導(dǎo)致的兼容性問題或者是BUG吧恃。
最重要的是用到了ServerHttpResponse
裝飾器ServerHttpResponseDecorator
,主要覆蓋寫入響應(yīng)體數(shù)據(jù)緩沖區(qū)的部分麻诀,至于怎么處理其他邏輯需要自行考慮痕寓,這里只是做一個(gè)簡(jiǎn)單的示范。一般的代碼邏輯如下:
ServerHttpResponse response = exchange.getResponse();
ServerHttpResponseDecorator responseDecorator = new ServerHttpResponseDecorator(response) {
@Override
public Mono<Void> writeWith(Publisher<? extends DataBuffer> body) {
if (body instanceof Flux) {
Flux<? extends DataBuffer> flux = (Flux<? extends DataBuffer>) body;
return super.writeWith(flux.map(buffer -> {
// buffer就是原始的響應(yīng)數(shù)據(jù)的緩沖區(qū)
// 下面處理完畢之后返回新的響應(yīng)數(shù)據(jù)的緩沖區(qū)即可
return bufferFactory.wrap(...);
}));
}
return super.writeWith(body);
}
};
return chain.filter(exchange.mutate().response(responseDecorator).build());
復(fù)制代碼
請(qǐng)求體或者響應(yīng)體報(bào)文過大的問題
有熱心的同學(xué)告訴筆者蝇闭,如果請(qǐng)求報(bào)文過大或者響應(yīng)報(bào)文過大的時(shí)候呻率,前面兩節(jié)的修改請(qǐng)求和響應(yīng)報(bào)文的方法會(huì)出現(xiàn)問題,這里嘗試重現(xiàn)一下遇到的具體問題呻引。先把請(qǐng)求報(bào)文嘗試加長(zhǎng):
Map<String, Object> json = new HashMap<>(8);
json.put("serialNumber", "請(qǐng)求流水號(hào)");
StringBuilder builder = new StringBuilder();
for (int i = 0; i < 1000; i++) {
builder.append("doge");
}
String content = String.format("{\"name\": \"%s\"}", builder.toString());
json.put("payload", AesUtils.X.encrypt(content));
System.out.println(new ObjectMapper().writeValueAsString(json));
// 請(qǐng)求的JSON報(bào)文如下:
{
"serialNumber": "請(qǐng)求流水號(hào)",
"payload": "0Dcf2plFpESprKjkdqNHM8jjIArkd7XW5Ll2/qaBqv/I4yAK5He11uS5dv6mgar/yOMgCuR3tdbkuXb+poGq/8jjIArkd7XW5Ll2/qaBqv/I4yAK5He11uS5dv6mgar/yOMgCuR3tdbkuXb+poGq/8jjIArkd7XW5Ll2/qaBqv/I4yAK5He11uS5dv6mgar/yOMgCuR3tdbkuXb+poGq/8jjIArkd7XW5Ll2/qaBqv/I4yAK5He11uS5dv6mgar/yOMgCuR3tdbkuXb+poGq/8jjIArkd7XW5Ll2/qaBqv/I4yAK5He11uS5dv6mgar/yOMgCuR3tdbkuXb+poGq/8jjIArkd7XW5Ll2/qaBqv/I4yAK5He11uS5dv6mgar/yOMgCuR3tdbkuXb+poGq/8jjIArkd7XW5Ll2/qaBqv/I4yAK5He11uS5dv6mgar/yOMgCuR3tdbkuXb+poGq/8jjIArkd7XW5Ll2/qaBqv/I4yAK5He11uS5dv6mgar/yOMgCuR3tdbkuXb+poGq/8jjIArkd7XW5Ll2/qaBqv/I4yAK5He11uS5dv6mgar/yOMgCuR3tdbkuXb+poGq/8jjIArkd7XW5Ll2/qaBqv/I4yAK5He11uS5dv6mgar/yOMgCuR3tdbkuXb+poGq/8jjIArkd7XW5Ll2/qaBqv/I4yAK5He11uS5dv6mgar/yOMgCuR3tdbkuXb+poGq/8jjIArkd7XW5Ll2/qaBqv/I4yAK5He11uS5dv6mgar/yOMgCuR3tdbkuXb+poGq/8jjIArkd7XW5Ll2/qaBqv/I4yAK5He11uS5dv6mgar/yOMgCuR3tdbkuXb+poGq/8jjIArkd7XW5Ll2/qaBqv/I4yAK5He11uS5dv6mgar/yOMgCuR3tdbkuXb+poGq/8jjIArkd7XW5Ll2/qaBqv/I4yAK5He11uS5dv6mgar/yOMgCuR3tdbkuXb+poGq/8jjIArkd7XW5Ll2/qaBqv/I4yAK5He11uS5dv6mgar/yOMgCuR3tdbkuXb+poGq/8jjIArkd7XW5Ll2/qaBqv/I4yAK5He11uS5dv6mgar/yOMgCuR3tdbkuXb+poGq/8jjIArkd7XW5Ll2/qaBqv/I4yAK5He11uS5dv6mgar/yOMgCuR3tdbkuXb+poGq/8jjIArkd7XW5Ll2/qaBqv/I4yAK5He11uS5dv6mgar/yOMgCuR3tdbkuXb+poGq/8jjIArkd7XW5Ll2/qaBqv/I4yAK5He11uS5dv6mgar/yOMgCuR3tdbkuXb+poGq/8jjIArkd7XW5Ll2/qaBqv/I4yAK5He11uS5dv6mgar/yOMgCuR3tdbkuXb+poGq/8jjIArkd7XW5Ll2/qaBqv/I4yAK5He11uS5dv6mgar/yOMgCuR3tdbkuXb+poGq/8jjIArkd7XW5Ll2/qaBqv/I4yAK5He11uS5dv6mgar/yOMgCuR3tdbkuXb+poGq/8jjIArkd7XW5Ll2/qaBqv/I4yAK5He11uS5dv6mgar/yOMgCuR3tdbkuXb+poGq/8jjIArkd7XW5Ll2/qaBqv/I4yAK5He11uS5dv6mgar/yOMgCuR3tdbkuXb+poGq/8jjIArkd7XW5Ll2/qaBqv/I4yAK5He11uS5dv6mgar/yOMgCuR3tdbkuXb+poGq/8jjIArkd7XW5Ll2/qaBqv/I4yAK5He11uS5dv6mgar/yOMgCuR3tdbkuXb+poGq/8jjIArkd7XW5Ll2/qaBqv/I4yAK5He11uS5dv6mgar/yOMgCuR3tdbkuXb+poGq/8jjIArkd7XW5Ll2/qaBqv/I4yAK5He11uS5dv6mgar/yOMgCuR3tdbkuXb+poGq/8jjIArkd7XW5Ll2/qaBqv/I4yAK5He11uS5dv6mgar/yOMgCuR3tdbkuXb+poGq/8jjIArkd7XW5Ll2/qaBqv/I4yAK5He11uS5dv6mgar/yOMgCuR3tdbkuXb+poGq/8jjIArkd7XW5Ll2/qaBqv/I4yAK5He11uS5dv6mgar/yOMgCuR3tdbkuXb+poGq/8jjIArkd7XW5Ll2/qaBqv/I4yAK5He11uS5dv6mgar/yOMgCuR3tdbkuXb+poGq/8jjIArkd7XW5Ll2/qaBqv/I4yAK5He11uS5dv6mgar/yOMgCuR3tdbkuXb+poGq/8jjIArkd7XW5Ll2/qaBqv/I4yAK5He11uS5dv6mgar/yOMgCuR3tdbkuXb+poGq/8jjIArkd7XW5Ll2/qaBqv/I4yAK5He11uS5dv6mgar/yOMgCuR3tdbkuXb+poGq/8jjIArkd7XW5Ll2/qaBqv/I4yAK5He11uS5dv6mgar/yOMgCuR3tdbkuXb+poGq/8jjIArkd7XW5Ll2/qaBqv/I4yAK5He11uS5dv6mgar/yOMgCuR3tdbkuXb+poGq/8jjIArkd7XW5Ll2/qaBqv/I4yAK5He11uS5dv6mgar/yOMgCuR3tdbkuXb+poGq/8jjIArkd7XW5Ll2/qaBqv/I4yAK5He11uS5dv6mgar/yOMgCuR3tdbkuXb+poGq/8jjIArkd7XW5Ll2/qaBqv/I4yAK5He11uS5dv6mgar/yOMgCuR3tdbkuXb+poGq/8jjIArkd7XW5Ll2/qaBqv/I4yAK5He11uS5dv6mgar/yOMgCuR3tdbkuXb+poGq/8jjIArkd7XW5Ll2/qaBqv/I4yAK5He11uS5dv6mgar/yOMgCuR3tdbkuXb+poGq/8jjIArkd7XW5Ll2/qaBqv/I4yAK5He11uS5dv6mgar/yOMgCuR3tdbkuXb+poGq/8jjIArkd7XW5Ll2/qaBqv/I4yAK5He11uS5dv6mgar/yOMgCuR3tdbkuXb+poGq/8jjIArkd7XW5Ll2/qaBqv/I4yAK5He11uS5dv6mgar/yOMgCuR3tdbkuXb+poGq/8jjIArkd7XW5Ll2/qaBqv/I4yAK5He11uS5dv6mgar/yOMgCuR3tdbkuXb+poGq/8jjIArkd7XW5Ll2/qaBqv/I4yAK5He11uS5dv6mgar/yOMgCuR3tdbkuXb+poGq/8jjIArkd7XW5Ll2/qaBqv/I4yAK5He11uS5dv6mgar/yOMgCuR3tdbkuXb+poGq/8jjIArkd7XW5Ll2/qaBqv/I4yAK5He11uS5dv6mgar/yOMgCuR3tdbkuXb+poGq/8jjIArkd7XW5Ll2/qaBqv/I4yAK5He11uS5dv6mgar/yOMgCuR3tdbkuXb+poGq/8jjIArkd7XW5Ll2/qaBqv/I4yAK5He11uS5dv6mgar/yOMgCuR3tdbkuXb+poGq/8jjIArkd7XW5Ll2/qaBqv/I4yAK5He11uS5dv6mgar/yOMgCuR3tdbkuXb+poGq/8jjIArkd7XW5Ll2/qaBqv/I4yAK5He11uS5dv6mgar/yOMgCuR3tdbkuXb+poGq/8jjIArkd7XW5Ll2/qaBqv/I4yAK5He11uS5dv6mgar/yOMgCuR3tdbkuXb+poGq/8jjIArkd7XW5Ll2/qaBqv/I4yAK5He11uS5dv6mgar/yOMgCuR3tdbkuXb+poGq/8jjIArkd7XW5Ll2/qaBqv/I4yAK5He11uS5dv6mgar/yOMgCuR3tdbkuXb+poGq/8jjIArkd7XW5Ll2/qaBqv/I4yAK5He11uS5dv6mgar/yOMgCuR3tdbkuXb+poGq/8jjIArkd7XW5Ll2/qaBqv/I4yAK5He11uS5dv6mgar/yOMgCuR3tdbkuXb+poGq/8jjIArkd7XW5Ll2/qaBqv/I4yAK5He11uS5dv6mgar/yOMgCuR3tdbkuXb+poGq/8jjIArkd7XW5Ll2/qaBqv/I4yAK5He11uS5dv6mgar/yOMgCuR3tdbkuXb+poGq/8jjIArkd7XW5Ll2/qaBqv/I4yAK5He11uS5dv6mgar/yOMgCuR3tdbkuXb+poGq/8jjIArkd7XW5Ll2/qaBqv/I4yAK5He11uS5dv6mgar/yOMgCuR3tdbkuXb+poGq/8jjIArkd7XW5Ll2/qaBqv/I4yAK5He11uS5dv6mgar/yOMgCuR3tdbkuXb+poGq/8jjIArkd7XW5Ll2/qaBqv/I4yAK5He11uS5dv6mgar/yOMgCuR3tdbkuXb+poGq/8jjIArkd7XW5Ll2/qaBqv/I4yAK5He11uS5dv6mgar/yOMgCuR3tdbkuXb+poGq/8jjIArkd7XW5Ll2/qaBqv/I4yAK5He11uS5dv6mgar/yOMgCuR3tdbkuXb+poGq/8jjIArkd7XW5Ll2/qaBqv/I4yAK5He11uS5dv6mgar/yOMgCuR3tdbkuXb+poGq/8jjIArkd7XW5Ll2/qaBqv/I4yAK5He11uS5dv6mgar/yOMgCuR3tdbkuXb+poGq/8jjIArkd7XW5Ll2/qaBqv/I4yAK5He11uS5dv6mgar/yOMgCuR3tdbkuXb+poGq/8jjIArkd7XW5Ll2/qaBqv/I4yAK5He11uS5dv6mgar/yOMgCuR3tdbkuXb+poGq/8jjIArkd7XW5Ll2/qaBqv/I4yAK5He11uS5dv6mgar/yOMgCuR3tdbkuXb+poGq/8jjIArkd7XW5Ll2/qaBqv/I4yAK5He11uS5dv6mgar/yOMgCuR3tdbkuXb+poGq/8jjIArkd7XW5Ll2/qaBqv/I4yAK5He11uS5dv6mgar/yOMgCuR3tdbkuXb+poGq/8jjIArkd7XW5Ll2/qaBqv/I4yAK5He11uS5dv6mgar/yOMgCuR3tdbkuXb+poGq/8jjIArkd7XW5Ll2/qaBqv/I4yAK5He11uS5dv6mgar/yOMgCuR3tdbkuXb+poGq/8jjIArkd7XW5Ll2/qaBqv/I4yAK5He11uS5dv6mgar/yOMgCuR3tdbkuXb+poGq/8jjIArkd7XW5Ll2/qaBqv/I4yAK5He11uS5dv6mgar/yOMgCuR3tdbkuXb+poGq/8jjIArkd7XW5Ll2/qaBqv/I4yAK5He11uS5dv6mgar/yOMgCuR3tdbkuXb+poGq/8jjIArkd7XW5Ll2/qaBqv/I4yAK5He11uS5dv6mgar/yOMgCuR3tdbkuXb+poGq/8jjIArkd7XW5Ll2/qaBqv/I4yAK5He11uS5dv6mgar/yOMgCuR3tdbkuXb+poGq/8jjIArkd7XW5Ll2/qaBqv/I4yAK5He11uS5dv6mgar/yOMgCuR3tdbkuXb+poGq/8jjIArkd7XW5Ll2/qaBqv/I4yAK5He11uS5dv6mgar/yOMgCuR3tdbkuXb+poGq/zyJ4ipyLGvo5LX87d9oDAs="
}
復(fù)制代碼
用上面的請(qǐng)求報(bào)文發(fā)起請(qǐng)求筷凤,確實(shí)存在問題:
[圖片上傳中...(image-a54118-1564041069471-0)]
<figcaption style="display: block; text-align: center; font-size: 1rem; line-height: 1.6; color: rgb(144, 144, 144); margin-top: 2px;"></figcaption>
主要問題是:
- 請(qǐng)求體包數(shù)據(jù)裝成的
Flux<DataBuffer>
實(shí)例被訂閱之后,讀取到的字節(jié)數(shù)組的長(zhǎng)度被截?cái)嗔税撸峁┑脑颊?qǐng)求報(bào)文里面字符串長(zhǎng)度要大于1000,轉(zhuǎn)換成byte數(shù)組絕對(duì)要大于1000挪丢,但是上面的示例中只讀取到長(zhǎng)度為673的byte數(shù)組蹂风。 - 讀取到的字節(jié)數(shù)組被截?cái)嗪螅瑒t使用Jackson進(jìn)行反序列化的時(shí)候提示沒有讀取到字符串的EOF標(biāo)識(shí)乾蓬,導(dǎo)致反序列化失敗惠啄。
既然遇到了問題,就想辦法解決任内。首先第一步定位一下是什么原因撵渡,直覺告訴筆者:要開啟一下DEBUG日志進(jìn)行觀察,如果還沒有頭緒可能要跟蹤一下源碼死嗦。
開啟DEBUG日志級(jí)別之后做一次請(qǐng)求趋距,發(fā)現(xiàn)了一些可疑的日志信息:
2019-05-19 11:16:15.660 [reactor-http-nio-2] DEBUG reactor.ipc.netty.http.server.HttpServer - [id: 0xa9b527e5, L:/0:0:0:0:0:0:0:1:9090 - R:/0:0:0:0:0:0:0:1:58012] READ COMPLETE
2019-05-19 11:16:15.660 [reactor-http-nio-2] DEBUG reactor.ipc.netty.http.server.HttpServer - [id: 0xa9b527e5, L:/0:0:0:0:0:0:0:1:9090 ! R:/0:0:0:0:0:0:0:1:58012] INACTIVE
2019-05-19 11:16:15.660 [reactor-http-nio-3] DEBUG reactor.ipc.netty.http.server.HttpServer - [id: 0x5554e091, L:/0:0:0:0:0:0:0:1:9090 - R:/0:0:0:0:0:0:0:1:58013] READ: 1024B
+-------------------------------------------------+
| 0 1 2 3 4 5 6 7 8 9 a b c d e f |
+--------+-------------------------------------------------+----------------+
|00000000| 50 4f 53 54 20 2f 6f 72 64 65 72 2f 6a 73 6f 6e |POST /order/json|
|00000010| 20 48 54 54 50 2f 31 2e 31 0d 0a 61 63 63 65 73 | HTTP/1.1..acces|
|00000020| 73 54 6f 6b 65 6e 3a 20 31 30 30 38 36 0d 0a 43 |sToken: 10086..C|
|00000030| 6f 6e 74 65 6e 74 2d 54 79 70 65 3a 20 61 70 70 |ontent-Type: app|
|00000040| 6c 69 63 61 74 69 6f 6e 2f 6a 73 6f 6e 0d 0a 55 |lication/json..U|
|00000050| 73 65 72 2d 41 67 65 6e 74 3a 20 50 6f 73 74 6d |ser-Agent: Postm|
|00000060| 61 6e 52 75 6e 74 69 6d 65 2f 37 2e 31 33 2e 30 |anRuntime/7.13.0|
|00000070| 0d 0a 41 63 63 65 70 74 3a 20 2a 2f 2a 0d 0a 43 |..Accept: */*..C|
|00000080| 61 63 68 65 2d 43 6f 6e 74 72 6f 6c 3a 20 6e 6f |ache-Control: no|
|00000090| 2d 63 61 63 68 65 0d 0a 50 6f 73 74 6d 61 6e 2d |-cache..Postman-|
|000000a0| 54 6f 6b 65 6e 3a 20 31 31 32 30 38 64 35 39 2d |Token: 11208d59-|
|000000b0| 65 61 34 61 2d 34 62 39 63 2d 61 30 33 39 2d 30 |ea4a-4b9c-a039-0|
|000000c0| 30 65 36 64 38 61 30 65 33 65 66 0d 0a 48 6f 73 |0e6d8a0e3ef..Hos|
|000000d0| 74 3a 20 6c 6f 63 61 6c 68 6f 73 74 3a 39 30 39 |t: localhost:909|
|000000e0| 30 0d 0a 63 6f 6f 6b 69 65 3a 20 63 75 73 74 6f |0..cookie: custo|
|000000f0| 6d 43 6f 6f 6b 69 65 4e 61 6d 65 3d 63 75 73 74 |mCookieName=cust|
|00000100| 6f 6d 43 6f 6f 6b 69 65 56 61 6c 75 65 0d 0a 61 |omCookieValue..a|
|00000110| 63 63 65 70 74 2d 65 6e 63 6f 64 69 6e 67 3a 20 |ccept-encoding: |
|00000120| 67 7a 69 70 2c 20 64 65 66 6c 61 74 65 0d 0a 63 |gzip, deflate..c|
|00000130| 6f 6e 74 65 6e 74 2d 6c 65 6e 67 74 68 3a 20 35 |ontent-length: 5|
|00000140| 34 31 36 0d 0a 43 6f 6e 6e 65 63 74 69 6f 6e 3a |416..Connection:|
|00000150| 20 6b 65 65 70 2d 61 6c 69 76 65 0d 0a 0d 0a 7b | keep-alive....{|
|00000160| 0a 20 20 20 20 22 73 65 72 69 61 6c 4e 75 6d 62 |. "serialNumb|
|00000170| 65 72 22 3a 20 22 e8 af b7 e6 b1 82 e6 b5 81 e6 |er": "..........|
|00000180| b0 b4 e5 8f b7 22 2c 0a 20 20 20 20 22 70 61 79 |.....",. "pay|
|00000190| 6c 6f 61 64 22 3a 20 22 30 44 63 66 32 70 6c 46 |load": "0Dcf2plF|
|000001a0| 70 45 53 70 72 4b 6a 6b 64 71 4e 48 4d 38 6a 6a |pESprKjkdqNHM8jj|
|000001b0| 49 41 72 6b 64 37 58 57 35 4c 6c 32 2f 71 61 42 |IArkd7XW5Ll2/qaB|
|000001c0| 71 76 2f 49 34 79 41 4b 35 48 65 31 31 75 53 35 |qv/I4yAK5He11uS5|
|000001d0| 64 76 36 6d 67 61 72 2f 79 4f 4d 67 43 75 52 33 |dv6mgar/yOMgCuR3|
|000001e0| 74 64 62 6b 75 58 62 2b 70 6f 47 71 2f 38 6a 6a |tdbkuXb+poGq/8jj|
|000001f0| 49 41 72 6b 64 37 58 57 35 4c 6c 32 2f 71 61 42 |IArkd7XW5Ll2/qaB|
|00000200| 71 76 2f 49 34 79 41 4b 35 48 65 31 31 75 53 35 |qv/I4yAK5He11uS5|
|00000210| 64 76 36 6d 67 61 72 2f 79 4f 4d 67 43 75 52 33 |dv6mgar/yOMgCuR3|
|00000220| 74 64 62 6b 75 58 62 2b 70 6f 47 71 2f 38 6a 6a |tdbkuXb+poGq/8jj|
|00000230| 49 41 72 6b 64 37 58 57 35 4c 6c 32 2f 71 61 42 |IArkd7XW5Ll2/qaB|
|00000240| 71 76 2f 49 34 79 41 4b 35 48 65 31 31 75 53 35 |qv/I4yAK5He11uS5|
|00000250| 64 76 36 6d 67 61 72 2f 79 4f 4d 67 43 75 52 33 |dv6mgar/yOMgCuR3|
|00000260| 74 64 62 6b 75 58 62 2b 70 6f 47 71 2f 38 6a 6a |tdbkuXb+poGq/8jj|
|00000270| 49 41 72 6b 64 37 58 57 35 4c 6c 32 2f 71 61 42 |IArkd7XW5Ll2/qaB|
|00000280| 71 76 2f 49 34 79 41 4b 35 48 65 31 31 75 53 35 |qv/I4yAK5He11uS5|
|00000290| 64 76 36 6d 67 61 72 2f 79 4f 4d 67 43 75 52 33 |dv6mgar/yOMgCuR3|
|000002a0| 74 64 62 6b 75 58 62 2b 70 6f 47 71 2f 38 6a 6a |tdbkuXb+poGq/8jj|
|000002b0| 49 41 72 6b 64 37 58 57 35 4c 6c 32 2f 71 61 42 |IArkd7XW5Ll2/qaB|
|000002c0| 71 76 2f 49 34 79 41 4b 35 48 65 31 31 75 53 35 |qv/I4yAK5He11uS5|
|000002d0| 64 76 36 6d 67 61 72 2f 79 4f 4d 67 43 75 52 33 |dv6mgar/yOMgCuR3|
|000002e0| 74 64 62 6b 75 58 62 2b 70 6f 47 71 2f 38 6a 6a |tdbkuXb+poGq/8jj|
|000002f0| 49 41 72 6b 64 37 58 57 35 4c 6c 32 2f 71 61 42 |IArkd7XW5Ll2/qaB|
|00000300| 71 76 2f 49 34 79 41 4b 35 48 65 31 31 75 53 35 |qv/I4yAK5He11uS5|
|00000310| 64 76 36 6d 67 61 72 2f 79 4f 4d 67 43 75 52 33 |dv6mgar/yOMgCuR3|
|00000320| 74 64 62 6b 75 58 62 2b 70 6f 47 71 2f 38 6a 6a |tdbkuXb+poGq/8jj|
|00000330| 49 41 72 6b 64 37 58 57 35 4c 6c 32 2f 71 61 42 |IArkd7XW5Ll2/qaB|
|00000340| 71 76 2f 49 34 79 41 4b 35 48 65 31 31 75 53 35 |qv/I4yAK5He11uS5|
|00000350| 64 76 36 6d 67 61 72 2f 79 4f 4d 67 43 75 52 33 |dv6mgar/yOMgCuR3|
|00000360| 74 64 62 6b 75 58 62 2b 70 6f 47 71 2f 38 6a 6a |tdbkuXb+poGq/8jj|
|00000370| 49 41 72 6b 64 37 58 57 35 4c 6c 32 2f 71 61 42 |IArkd7XW5Ll2/qaB|
|00000380| 71 76 2f 49 34 79 41 4b 35 48 65 31 31 75 53 35 |qv/I4yAK5He11uS5|
|00000390| 64 76 36 6d 67 61 72 2f 79 4f 4d 67 43 75 52 33 |dv6mgar/yOMgCuR3|
|000003a0| 74 64 62 6b 75 58 62 2b 70 6f 47 71 2f 38 6a 6a |tdbkuXb+poGq/8jj|
|000003b0| 49 41 72 6b 64 37 58 57 35 4c 6c 32 2f 71 61 42 |IArkd7XW5Ll2/qaB|
|000003c0| 71 76 2f 49 34 79 41 4b 35 48 65 31 31 75 53 35 |qv/I4yAK5He11uS5|
|000003d0| 64 76 36 6d 67 61 72 2f 79 4f 4d 67 43 75 52 33 |dv6mgar/yOMgCuR3|
|000003e0| 74 64 62 6b 75 58 62 2b 70 6f 47 71 2f 38 6a 6a |tdbkuXb+poGq/8jj|
|000003f0| 49 41 72 6b 64 37 58 57 35 4c 6c 32 2f 71 61 42 |IArkd7XW5Ll2/qaB|
+--------+-------------------------------------------------+----------------+
2019-05-19 11:16:15.662 [reactor-http-nio-2] DEBUG reactor.ipc.netty.http.server.HttpServer - [id: 0xa9b527e5, L:/0:0:0:0:0:0:0:1:9090 ! R:/0:0:0:0:0:0:0:1:58012] UNREGISTERED
2019-05-19 11:16:15.665 [reactor-http-nio-3] DEBUG reactor.ipc.netty.http.server.HttpServerOperations - [id: 0x5554e091, L:/0:0:0:0:0:0:0:1:9090 - R:/0:0:0:0:0:0:0:1:58013] Increasing pending responses, now 1
2019-05-19 11:16:15.671 [reactor-http-nio-3] DEBUG reactor.ipc.netty.http.server.HttpServer - [id: 0x5554e091, L:/0:0:0:0:0:0:0:1:9090 - R:/0:0:0:0:0:0:0:1:58013] READ COMPLETE
復(fù)制代碼
注意一下關(guān)鍵字READ: 1024B
,這里應(yīng)該是底層的Reactor-Netty
讀取的最大數(shù)據(jù)報(bào)的長(zhǎng)度限制越除,打印出來的數(shù)據(jù)報(bào)剛好也是1024B的大小节腐,這個(gè)應(yīng)該就是導(dǎo)致請(qǐng)求體被截?cái)嗟母驹蛲舛ⅲ贿@個(gè)問題不單單會(huì)出現(xiàn)在請(qǐng)求體的獲取,也會(huì)出現(xiàn)在響應(yīng)體的寫入翼雀。既然這個(gè)是共性的問題饱苟,那么項(xiàng)目Github上肯定有對(duì)應(yīng)的Issue,找到一個(gè)互動(dòng)比較長(zhǎng)的gateway request size limit 1024B because netty default limit 1024,how to solve it? #581狼渊,從回答來看箱熬,官方建議使用ModifyRequestBodyGatewayFilterFactory
和ModifyResponseBodyGatewayFilterFactory
完成對(duì)應(yīng)的功能。這里可以嘗試借鑒一下ModifyRequestBodyGatewayFilterFactory
的實(shí)現(xiàn)方式修改之前的代碼狈邑,因?yàn)榇a的邏輯比較長(zhǎng)和復(fù)雜城须,解密請(qǐng)求體的過濾器拆分到新的類RequestEncryptionGlobalFilter
,加密響應(yīng)體的過濾器拆分到ResponseDecryptionGlobalFilter
:
RequestEncryptionGlobalFilter
的代碼如下:
@Slf4j
@Component
public class RequestEncryptionGlobalFilter implements GlobalFilter, Ordered {
@Autowired
private ObjectMapper objectMapper;
private final List<HttpMessageReader<?>> messageReaders = HandlerStrategies.withDefaults().messageReaders();
@Override
public int getOrder() {
return -2;
}
@Override
public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
return processRequest(exchange, chain);
}
private Mono<Void> processRequest(ServerWebExchange exchange, GatewayFilterChain chain) {
ServerRequest serverRequest = new DefaultServerRequest(exchange, messageReaders);
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);
CachedBodyOutputMessage outputMessage = new CachedBodyOutputMessage(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);
JsonNode jsonNode = readNode(text);
JsonNode payload = jsonNode.get("payload");
String payloadText = payload.asText();
byte[] content = AesUtils.X.decrypt(payloadText);
String requestBody = new String(content, StandardCharsets.UTF_8);
log.info("修改請(qǐng)求體payload,修改前:{},修改后:{}", payloadText, requestBody);
rewritePayloadNode(requestBody, jsonNode);
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());
}));
}
private void rewritePayloadNode(String text, JsonNode root) {
try {
JsonNode node = objectMapper.readTree(text);
ObjectNode objectNode = (ObjectNode) root;
objectNode.set("payload", node);
} catch (Exception e) {
throw new IllegalStateException(e);
}
}
private void setPayloadTextNode(String text, JsonNode root) {
try {
ObjectNode objectNode = (ObjectNode) root;
objectNode.set("payload", new TextNode(text));
} catch (Exception e) {
throw new IllegalStateException(e);
}
}
private JsonNode readNode(String in) {
try {
return objectMapper.readTree(in);
} catch (Exception e) {
throw new IllegalStateException(e);
}
}
private class DataBufferHolder {
DataBuffer dataBuffer;
int length;
}
}
復(fù)制代碼
ResponseDecryptionGlobalFilter
的代碼如下:
@Slf4j
@Component
public class ResponseDecryptionGlobalFilter implements GlobalFilter, Ordered {
@Autowired
private ObjectMapper objectMapper;
@Override
public int getOrder() {
return NettyWriteResponseFilter.WRITE_RESPONSE_FILTER_ORDER - 1;
}
@Override
public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
return processResponse(exchange, chain);
}
private Mono<Void> processResponse(ServerWebExchange exchange, GatewayFilterChain chain) {
ServerHttpResponseDecorator responseDecorator = new ServerHttpResponseDecorator(exchange.getResponse()) {
@Override
public Mono<Void> writeWith(Publisher<? extends DataBuffer> body) {
String originalResponseContentType = exchange.getAttribute(ORIGINAL_RESPONSE_CONTENT_TYPE_ATTR);
HttpHeaders httpHeaders = new HttpHeaders();
httpHeaders.add(HttpHeaders.CONTENT_TYPE, originalResponseContentType);
ResponseAdapter responseAdapter = new ResponseAdapter(body, httpHeaders);
DefaultClientResponse clientResponse = new DefaultClientResponse(responseAdapter, ExchangeStrategies.withDefaults());
Mono<String> rawBody = clientResponse.bodyToMono(String.class).map(s -> s);
BodyInserter<Mono<String>, ReactiveHttpOutputMessage> bodyInserter = BodyInserters.fromPublisher(rawBody, String.class);
CachedBodyOutputMessage outputMessage = new CachedBodyOutputMessage(exchange, exchange.getResponse().getHeaders());
return bodyInserter.insert(outputMessage, new BodyInserterContext())
.then(Mono.defer(() -> {
Flux<DataBuffer> messageBody = outputMessage.getBody();
Flux<DataBuffer> flux = messageBody.map(buffer -> {
CharBuffer charBuffer = StandardCharsets.UTF_8.decode(buffer.asByteBuffer());
DataBufferUtils.release(buffer);
JsonNode jsonNode = readNode(charBuffer.toString());
JsonNode payload = jsonNode.get("payload");
String text = payload.toString();
String content = AesUtils.X.encrypt(text);
log.info("修改響應(yīng)體payload,修改前:{},修改后:{}", text, content);
setPayloadTextNode(content, jsonNode);
return getDelegate().bufferFactory().wrap(jsonNode.toString().getBytes(StandardCharsets.UTF_8));
});
HttpHeaders headers = getDelegate().getHeaders();
if (!headers.containsKey(HttpHeaders.TRANSFER_ENCODING)) {
flux = flux.doOnNext(data -> headers.setContentLength(data.readableByteCount()));
}
return getDelegate().writeWith(flux);
}));
}
};
return chain.filter(exchange.mutate().response(responseDecorator).build());
}
private void setPayloadTextNode(String text, JsonNode root) {
try {
ObjectNode objectNode = (ObjectNode) root;
objectNode.set("payload", new TextNode(text));
} catch (Exception e) {
throw new IllegalStateException(e);
}
}
private JsonNode readNode(String in) {
try {
return objectMapper.readTree(in);
} catch (Exception e) {
throw new IllegalStateException(e);
}
}
private class ResponseAdapter implements ClientHttpResponse {
private final Flux<DataBuffer> flux;
private final HttpHeaders headers;
@SuppressWarnings("unchecked")
private ResponseAdapter(Publisher<? extends DataBuffer> body, HttpHeaders headers) {
this.headers = headers;
if (body instanceof Flux) {
flux = (Flux) body;
} else {
flux = ((Mono) body).flux();
}
}
@Override
public Flux<DataBuffer> getBody() {
return flux;
}
@Override
public HttpHeaders getHeaders() {
return headers;
}
@Override
public HttpStatus getStatusCode() {
return null;
}
@Override
public int getRawStatusCode() {
return 0;
}
@Override
public MultiValueMap<String, ResponseCookie> getCookies() {
return null;
}
}
}
復(fù)制代碼
模擬請(qǐng)求:
POST /order/json HTTP/1.1
Host: localhost:9090
accessToken: 10086
Content-Type: application/json
User-Agent: PostmanRuntime/7.13.0
Accept: */*
Cache-Control: no-cache
Postman-Token: 3a830202-f3d1-450e-839f-ae8f3b88bced,b229feb1-7c8b-4d25-a039-09345f3fe8f0
Host: localhost:9090
cookie: customCookieName=customCookieValue
accept-encoding: gzip, deflate
content-length: 5416
Connection: keep-alive
cache-control: no-cache
{
"serialNumber": "請(qǐng)求流水號(hào)",
"payload": "0Dcf2plFpESprKjkdqNHM8jjIArkd7XW5Ll2/qaBqv/I4yAK5He11uS5dv6mgar/yOMgCuR3tdbkuXb+poGq/8jjIArkd7XW5Ll2/qaBqv/I4yAK5He11uS5dv6mgar/yOMgCuR3tdbkuXb+poGq/8jjIArkd7XW5Ll2/qaBqv/I4yAK5He11uS5dv6mgar/yOMgCuR3tdbkuXb+poGq/8jjIArkd7XW5Ll2/qaBqv/I4yAK5He11uS5dv6mgar/yOMgCuR3tdbkuXb+poGq/8jjIArkd7XW5Ll2/qaBqv/I4yAK5He11uS5dv6mgar/yOMgCuR3tdbkuXb+poGq/8jjIArkd7XW5Ll2/qaBqv/I4yAK5He11uS5dv6mgar/yOMgCuR3tdbkuXb+poGq/8jjIArkd7XW5Ll2/qaBqv/I4yAK5He11uS5dv6mgar/yOMgCuR3tdbkuXb+poGq/8jjIArkd7XW5Ll2/qaBqv/I4yAK5He11uS5dv6mgar/yOMgCuR3tdbkuXb+poGq/8jjIArkd7XW5Ll2/qaBqv/I4yAK5He11uS5dv6mgar/yOMgCuR3tdbkuXb+poGq/8jjIArkd7XW5Ll2/qaBqv/I4yAK5He11uS5dv6mgar/yOMgCuR3tdbkuXb+poGq/8jjIArkd7XW5Ll2/qaBqv/I4yAK5He11uS5dv6mgar/yOMgCuR3tdbkuXb+poGq/8jjIArkd7XW5Ll2/qaBqv/I4yAK5He11uS5dv6mgar/yOMgCuR3tdbkuXb+poGq/8jjIArkd7XW5Ll2/qaBqv/I4yAK5He11uS5dv6mgar/yOMgCuR3tdbkuXb+poGq/8jjIArkd7XW5Ll2/qaBqv/I4yAK5He11uS5dv6mgar/yOMgCuR3tdbkuXb+poGq/8jjIArkd7XW5Ll2/qaBqv/I4yAK5He11uS5dv6mgar/yOMgCuR3tdbkuXb+poGq/8jjIArkd7XW5Ll2/qaBqv/I4yAK5He11uS5dv6mgar/yOMgCuR3tdbkuXb+poGq/8jjIArkd7XW5Ll2/qaBqv/I4yAK5He11uS5dv6mgar/yOMgCuR3tdbkuXb+poGq/8jjIArkd7XW5Ll2/qaBqv/I4yAK5He11uS5dv6mgar/yOMgCuR3tdbkuXb+poGq/8jjIArkd7XW5Ll2/qaBqv/I4yAK5He11uS5dv6mgar/yOMgCuR3tdbkuXb+poGq/8jjIArkd7XW5Ll2/qaBqv/I4yAK5He11uS5dv6mgar/yOMgCuR3tdbkuXb+poGq/8jjIArkd7XW5Ll2/qaBqv/I4yAK5He11uS5dv6mgar/yOMgCuR3tdbkuXb+poGq/8jjIArkd7XW5Ll2/qaBqv/I4yAK5He11uS5dv6mgar/yOMgCuR3tdbkuXb+poGq/8jjIArkd7XW5Ll2/qaBqv/I4yAK5He11uS5dv6mgar/yOMgCuR3tdbkuXb+poGq/8jjIArkd7XW5Ll2/qaBqv/I4yAK5He11uS5dv6mgar/yOMgCuR3tdbkuXb+poGq/8jjIArkd7XW5Ll2/qaBqv/I4yAK5He11uS5dv6mgar/yOMgCuR3tdbkuXb+poGq/8jjIArkd7XW5Ll2/qaBqv/I4yAK5He11uS5dv6mgar/yOMgCuR3tdbkuXb+poGq/8jjIArkd7XW5Ll2/qaBqv/I4yAK5He11uS5dv6mgar/yOMgCuR3tdbkuXb+poGq/8jjIArkd7XW5Ll2/qaBqv/I4yAK5He11uS5dv6mgar/yOMgCuR3tdbkuXb+poGq/8jjIArkd7XW5Ll2/qaBqv/I4yAK5He11uS5dv6mgar/yOMgCuR3tdbkuXb+poGq/8jjIArkd7XW5Ll2/qaBqv/I4yAK5He11uS5dv6mgar/yOMgCuR3tdbkuXb+poGq/8jjIArkd7XW5Ll2/qaBqv/I4yAK5He11uS5dv6mgar/yOMgCuR3tdbkuXb+poGq/8jjIArkd7XW5Ll2/qaBqv/I4yAK5He11uS5dv6mgar/yOMgCuR3tdbkuXb+poGq/8jjIArkd7XW5Ll2/qaBqv/I4yAK5He11uS5dv6mgar/yOMgCuR3tdbkuXb+poGq/8jjIArkd7XW5Ll2/qaBqv/I4yAK5He11uS5dv6mgar/yOMgCuR3tdbkuXb+poGq/8jjIArkd7XW5Ll2/qaBqv/I4yAK5He11uS5dv6mgar/yOMgCuR3tdbkuXb+poGq/8jjIArkd7XW5Ll2/qaBqv/I4yAK5He11uS5dv6mgar/yOMgCuR3tdbkuXb+poGq/8jjIArkd7XW5Ll2/qaBqv/I4yAK5He11uS5dv6mgar/yOMgCuR3tdbkuXb+poGq/8jjIArkd7XW5Ll2/qaBqv/I4yAK5He11uS5dv6mgar/yOMgCuR3tdbkuXb+poGq/8jjIArkd7XW5Ll2/qaBqv/I4yAK5He11uS5dv6mgar/yOMgCuR3tdbkuXb+poGq/8jjIArkd7XW5Ll2/qaBqv/I4yAK5He11uS5dv6mgar/yOMgCuR3tdbkuXb+poGq/8jjIArkd7XW5Ll2/qaBqv/I4yAK5He11uS5dv6mgar/yOMgCuR3tdbkuXb+poGq/8jjIArkd7XW5Ll2/qaBqv/I4yAK5He11uS5dv6mgar/yOMgCuR3tdbkuXb+poGq/8jjIArkd7XW5Ll2/qaBqv/I4yAK5He11uS5dv6mgar/yOMgCuR3tdbkuXb+poGq/8jjIArkd7XW5Ll2/qaBqv/I4yAK5He11uS5dv6mgar/yOMgCuR3tdbkuXb+poGq/8jjIArkd7XW5Ll2/qaBqv/I4yAK5He11uS5dv6mgar/yOMgCuR3tdbkuXb+poGq/8jjIArkd7XW5Ll2/qaBqv/I4yAK5He11uS5dv6mgar/yOMgCuR3tdbkuXb+poGq/8jjIArkd7XW5Ll2/qaBqv/I4yAK5He11uS5dv6mgar/yOMgCuR3tdbkuXb+poGq/8jjIArkd7XW5Ll2/qaBqv/I4yAK5He11uS5dv6mgar/yOMgCuR3tdbkuXb+poGq/8jjIArkd7XW5Ll2/qaBqv/I4yAK5He11uS5dv6mgar/yOMgCuR3tdbkuXb+poGq/8jjIArkd7XW5Ll2/qaBqv/I4yAK5He11uS5dv6mgar/yOMgCuR3tdbkuXb+poGq/8jjIArkd7XW5Ll2/qaBqv/I4yAK5He11uS5dv6mgar/yOMgCuR3tdbkuXb+poGq/8jjIArkd7XW5Ll2/qaBqv/I4yAK5He11uS5dv6mgar/yOMgCuR3tdbkuXb+poGq/8jjIArkd7XW5Ll2/qaBqv/I4yAK5He11uS5dv6mgar/yOMgCuR3tdbkuXb+poGq/8jjIArkd7XW5Ll2/qaBqv/I4yAK5He11uS5dv6mgar/yOMgCuR3tdbkuXb+poGq/8jjIArkd7XW5Ll2/qaBqv/I4yAK5He11uS5dv6mgar/yOMgCuR3tdbkuXb+poGq/8jjIArkd7XW5Ll2/qaBqv/I4yAK5He11uS5dv6mgar/yOMgCuR3tdbkuXb+poGq/8jjIArkd7XW5Ll2/qaBqv/I4yAK5He11uS5dv6mgar/yOMgCuR3tdbkuXb+poGq/8jjIArkd7XW5Ll2/qaBqv/I4yAK5He11uS5dv6mgar/yOMgCuR3tdbkuXb+poGq/8jjIArkd7XW5Ll2/qaBqv/I4yAK5He11uS5dv6mgar/yOMgCuR3tdbkuXb+poGq/8jjIArkd7XW5Ll2/qaBqv/I4yAK5He11uS5dv6mgar/yOMgCuR3tdbkuXb+poGq/8jjIArkd7XW5Ll2/qaBqv/I4yAK5He11uS5dv6mgar/yOMgCuR3tdbkuXb+poGq/8jjIArkd7XW5Ll2/qaBqv/I4yAK5He11uS5dv6mgar/yOMgCuR3tdbkuXb+poGq/8jjIArkd7XW5Ll2/qaBqv/I4yAK5He11uS5dv6mgar/yOMgCuR3tdbkuXb+poGq/8jjIArkd7XW5Ll2/qaBqv/I4yAK5He11uS5dv6mgar/yOMgCuR3tdbkuXb+poGq/8jjIArkd7XW5Ll2/qaBqv/I4yAK5He11uS5dv6mgar/yOMgCuR3tdbkuXb+poGq/8jjIArkd7XW5Ll2/qaBqv/I4yAK5He11uS5dv6mgar/yOMgCuR3tdbkuXb+poGq/8jjIArkd7XW5Ll2/qaBqv/I4yAK5He11uS5dv6mgar/yOMgCuR3tdbkuXb+poGq/8jjIArkd7XW5Ll2/qaBqv/I4yAK5He11uS5dv6mgar/yOMgCuR3tdbkuXb+poGq/8jjIArkd7XW5Ll2/qaBqv/I4yAK5He11uS5dv6mgar/yOMgCuR3tdbkuXb+poGq/8jjIArkd7XW5Ll2/qaBqv/I4yAK5He11uS5dv6mgar/yOMgCuR3tdbkuXb+poGq/8jjIArkd7XW5Ll2/qaBqv/I4yAK5He11uS5dv6mgar/yOMgCuR3tdbkuXb+poGq/8jjIArkd7XW5Ll2/qaBqv/I4yAK5He11uS5dv6mgar/yOMgCuR3tdbkuXb+poGq/8jjIArkd7XW5Ll2/qaBqv/I4yAK5He11uS5dv6mgar/yOMgCuR3tdbkuXb+poGq/8jjIArkd7XW5Ll2/qaBqv/I4yAK5He11uS5dv6mgar/yOMgCuR3tdbkuXb+poGq/8jjIArkd7XW5Ll2/qaBqv/I4yAK5He11uS5dv6mgar/yOMgCuR3tdbkuXb+poGq/8jjIArkd7XW5Ll2/qaBqv/I4yAK5He11uS5dv6mgar/yOMgCuR3tdbkuXb+poGq/8jjIArkd7XW5Ll2/qaBqv/I4yAK5He11uS5dv6mgar/yOMgCuR3tdbkuXb+poGq/8jjIArkd7XW5Ll2/qaBqv/I4yAK5He11uS5dv6mgar/yOMgCuR3tdbkuXb+poGq/8jjIArkd7XW5Ll2/qaBqv/I4yAK5He11uS5dv6mgar/yOMgCuR3tdbkuXb+poGq/8jjIArkd7XW5Ll2/qaBqv/I4yAK5He11uS5dv6mgar/yOMgCuR3tdbkuXb+poGq/8jjIArkd7XW5Ll2/qaBqv/I4yAK5He11uS5dv6mgar/yOMgCuR3tdbkuXb+poGq/8jjIArkd7XW5Ll2/qaBqv/I4yAK5He11uS5dv6mgar/yOMgCuR3tdbkuXb+poGq/8jjIArkd7XW5Ll2/qaBqv/I4yAK5He11uS5dv6mgar/yOMgCuR3tdbkuXb+poGq/zyJ4ipyLGvo5LX87d9oDAs="
}
// 響應(yīng)
{"serialNumber":"請(qǐng)求流水號(hào)","userId":null,"payload":"7S2VqLu4J6LdW0As50JgZ0eFFoe2FbWytaYdpr2HPg5HhRaHthW1srWmHaa9hz4OR4UWh7YVtbK1ph2mvYc+DkeFFoe2FbWytaYdpr2HPg5HhRaHthW1srWmHaa9hz4OR4UWh7YVtbK1ph2mvYc+DkeFFoe2FbWytaYdpr2HPg5HhRaHthW1srWmHaa9hz4OR4UWh7YVtbK1ph2mvYc+DkeFFoe2FbWytaYdpr2HPg5HhRaHthW1srWmHaa9hz4OR4UWh7YVtbK1ph2mvYc+DkeFFoe2FbWytaYdpr2HPg5HhRaHthW1srWmHaa9hz4OR4UWh7YVtbK1ph2mvYc+DkeFFoe2FbWytaYdpr2HPg5HhRaHthW1srWmHaa9hz4OR4UWh7YVtbK1ph2mvYc+DkeFFoe2FbWytaYdpr2HPg5HhRaHthW1srWmHaa9hz4OR4UWh7YVtbK1ph2mvYc+DkeFFoe2FbWytaYdpr2HPg5HhRaHthW1srWmHaa9hz4OR4UWh7YVtbK1ph2mvYc+DkeFFoe2FbWytaYdpr2HPg5HhRaHthW1srWmHaa9hz4OR4UWh7YVtbK1ph2mvYc+DkeFFoe2FbWytaYdpr2HPg5HhRaHthW1srWmHaa9hz4OR4UWh7YVtbK1ph2mvYc+DkeFFoe2FbWytaYdpr2HPg5HhRaHthW1srWmHaa9hz4OR4UWh7YVtbK1ph2mvYc+DkeFFoe2FbWytaYdpr2HPg5HhRaHthW1srWmHaa9hz4OR4UWh7YVtbK1ph2mvYc+DkeFFoe2FbWytaYdpr2HPg5HhRaHthW1srWmHaa9hz4OR4UWh7YVtbK1ph2mvYc+DkeFFoe2FbWytaYdpr2HPg5HhRaHthW1srWmHaa9hz4OR4UWh7YVtbK1ph2mvYc+DkeFFoe2FbWytaYdpr2HPg5HhRaHthW1srWmHaa9hz4OR4UWh7YVtbK1ph2mvYc+DkeFFoe2FbWytaYdpr2HPg5HhRaHthW1srWmHaa9hz4OR4UWh7YVtbK1ph2mvYc+DkeFFoe2FbWytaYdpr2HPg5HhRaHthW1srWmHaa9hz4OR4UWh7YVtbK1ph2mvYc+DkeFFoe2FbWytaYdpr2HPg5HhRaHthW1srWmHaa9hz4OR4UWh7YVtbK1ph2mvYc+DkeFFoe2FbWytaYdpr2HPg5HhRaHthW1srWmHaa9hz4OR4UWh7YVtbK1ph2mvYc+DkeFFoe2FbWytaYdpr2HPg5HhRaHthW1srWmHaa9hz4OR4UWh7YVtbK1ph2mvYc+DkeFFoe2FbWytaYdpr2HPg5HhRaHthW1srWmHaa9hz4OR4UWh7YVtbK1ph2mvYc+DkeFFoe2FbWytaYdpr2HPg5HhRaHthW1srWmHaa9hz4OR4UWh7YVtbK1ph2mvYc+DkeFFoe2FbWytaYdpr2HPg5HhRaHthW1srWmHaa9hz4OR4UWh7YVtbK1ph2mvYc+DkeFFoe2FbWytaYdpr2HPg5HhRaHthW1srWmHaa9hz4OR4UWh7YVtbK1ph2mvYc+DkeFFoe2FbWytaYdpr2HPg5HhRaHthW1srWmHaa9hz4OR4UWh7YVtbK1ph2mvYc+DkeFFoe2FbWytaYdpr2HPg5HhRaHthW1srWmHaa9hz4OR4UWh7YVtbK1ph2mvYc+DkeFFoe2FbWytaYdpr2HPg5HhRaHthW1srWmHaa9hz4OR4UWh7YVtbK1ph2mvYc+DkeFFoe2FbWytaYdpr2HPg5HhRaHthW1srWmHaa9hz4OR4UWh7YVtbK1ph2mvYc+DkeFFoe2FbWytaYdpr2HPg5HhRaHthW1srWmHaa9hz4OR4UWh7YVtbK1ph2mvYc+DkeFFoe2FbWytaYdpr2HPg5HhRaHthW1srWmHaa9hz4OR4UWh7YVtbK1ph2mvYc+DkeFFoe2FbWytaYdpr2HPg5HhRaHthW1srWmHaa9hz4OR4UWh7YVtbK1ph2mvYc+DkeFFoe2FbWytaYdpr2HPg5HhRaHthW1srWmHaa9hz4OR4UWh7YVtbK1ph2mvYc+DkeFFoe2FbWytaYdpr2HPg5HhRaHthW1srWmHaa9hz4OR4UWh7YVtbK1ph2mvYc+DkeFFoe2FbWytaYdpr2HPg5HhRaHthW1srWmHaa9hz4OR4UWh7YVtbK1ph2mvYc+DkeFFoe2FbWytaYdpr2HPg5HhRaHthW1srWmHaa9hz4OR4UWh7YVtbK1ph2mvYc+DkeFFoe2FbWytaYdpr2HPg5HhRaHthW1srWmHaa9hz4OR4UWh7YVtbK1ph2mvYc+DkeFFoe2FbWytaYdpr2HPg5HhRaHthW1srWmHaa9hz4OR4UWh7YVtbK1ph2mvYc+DkeFFoe2FbWytaYdpr2HPg5HhRaHthW1srWmHaa9hz4OR4UWh7YVtbK1ph2mvYc+DkeFFoe2FbWytaYdpr2HPg5HhRaHthW1srWmHaa9hz4OR4UWh7YVtbK1ph2mvYc+DkeFFoe2FbWytaYdpr2HPg5HhRaHthW1srWmHaa9hz4OR4UWh7YVtbK1ph2mvYc+DkeFFoe2FbWytaYdpr2HPg5HhRaHthW1srWmHaa9hz4OR4UWh7YVtbK1ph2mvYc+DkeFFoe2FbWytaYdpr2HPg5HhRaHthW1srWmHaa9hz4OR4UWh7YVtbK1ph2mvYc+DkeFFoe2FbWytaYdpr2HPg5HhRaHthW1srWmHaa9hz4OR4UWh7YVtbK1ph2mvYc+DkeFFoe2FbWytaYdpr2HPg5HhRaHthW1srWmHaa9hz4OR4UWh7YVtbK1ph2mvYc+DkeFFoe2FbWytaYdpr2HPg5HhRaHthW1srWmHaa9hz4OR4UWh7YVtbK1ph2mvYc+DkeFFoe2FbWytaYdpr2HPg5HhRaHthW1srWmHaa9hz4OR4UWh7YVtbK1ph2mvYc+DkeFFoe2FbWytaYdpr2HPg5HhRaHthW1srWmHaa9hz4OR4UWh7YVtbK1ph2mvYc+DkeFFoe2FbWytaYdpr2HPg5HhRaHthW1srWmHaa9hz4OR4UWh7YVtbK1ph2mvYc+DkeFFoe2FbWytaYdpr2HPg5HhRaHthW1srWmHaa9hz4OR4UWh7YVtbK1ph2mvYc+DkeFFoe2FbWytaYdpr2HPg5HhRaHthW1srWmHaa9hz4OR4UWh7YVtbK1ph2mvYc+DkeFFoe2FbWytaYdpr2HPg5HhRaHthW1srWmHaa9hz4OR4UWh7YVtbK1ph2mvYc+DkeFFoe2FbWytaYdpr2HPg5HhRaHthW1srWmHaa9hz4OR4UWh7YVtbK1ph2mvYc+DkeFFoe2FbWytaYdpr2HPg5HhRaHthW1srWmHaa9hz4OR4UWh7YVtbK1ph2mvYc+DkeFFoe2FbWytaYdpr2HPg5HhRaHthW1srWmHaa9hz4OR4UWh7YVtbK1ph2mvYc+DkeFFoe2FbWytaYdpr2HPg5HhRaHthW1srWmHaa9hz4OR4UWh7YVtbK1ph2mvYc+DkeFFoe2FbWytaYdpr2HPg5HhRaHthW1srWmHaa9hz4OR4UWh7YVtbK1ph2mvYc+DkeFFoe2FbWytaYdpr2HPg5HhRaHthW1srWmHaa9hz4OR4UWh7YVtbK1ph2mvYc+DkeFFoe2FbWytaYdpr2HPg5HhRaHthW1srWmHaa9hz4OR4UWh7YVtbK1ph2mvYc+DkeFFoe2FbWytaYdpr2HPg5HhRaHthW1srWmHaa9hz4OR4UWh7YVtbK1ph2mvYc+DkeFFoe2FbWytaYdpr2HPg5HhRaHthW1srWmHaa9hz4OR4UWh7YVtbK1ph2mvYc+DkeFFoe2FbWytaYdpr2HPg5HhRaHthW1srWmHaa9hz4OR4UWh7YVtbK1ph2mvYc+DkeFFoe2FbWytaYdpr2HPg5HhRaHthW1srWmHaa9hz4OR4UWh7YVtbK1ph2mvYc+DkeFFoe2FbWytaYdpr2HPg5HhRaHthW1srWmHaa9hz4OR4UWh7YVtbK1ph2mvYc+DkeFFoe2FbWytaYdpr2HPg5HhRaHthW1srWmHaa9hz4OR4UWh7YVtbK1ph2mvYc+DkeFFoe2FbWytaYdpr2HPg5HhRaHthW1srWmHaa9hz4OR4UWh7YVtbK1ph2mvYc+DkeFFoe2FbWytaYdpr2HPg5HhRaHthW1srWmHaa9hz4OR4UWh7YVtbK1ph2mvYc+DkeFFoe2FbWytaYdpr2HPg5HhRaHthW1srWmHaa9hz4OR4UWh7YVtbK1ph2mvYc+DkeFFoe2FbWytaYdpr2HPg5HhRaHthW1srWmHaa9hz4OR4UWh7YVtbK1ph2mvYc+DkeFFoe2FbWytaYdpr2HPg5HhRaHthW1srWmHaa9hz4OR4UWh7YVtbK1ph2mvYc+DkeFFoe2FbWytaYdpr2HPg5HhRaHthW1srWmHaa9hz4OR4UWh7YVtbK1ph2mvYc+DkeFFoe2FbWytaYdpr2HPg5HhRaHthW1srWmHaa9hz4OR4UWh7YVtbK1ph2mvYc+DkeFFoe2FbWytaYdpr2HPg5HhRaHthW1srWmHaa9hz4OR4UWh7YVtbK1ph2mvYc+DkeFFoe2FbWytaYdpr2HPg5HhRaHthW1srWmHaa9hz4OR4UWh7YVtbK1ph2mvYc+DkeFFoe2FbWytaYdpr2HPg5HhRaHthW1srWmHaa9hz4OR4UWh7YVtbK1ph2mvYc+DkeFFoe2FbWytaYdpr2HPg5HhRaHthW1srWmHaa9hz4OR4UWh7YVtbK1ph2mvYc+DkeFFoe2FbWytaYdpr2HPg5HhRaHthW1srWmHaa9hz4OR4UWh7YVtbK1ph2mvYc+DkeFFoe2FbWytaYdpr2HPg5HhRaHthW1srWmHaa9hz4OR4UWh7YVtbK1ph2mvYc+DkeFFoe2FbWytaYdpr2HPg5HhRaHthW1srWmHaa9hz4OR4UWh7YVtbK1ph2mvYc+DkeFFoe2FbWytaYdpr2HPg5HhRaHthW1srWmHaa9hz4OR4UWh7YVtbK1ph2mvYc+DkeFFoe2FbWytaYdpr2HPg5HhRaHthW1srWmHaa9hz4OR4UWh7YVtbK1ph2mvYc+DkeFFoe2FbWytaYdpr2HPg5HhRaHthW1srWmHaa9hz4OR4UWh7YVtbK1ph2mvYc+DkeFFoe2FbWytaYdpr2HPg5HhRaHthW1srWmHaa9hz4OR4UWh7YVtbK1ph2mvYc+DkeFFoe2FbWytaYdpr2HPg5HhRaHthW1srWmHaa9hz4OR4UWh7YVtbK1ph2mvYc+Dm8rTVHylECORYnLNgnfWx0ENJ9a6E+abYhyFJ9zSIda"}
復(fù)制代碼
徹底解決了之前的請(qǐng)求或者響應(yīng)報(bào)文截?cái)嗟膯栴}官地,筆者發(fā)現(xiàn)了很多博文都在(照搬)更改讀取DataBuffer
實(shí)例時(shí)候的代碼邏輯酿傍,其實(shí)那段邏輯是不相關(guān)的,可以嘗試用BufferedReader
基于行讀取然后用StringBuilder
承載驱入,或者像本文那樣直接讀取為byte數(shù)組等等赤炒,因?yàn)楦镜脑蚴堑讓拥?code>Reactor-Netty的數(shù)據(jù)塊讀取大小限制導(dǎo)致獲取到的DataBuffer
實(shí)例里面的數(shù)據(jù)是不完整的,解決方案就是參照Spring Cloud Gateway
本身提供的基礎(chǔ)類庫(kù)進(jìn)行改造(暫時(shí)沒發(fā)現(xiàn)有入口可以調(diào)整Reactor-Netty
的配置)亏较,難度也不大莺褒。
小結(jié)
剛好遇到一個(gè)需求需要做網(wǎng)關(guān)的加解密包括請(qǐng)求體和響應(yīng)體的修改,這里順便把Spring Cloud Gateway
一些涉及到這方面的一些內(nèi)容梳理了一遍雪情,順便把坑踩了并且填完遵岩。下一步嘗試按照目前官方提供的可用組件修改一下實(shí)現(xiàn)自定義的邏輯,包括Hystrix
巡通、基于Eureka
和Ribbon
的負(fù)載均衡尘执、限流等等。
對(duì)文章感興趣的朋友宴凉,可以加我的群來聊聊喲
歡迎工作一到五年的Java工程師朋友們加入JavaQQ群:219571750誊锭,群內(nèi)提供免費(fèi)的Java架構(gòu)學(xué)習(xí)資料(里面有高可用、高并發(fā)弥锄、高性能及分布式丧靡、Jvm性能調(diào)優(yōu)、Spring源碼籽暇,MyBatis温治,Netty,Redis,Kafka,Mysql,Zookeeper,Tomcat,Docker,Dubbo,Nginx等多個(gè)知識(shí)點(diǎn)的架構(gòu)資料)合理利用自己每一分每一秒的時(shí)間來學(xué)習(xí)提升自己,不要再用"沒有時(shí)間“來掩飾自己思想上的懶惰戒悠!趁年輕熬荆,使勁拼,給未來的自己一個(gè)交代绸狐!