ferry

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));
    }
}

 


posted @ 2025-08-20 22:06  牧之丨  阅读(4)  评论(0)    收藏  举报