package com.gateway.filter;
import com.google.common.collect.Lists;
import com.common.core.domain.R;
import com.common.json.utils.JsonUtils;
import com.common.net.ftp.ClientTemplate;
import com.gateway.conf.FtpLocalProperties;
import com.gateway.constants.MetadataConstant;
import com.gateway.ferry.conf.SystemProperties;
import com.gateway.ferry.constant.ActionEnum;
import com.gateway.ferry.constant.CharConstant;
import com.gateway.ferry.constant.ErrorCode;
import com.gateway.ferry.constant.ServerProtocolEnum;
import com.gateway.ferry.exception.TransportException;
import com.gateway.ferry.model.ApiResult;
import com.gateway.ferry.model.NodeInfo;
import com.gateway.ferry.protocol.*;
import com.gateway.ferry.service.FerryTemplateService;
import com.gateway.ferry.service.NodeService;
import com.gateway.ferry.util.SegmentSharedHolder;
import com.gateway.ferry.util.TextUtil;
import com.gateway.service.AuthServiceImpl;
import com.gateway.task.NacosNodeRouteTask;
import lombok.extern.slf4j.Slf4j;
import okhttp3.*;
import okhttp3.Request.Builder;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.cloud.gateway.filter.GatewayFilterChain;
import org.springframework.cloud.gateway.filter.GlobalFilter;
import org.springframework.cloud.gateway.route.Route;
import org.springframework.cloud.gateway.support.ServerWebExchangeUtils;
import org.springframework.core.Ordered;
import org.springframework.core.io.buffer.DataBuffer;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType;
import org.springframework.http.server.reactive.ServerHttpRequest;
import org.springframework.http.server.reactive.ServerHttpResponse;
import org.springframework.stereotype.Component;
import org.springframework.web.server.ServerWebExchange;
import reactor.core.publisher.Mono;
import java.io.ByteArrayInputStream;
import java.nio.charset.StandardCharsets;
import java.nio.file.Paths;
import java.util.*;
import java.util.Map.Entry;
import java.util.concurrent.TimeUnit;
/**
* Copyright (c) 2025 NVXClouds.Co.Ltd. All rights reserved.
*
* @author muzhi
* @version 3.0.0
* @date 2025-08-08 00:10
*/
@Slf4j
@Component
public class FerryRequestBodyFilter implements GlobalFilter, Ordered {
@Autowired
private SystemProperties systemProperties;
@Autowired
private NodeService nodeService;
@Autowired
private FerryTemplateService ferryTemplateService;
@Autowired
private FtpLocalProperties ftpLocalProperties;
@Autowired
private AuthServiceImpl authService;
private static final List<String> supportedFerryProtocol = Lists.newArrayList(ServerProtocolEnum.FTP.name(), ServerProtocolEnum.FTPS.name(), ServerProtocolEnum.SFTP.name());
private static final List<String> IGNORE_HEADER = Lists.newArrayList(
"content-length"
);
private OkHttpClient client = new OkHttpClient.Builder()
.connectTimeout(15, TimeUnit.SECONDS)
.writeTimeout(15, TimeUnit.SECONDS)
.readTimeout(15, TimeUnit.SECONDS)
// .addInterceptor(new HostResolveInterceptor(systemProperties.getService()))
.build();
@Override
public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
// 1. 判断是否满足短路条件
R<String> sendRemote = authService.changeSignRequestKey(exchange);
if(R.isSuccess(sendRemote)){
Route route = (Route)exchange.getAttributes().get(ServerWebExchangeUtils.GATEWAY_ROUTE_ATTR);
Map<String, Object> metadata = route.getMetadata();
if(metadata != null && metadata.containsKey(MetadataConstant.FERRY_PROTOCOL)) {
String protocol = metadata.get(MetadataConstant.FERRY_PROTOCOL).toString();
if(supportedFerryProtocol.contains(protocol)){
return ferry(exchange, NacosNodeRouteTask.LOCAL.getNodeCode(), protocol);
}
}
}
// 2. 不满足短路条件时继续链路
return chain.filter(exchange);
}
@Override
public int getOrder() {
return Ordered.LOWEST_PRECEDENCE - 4;
}
public Mono<Void> ferry(ServerWebExchange exchange, String srcNodeCode, String destNodeCode) {
ServerHttpRequest request = exchange.getRequest();
ServerHttpResponse response = exchange.getResponse();
String uri = request.getURI().toString();
String service = StringUtils.substringBetween(uri, "/" + destNodeCode + "/", "/");;
String action = request.getHeaders().getFirst(com.gateway.ferry.constant.FerryConstant.HEADER_ACTION);
RequestData requestData = new RequestData();
requestData.setMethod(request.getMethod().name());
requestData.setUri(uri);
Map<String, String> headerMap = new HashMap<>();
HttpHeaders headers = request.getHeaders();
headers.forEach((k, v) -> {
String headerName = k;
String headerValue = headers.getFirst(headerName);
headerMap.put(headerName, headerValue);
});
StringBuffer queryString = new StringBuffer();
request.getQueryParams().forEach((k, v) -> {
if(queryString.length()>0) {
queryString.append("&");
}
queryString.append(k).append("=").append(v);
});
requestData.setQueryString(queryString.toString());
requestData.setHeader(headerMap);
Map<String, String[]> paramMap = new HashMap<>();
requestData.setParam(paramMap);
Mono<RequestData> enriched;
MediaType contentType = request.getHeaders().getContentType();
requestData.setContentType(contentType.toString());
if (MediaType.MULTIPART_FORM_DATA.includes(contentType)) {
CachingBodyRequestDecorator cachedRequest = new CachingBodyRequestDecorator(exchange.getRequest());
ServerWebExchange mutated = exchange.mutate().request(cachedRequest).build();
// 1. 取出 byte[]
byte[] raw = cachedRequest.getCachedBodyBytes();
requestData.setRaw(raw);
} else {
// BufferedReader reader = null;
// try{
// reader = request.getReader();
// if(reader.ready()){
// Map<String, Object> body = TransportUtil.getRequestBody(contentType, reader);
// requestData.setBody(body);
// }
// } finally {
// if(reader != null){
// reader.close();
// }
// }
}
RequestBody body = RequestBody.create(
requestData.getRaw(),
okhttp3.MediaType.parse(requestData.getContentType())); // 带 boundary
// 3. 发请求
Headers.Builder headerBuilder = new Headers.Builder();
Iterator<Entry<String, String>> iterator = requestData.getHeader().entrySet().iterator();
while (iterator.hasNext()){
Map.Entry<String, String> next = iterator.next();
if(!TextUtil.containsIgnore(IGNORE_HEADER, next.getKey())){
headerBuilder.add(next.getKey(), next.getValue());
}
}
String url = "http://127.0.0.1:8080/demo/dataset/file/upload";
Request httpRequest = new Builder().url(url).headers(headerBuilder.build())
.post(body)
.build();
if(true){
try{
Response httpResponse = client.newCall(httpRequest).execute();
byte[] bytes = httpResponse.body().bytes();
response.setStatusCode(HttpStatus.OK);
response.getHeaders().setContentType(MediaType.APPLICATION_JSON);
DataBuffer buffer = response.bufferFactory().wrap(bytes);
// 3. 直接返回,不再调用 chain.filter(...)
return response.writeWith(Mono.just(buffer));
} catch (Exception exp) {
log.error("error when parse request {}, {}", url, exp.getMessage(), exp);
return exchange.getResponse().setComplete();
}
}
byte[] data = ProtostuffSerializer.serialize(requestData, RequestData.class);
String sid = System.currentTimeMillis() + TextUtil.random(6);
Segment segment = new Segment();
segment.setMagic(com.gateway.ferry.constant.FerryConstant.MAGIC);
segment.setVersion(com.gateway.ferry.constant.FerryConstant.PROTOCOL_VERSION);
segment.setSid(sid);
segment.setAction(action);
segment.setSrc(srcNodeCode);
segment.setDest(destNodeCode);
segment.setService(service);
segment.setData(data);
segment.setup();
//get node local by dest node code
NodeInfo local = nodeService.getByCode(srcNodeCode).orElseThrow(() -> new TransportException(ErrorCode.NOT_FOUND_NODE, srcNodeCode));
//encrypt segment file to local disk
//get dest node code ftp(s) client template
ClientTemplate template = ferryTemplateService.getFerryTemplate(local);
byte[] requestSegment = ProtostuffSerializer.serialize(segment, Segment.class);
//upload segment to local receiver temp
ByteArrayInputStream bais = new ByteArrayInputStream(requestSegment);
String fid = new StringJoiner("_")
.add(srcNodeCode).add(destNodeCode)
.add(String.valueOf(System.currentTimeMillis()))
.add(TextUtil.random(4)).toString() + CharConstant.DOT + requestSegment.length;
String remoteDataFile = fid + com.gateway.ferry.constant.FerryConstant.SUFFIX_REQUEST;
String remoteLogFile = remoteDataFile + com.gateway.ferry.constant.FerryConstant.SUFFIX_LOG;
template.uploadFile(bais,
Paths.get(TextUtil.trimNodePath(ftpLocalProperties.getSendFile()), destNodeCode).toString(),
remoteDataFile);
// String logContent = Paths.get(FerryConstant.ARCHIVE_FOLDER, remoteDataFile).toString();
String logContent = "0";
ByteArrayInputStream logBais = new ByteArrayInputStream(logContent.getBytes());
template.uploadFile(logBais,
Paths.get(TextUtil.trimNodePath(ftpLocalProperties.getSendLog()), destNodeCode).toString(),
remoteLogFile);
//move local receive temp file to local receive path
if(ActionEnum.FETCH.name().equalsIgnoreCase(action)){
//load segment from local ftp server response
String resultData;
try{
ResponseData responseData = SegmentSharedHolder.await(sid, 15_000, TimeUnit.MILLISECONDS);
resultData = new String(responseData.getBody());
} catch (Exception ex){
resultData = JsonUtils.toJsonString(ApiResult.fail(ErrorCode.SERVER_ERROR.getCode(), "请求等待超时"));
}
return writeData(ErrorCode.SERVER_ERROR.getHttpStatus(), response, resultData);
} else {
return writeData(HttpStatus.OK, response, JsonUtils.toJsonString(ApiResult.success(null)));
}
}
private Mono<Void> writeData(HttpStatus status, ServerHttpResponse response, String data) {
// 2. 构造响应
byte[] bytes = data.getBytes(StandardCharsets.UTF_8);
response.setStatusCode(status);
response.getHeaders().setContentType(MediaType.APPLICATION_JSON);
DataBuffer buffer = response.bufferFactory().wrap(bytes);
// 3. 直接返回,不再调用 chain.filter(...)
return response.writeWith(Mono.just(buffer));
}
}