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

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"})@Autowiredpublic RestTemplate restTemplate(@Qualifier("fetchApiClientHttpRequestFactory") ClientHttpRequestFactory factory) {RestTemplate restTemplate = new RestTemplate(factory);restTemplate.setErrorHandler(new DefaultResponseErrorHandler() {@Overridepublic 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;@Resourceprivate RouterDelegateProperties properties;@Resourceprivate Map<String, RouterDelegateExtractor> stringRouterDelegateExtractorMap;/*** 初始化配置类** @param args* @throws Exception*/@Overridepublic 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 {// 创建urlString 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 upstreamHttpHeaders 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/appletsString 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";@Autowiredprivate 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

相关文章:

  • 北京网站建设多少钱?
  • 辽宁网页制作哪家好_网站建设
  • 高端品牌网站建设_汉中网站制作
  • 系统架构师-ERP+集成
  • lwip移植-基于类rtosw5500
  • 实例:如何统计当前主机的连接状态和连接数
  • 实习项目|苍穹外卖|day8
  • 【机器学习】和【人工智能】在物理学领域的应用以及代码案例分析
  • SpringMVC使用:类型转换数据格式化数据验证
  • Linux 路径写法,目录相关命令及文件管理命令
  • 软件工程知识点总结(1):软件工程概述
  • Rust : 从事量化的生态现状与前景
  • 小程序多个set-cookie无法处理
  • C#发送正文带图片带附件的邮件
  • webgl-插值渲染原理理解
  • YC教父的创始人模式VS职业经理人模式:AI时代的独立开发者崛起
  • 4-1.Android Camera 之 CameraInfo 编码模板(前后置摄像头理解、摄像头图像的自然方向理解)
  • SpringBoot和Mybatis框架怎么防止SQL注入
  • $translatePartialLoader加载失败及解决方式
  • Bootstrap JS插件Alert源码分析
  • Debian下无root权限使用Python访问Oracle
  • linux安装openssl、swoole等扩展的具体步骤
  • rc-form之最单纯情况
  • React系列之 Redux 架构模式
  • Spring声明式事务管理之一:五大属性分析
  • 纯 javascript 半自动式下滑一定高度,导航栏固定
  • 前端存储 - localStorage
  • 小程序上传图片到七牛云(支持多张上传,预览,删除)
  • 应用生命周期终极 DevOps 工具包
  • hi-nginx-1.3.4编译安装
  • 函数计算新功能-----支持C#函数
  • 教程:使用iPhone相机和openCV来完成3D重建(第一部分) ...
  • 资深实践篇 | 基于Kubernetes 1.61的Kubernetes Scheduler 调度详解 ...
  • ​​​​​​​GitLab 之 GitLab-Runner 安装,配置与问题汇总
  • ​iOS实时查看App运行日志
  • ​LeetCode解法汇总2583. 二叉树中的第 K 大层和
  • ​人工智能书单(数学基础篇)
  • # Redis 入门到精通(七)-- redis 删除策略
  • (C#)一个最简单的链表类
  • (C语言)深入理解指针2之野指针与传值与传址与assert断言
  • (LeetCode C++)盛最多水的容器
  • (ZT)一个美国文科博士的YardLife
  • (全部习题答案)研究生英语读写教程基础级教师用书PDF|| 研究生英语读写教程提高级教师用书PDF
  • (十三)Flask之特殊装饰器详解
  • (一)appium-desktop定位元素原理
  • (一)eclipse Dynamic web project 工程目录以及文件路径问题
  • (转)linux自定义开机启动服务和chkconfig使用方法
  • (转)甲方乙方——赵民谈找工作
  • (最新)华为 2024 届秋招-硬件技术工程师-单板硬件开发—机试题—(共12套)(每套四十题)
  • ******IT公司面试题汇总+优秀技术博客汇总
  • .DFS.
  • .Net 4.0并行库实用性演练
  • .NET WPF 抖动动画
  • .NET 漏洞分析 | 某ERP系统存在SQL注入
  • .NET中的Event与Delegates,从Publisher到Subscriber的衔接!
  • .sh文件怎么运行_创建优化的Go镜像文件以及踩过的坑
  • /usr/bin/python: can't decompress data; zlib not available 的异常处理
  • @SpringBootConfiguration重复加载报错