当前位置: 首页 > article >正文

route-forward springboot实现路由转发程序

目录

前言:想实现一个轻量级的接口反向代理和转发的一个接口服务,可以通过这个服务做一些需要认证才能访问的接口给到前端使用,这样就实现了一种认证可以调用多种第三方系统的服务。

基本逻辑就是将请求的请求方式、请求头、请求体提取出来,将这些信息转发到另外一个接口

  • 1.1 配置类
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.context.annotation.Configuration;
import org.springframework.stereotype.Component;

import java.util.List;

/**
 * 路由代理配置
 */
@Configuration
@ConfigurationProperties(prefix = "delegate.config.api", ignoreUnknownFields = false)
public class RouterDelegateProperties {

    /**
     * 网关地址
     */
    String rootPath;

    /**
     * 服务名称, 服务名称和服务网关和服务拓展器一一对应, 服务网关和服务拓展器没有则用default代替
     */
    List<String> serviceName;

    /**
     * 服务网关, 服务名称和服务网关和服务拓展器一一对应, 服务网关和服务拓展器没有则用default代替
     */
    List<String> serviceRoot;

    /**
     * 服务拓展器, 服务名称和服务网关和服务拓展器一一对应, 服务网关和服务拓展器没有则用default代替
     */
    List<String> serviceExtractor;


    public String getRootPath() {
        return rootPath;
    }

    public void setRootPath(String rootPath) {
        this.rootPath = rootPath;
    }

    public List<String> getServiceName() {
        return serviceName;
    }

    public void setServiceName(List<String> serviceName) {
        this.serviceName = serviceName;
    }

    public List<String> getServiceRoot() {
        return serviceRoot;
    }

    public void setServiceRoot(List<String> serviceRoot) {
        this.serviceRoot = serviceRoot;
    }

    public List<String> getServiceExtractor() {
        return serviceExtractor;
    }

    public void setServiceExtractor(List<String> serviceExtractor) {
        this.serviceExtractor = serviceExtractor;
    }
}

  • 1.2 实体DTO
import java.io.Serializable;

/**
 * 代理路由配置 DTO
 */
public class RouterDelegateConfigDTO implements Serializable {

    private static final long serialVersionUID = 1L;

    /**
     * 网关地址
     */
    private String rootPath;

    /**
     * 服务名称
     */
    private String serviceName;

    /**
     * 服务名称地址
     */
    private String serviceRoot;

    /**
     * 服务名称处理器
     */
    private String serviceExtractor;


    public String getRootPath() {
        return rootPath;
    }

    public void setRootPath(String rootPath) {
        this.rootPath = rootPath;
    }

    public String getServiceName() {
        return serviceName;
    }

    public void setServiceName(String serviceName) {
        this.serviceName = serviceName;
    }

    public String getServiceRoot() {
        return serviceRoot;
    }

    public void setServiceRoot(String serviceRoot) {
        this.serviceRoot = serviceRoot;
    }

    public String getServiceExtractor() {
        return serviceExtractor;
    }

    public void setServiceExtractor(String serviceExtractor) {
        this.serviceExtractor = serviceExtractor;
    }
}

  • 1.3 路由代理拓展器
import org.springframework.http.HttpHeaders;
import zsoft.gov.datacenter.biztable.common.dto.router.RouterDelegateConfigDTO;

import javax.servlet.http.HttpServletRequest;

/**
 * 路由代理拓展器
 */
public interface RouterDelegateExtractor {

    /**
     * 处理请求url, 返回null则使用通用处理逻辑
     *
     * @param request   请求体对象
     * @param configDTO 服务配置对象
     * @param prefix    代理前缀
     * @return
     */
    String getRequestRootUrl(HttpServletRequest request, RouterDelegateConfigDTO configDTO, String prefix);

    /**
     * 处理请求头
     *
     * @param request 请求体对象
     * @param headers 请求头
     */
    void parseRequestHeader(HttpServletRequest request, HttpHeaders headers);

    /**
     * 处理请求体, 返回null则使用通用处理逻辑
     *
     * @param request 请求体对象
     * @return
     */
    byte[] parseRequestBody(HttpServletRequest request);

}

  • 1.4 请求对象 RestTemplate
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.http.client.ClientHttpRequestFactory;
import org.springframework.http.client.ClientHttpResponse;
import org.springframework.http.client.SimpleClientHttpRequestFactory;
import org.springframework.http.converter.HttpMessageConverter;
import org.springframework.http.converter.StringHttpMessageConverter;
import org.springframework.web.client.DefaultResponseErrorHandler;
import org.springframework.web.client.RestTemplate;

import java.io.IOException;
import java.nio.charset.Charset;
import java.util.List;

@Configuration
public class FetchApiRestTemplateConfig {

    @Bean({"fetchApiRestTemplate"})
    @Autowired
    public RestTemplate restTemplate(@Qualifier("fetchApiClientHttpRequestFactory") ClientHttpRequestFactory factory) {
        RestTemplate restTemplate = new RestTemplate(factory);
        restTemplate.setErrorHandler(new DefaultResponseErrorHandler() {
            @Override
            public void handleError(ClientHttpResponse response) throws IOException {
//                if (response.getRawStatusCode() != 401 && response.getRawStatusCode() != 404) {
//                    super.handleError(response);
//                }
                // 处理返回 4xx 的状态码时不抛出异常
                if (!response.getStatusCode().is4xxClientError()) {
                    super.handleError(response);
                }
            }
        });
        // 中文乱码问题
        List<HttpMessageConverter<?>> httpMessageConverters = restTemplate.getMessageConverters();
        httpMessageConverters.stream().forEach(httpMessageConverter -> {
            if (httpMessageConverter instanceof StringHttpMessageConverter) {
                StringHttpMessageConverter messageConverter = (StringHttpMessageConverter) httpMessageConverter;
                messageConverter.setDefaultCharset(Charset.forName("UTF-8"));
            }
        });
        return restTemplate;
    }

    @Bean({"fetchApiClientHttpRequestFactory"})
    public ClientHttpRequestFactory simpleClientHttpRequestFactory() {
        SimpleClientHttpRequestFactory factory = new SimpleClientHttpRequestFactory();
        factory.setReadTimeout(1000 * 50); // 读取超时(毫秒)
        factory.setConnectTimeout(1000 * 10); // 连接超时(毫秒)
        return factory;
    }

}

  • 2、核心转发代码
import org.apache.commons.collections.CollectionUtils;
import org.apache.commons.lang3.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.boot.ApplicationArguments;
import org.springframework.boot.ApplicationRunner;
import org.springframework.http.*;
import org.springframework.stereotype.Service;
import org.springframework.util.StreamUtils;
import org.springframework.web.client.RestTemplate;
import zsoft.gov.datacenter.biztable.common.config.RouterDelegateProperties;
import zsoft.gov.datacenter.biztable.common.dto.router.RouterDelegateConfigDTO;
import zsoft.gov.datacenter.biztable.common.response.Result;

import javax.annotation.Resource;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.io.InputStream;
import java.net.URI;
import java.net.URISyntaxException;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;

/**
 * 路由代理
 */
@Service
public class RouterDelegate implements ApplicationRunner {

    protected Logger logger = LoggerFactory.getLogger(getClass());

    private static Map<String, RouterDelegateConfigDTO> configMap;

    @Resource
    @Qualifier("fetchApiRestTemplate")
    private RestTemplate restTemplate;

    @Resource
    private RouterDelegateProperties properties;

    @Resource
    private Map<String, RouterDelegateExtractor> stringRouterDelegateExtractorMap;


    /**
     * 初始化配置类
     *
     * @param args
     * @throws Exception
     */
    @Override
    public void run(ApplicationArguments args) throws Exception {
        boolean intiFlag = false;
        logger.info(">>> -----开始初始化路由代理配置类!");

        /**
         * 最终configMap效果
         * {
         *     服务名称: {
         *         rootPath: "系统网关地址",
         *         serviceName: "服务名称",
         *         serviceRoot: "服务网关",
         *         serviceExtractor: "服务拓展器",
         *     }
         * }
         */
        String rootPath = properties.getRootPath();
        List<String> serviceName = properties.getServiceName();
        List<String> serviceRoot = properties.getServiceRoot();
        List<String> serviceExtractor = properties.getServiceExtractor();
        // 服务名称, 服务名称和服务网关和服务处理器一一对应, 如果没有对应的服务网关和服务处理器, 则用英文逗号隔开
        if (StringUtils.isNotBlank(rootPath)
                && CollectionUtils.isNotEmpty(serviceName)
                && CollectionUtils.isNotEmpty(serviceExtractor)
                && CollectionUtils.isNotEmpty(serviceRoot)
                && serviceName.size() == serviceRoot.size()
                && serviceName.size() == serviceExtractor.size()) {

            intiFlag = true;
            // 初始化大小避免扩容
            int initialCapacity = (int) (serviceName.size() / 0.75) + 1;
            configMap = new ConcurrentHashMap<>(initialCapacity);
            for (int i = 0; i < serviceName.size(); i++) {
                RouterDelegateConfigDTO dto = new RouterDelegateConfigDTO();
                String serName = serviceName.get(i);
                dto.setRootPath(rootPath);
                dto.setServiceName(serName);
                // default 是占位符, 配置成default相当于没有配置
                dto.setServiceRoot("default".equals(serviceRoot.get(i)) ? null : serviceRoot.get(i));
                dto.setServiceExtractor("default".equals(serviceExtractor.get(i)) ? null : serviceExtractor.get(i));
                configMap.put(serName, dto);
            }
        }

        if (intiFlag) logger.info(">>> 初始化路由代理配置类成功!");
        else logger.error(">>> 初始化路由代理配置类失败!");
    }

    public ResponseEntity<byte[]> redirect(HttpServletRequest request, HttpServletResponse response, String prefix, String serviceName) {
        String requestURI = request.getRequestURI();

        RouterDelegateConfigDTO currentConfig = getCurrentServiceConfig(serviceName);
        if (currentConfig == null) {
            return buildErrorResponseEntity("SERVICE ERROR! 服务不存在!", HttpStatus.NOT_FOUND);
        }
        RouterDelegateExtractor extractorCallBack = getRouterDelegateExtractor(serviceName);

        try {
            // 创建url
            String redirectUrl = createRequestUrl(request, currentConfig, prefix, extractorCallBack);
            logger.info(">>> redirectUrl代理后的完整地址: [{}]", redirectUrl);
            RequestEntity requestEntity = createRequestEntity(request, redirectUrl, extractorCallBack);
            // return route(request, redirectUrl, extractorCallBack);
            ResponseEntity<byte[]> result = route(requestEntity);
            if (result.getHeaders() != null && result.getHeaders().containsKey(HttpHeaders.TRANSFER_ENCODING)) {
                // 移除响应头 Transfer-Encoding, 因为高版本的nginx会自动添加该响应头, 多个响应值nginx会报错
                // 多个响应值nginx报错: *6889957 upstream sent duplicate header line: "Transfer-Encoding: chunked", previous value: "Transfer-Encoding: chunked" while reading response header from upstream
                HttpHeaders headers = HttpHeaders.writableHttpHeaders(result.getHeaders());
                headers.remove(HttpHeaders.TRANSFER_ENCODING);
            }
            //logger.info(">>> [{}] 代理成功, 请求耗时: [{}]", requestURI, System.currentTimeMillis() - l1);
            return result;
        } catch (Exception e) {
            logger.error("REDIRECT ERROR", e);
            //logger.error(">>> [{}] 代理失败, 请求耗时: [{}]", requestURI, System.currentTimeMillis() - l1);
            return buildErrorResponseEntity("REDIRECT ERROR! " + e.getMessage(), HttpStatus.INTERNAL_SERVER_ERROR);
        }
    }

    private ResponseEntity buildErrorResponseEntity(String msg, HttpStatus httpStatus) {
        HttpHeaders headers = new HttpHeaders();
        headers.setContentType(MediaType.APPLICATION_JSON);
        Result body = Result.build(httpStatus.value(), msg);
        return new ResponseEntity(body, headers, httpStatus);
    }

    /**
     * 获取当前服务配置
     *
     * @param serviceName
     * @return
     */
    public RouterDelegateConfigDTO getCurrentServiceConfig(String serviceName) {
        if (configMap == null || !configMap.containsKey(serviceName)) {
            return null;
        }

        return configMap.get(serviceName);
    }

    /**
     * 获取当前路由服务拓展器
     *
     * @param serviceName
     * @return
     */
    private RouterDelegateExtractor getRouterDelegateExtractor(String serviceName) {
        RouterDelegateConfigDTO currentConfig = getCurrentServiceConfig(serviceName);
        if (currentConfig == null) {
            return null;
        }

        String serviceExtractor = currentConfig.getServiceExtractor();
        if (StringUtils.isBlank(serviceExtractor)) {
            return null;
        }

        RouterDelegateExtractor extractor = stringRouterDelegateExtractorMap.get(serviceExtractor + "RouterDelegateExtractor");
        return extractor;
    }

    /**
     * 创建请求地址
     *
     * @param request
     * @param configDTO
     * @param prefix
     * @param extractorCallback
     * @return
     */
    private String createRequestUrl(HttpServletRequest request, RouterDelegateConfigDTO configDTO, String prefix, RouterDelegateExtractor extractorCallback) {
        String routeUrl = configDTO.getRootPath();
        // 拓展器不为null, 并且有返回结果才使用
        if (extractorCallback != null) {
            String hostUrl = extractorCallback.getRequestRootUrl(request, configDTO, prefix);
            if (hostUrl != null) routeUrl = hostUrl;
        }

        String queryString = request.getQueryString();
//        return routeUrl + request.getRequestURI().replace(prefix, "") +
//                (queryString != null ? "?" + queryString : "");

        // request.getRequestURI() 包括 server.servlet.context-path
        // request.getServletPath() 不包括 server.servlet.context-path
        // http://127.0.0.1/databook-api/graphdb/sj/tianda/openapi/v1/applets?name=ts
        // request.getRequestURI() = /databook-api/graphdb/sj/tianda/openapi/v1/applets
        // request.getServletPath() = /graphdb/sj/tianda/openapi/v1/applets
        String serviceName = configDTO.getServiceName();
        return routeUrl + request.getServletPath().replaceFirst(prefix + "/" + serviceName, "") +
                (queryString != null ? "?" + queryString : "");
    }

    private RequestEntity createRequestEntity(HttpServletRequest request, String url, RouterDelegateExtractor extractorCallBack) throws URISyntaxException, IOException {
        String method = request.getMethod();
        HttpMethod httpMethod = HttpMethod.resolve(method);
        HttpHeaders headers = parseRequestHeader(request, extractorCallBack);
        byte[] body = parseRequestBody(request, extractorCallBack);
        return new RequestEntity<>(body, headers, httpMethod, new URI(url));
    }

    private ResponseEntity<byte[]> route(HttpServletRequest request, String url, RouterDelegateExtractor extractorCallBack) throws IOException, URISyntaxException {
        String method = request.getMethod();
        HttpMethod httpMethod = HttpMethod.resolve(method);
        HttpHeaders headers = parseRequestHeader(request, extractorCallBack);
        byte[] body = parseRequestBody(request, extractorCallBack);
        // 设置请求实体
        HttpEntity<byte[]> httpEntity = new HttpEntity<>(body, headers);
        URI uri = new URI(url);
        return restTemplate.exchange(uri, httpMethod, httpEntity, byte[].class);
    }

    private ResponseEntity<byte[]> route(RequestEntity requestEntity) {
        return restTemplate.exchange(requestEntity, byte[].class);
    }

    /**
     * 处理请求头
     *
     * @param request
     * @param extractorCallBack
     * @return
     */
    private HttpHeaders parseRequestHeader(HttpServletRequest request, RouterDelegateExtractor extractorCallBack) {
        List<String> headerNames = Collections.list(request.getHeaderNames());
        HttpHeaders headers = new HttpHeaders();
        for (String headerName : headerNames) {
            List<String> headerValues = Collections.list(request.getHeaders(headerName));
            for (String headerValue : headerValues) {
                headers.add(headerName, headerValue);
            }
        }

        if (extractorCallBack != null) {
            extractorCallBack.parseRequestHeader(request, headers);
        }

        // 移除请求头accept-encoding, 不移除会导致响应体转成String时会乱码
        headers.remove("accept-encoding");
        return headers;
    }

    /**
     * 处理请求体
     *
     * @param request
     * @param extractorCallBack
     * @return
     * @throws IOException
     */
    private byte[] parseRequestBody(HttpServletRequest request, RouterDelegateExtractor extractorCallBack) throws IOException {
        // 拓展器不为null, 并且返回的结果也不为null才使用返回结果, 否则使用通用处理逻辑
        if (extractorCallBack != null) {
            byte[] body = extractorCallBack.parseRequestBody(request);
            if (body != null) return body;
        }

        InputStream inputStream = request.getInputStream();
        return StreamUtils.copyToByteArray(inputStream);
    }

}

  • 3、暴露接口
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RestController;
import zsoft.gov.datacenter.biztable.common.router.RouterDelegate;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

/**
 * 路由代理接口
 */
@RestController
@RequestMapping
public class RouterDelegateController {

    public final static String DELEGATE_PREFIX = "/delegate";

    @Autowired
    private RouterDelegate routerDelegate;

    /**
     * 路由代理接口
     *
     * @param serviceName
     * @param request
     * @param response
     * @return
     */
    @RequestMapping(value = DELEGATE_PREFIX + "/{serviceName}/**", method = {RequestMethod.GET, RequestMethod.POST, RequestMethod.PUT, RequestMethod.DELETE})
    public ResponseEntity redirect(@PathVariable("serviceName") String serviceName, HttpServletRequest request, HttpServletResponse response) {
        return routerDelegate.redirect(request, response, DELEGATE_PREFIX, serviceName);
    }

}


  • 4、基础配置
#路由代理配置-网关地址
delegate.config.api.rootPath=http://192.168.50.43:7612
#路由代理配置-服务名称, 服务名称和服务网关和服务拓展器一一对应, 服务网关和服务拓展器没有则用default代替
delegate.config.api.serviceName=common,csdn
#路由代理配置-服务网关, 服务名称和服务网关和服务拓展器一一对应, 服务网关和服务拓展器没有则用default代替
delegate.config.api.serviceRoot=default,https://csdn.net
#路由代理配置-服务拓展器, 服务名称和服务网关和服务拓展器一一对应, 服务网关和服务拓展器没有则用default代替
delegate.config.api.serviceExtractor=default,csdnBlog


http://www.kler.cn/a/301663.html

相关文章:

  • Educational Codeforces Round 164 (Rated for Div. 2)(A~E)
  • 论文笔记 SuDORMRF:EFFICIENT NETWORKS FOR UNIVERSAL AUDIO SOURCE SEPARATION
  • MySQL的游标和While循环的详细对比
  • Vue3 provide 和 inject的使用
  • React Native 全栈开发实战班 - 网络与数据之网络请求基础
  • PMP--一、二、三模、冲刺--分类--变更--技巧--特点
  • 【C++开发中XML 文件的妙用】
  • Django+Vue协同过滤算法图书推荐系统的设计与实现
  • 【数字集成电路与系统设计】Chisel/Scala简介与Verilog介绍
  • 【分支-快速排序】
  • Python中如何实现列表的排序
  • 十、组合模式
  • AcWing119 袭击
  • 韩语中的多义词 (치다)柯桥学韩语到蓝天广场附近
  • Python的学习(三十二)---- ctypes库的使用整理
  • LSP协议:打造流动性管理的市场新标杆
  • [前端][js]获取当前正在执行的Javascript脚本文件的路径
  • 项目实现:云备份(一)
  • 【数字集成电路与系统设计】一些Chisel语法的介绍
  • 二、Maven工程的创建--JavaSEJavaEE
  • Element UI按钮组件:构建响应式用户界面的秘诀
  • vue3 ref
  • ffmpeg7.0 AVFrame的分配与释放
  • 使用 DBeaver 创建 MySQL 数据库
  • 第十五届蓝桥杯图形化省赛题目及解析
  • 前端 PDF 预览技巧:标签 vs 插件,如何优雅地展示 PDF 文件