route-forward springboot实现路由转发步伐

打印 上一主题 下一主题

主题 1061|帖子 1061|积分 3183

马上注册,结交更多好友,享用更多功能,让你轻松玩转社区。

您需要 登录 才可以下载或查看,没有账号?立即注册

x
目录

   媒介:想实现一个轻量级的接口反向代理和转发的一个接口服务,可以通过这个服务做一些须要认证才华访问的接口给到前端利用,这样就实现了一种认证可以调用多种第三方体系的服务。
  基本逻辑就是将请求的请求方式、请求头、请求体提取出来,将这些信息转发到别的一个接口


  • 1.1 配置类
  1. import org.springframework.boot.context.properties.ConfigurationProperties;
  2. import org.springframework.context.annotation.Configuration;
  3. import org.springframework.stereotype.Component;
  4. import java.util.List;
  5. /**
  6. * 路由代理配置
  7. */
  8. @Configuration
  9. @ConfigurationProperties(prefix = "delegate.config.api", ignoreUnknownFields = false)
  10. public class RouterDelegateProperties {
  11.     /**
  12.      * 网关地址
  13.      */
  14.     String rootPath;
  15.     /**
  16.      * 服务名称, 服务名称和服务网关和服务拓展器一一对应, 服务网关和服务拓展器没有则用default代替
  17.      */
  18.     List<String> serviceName;
  19.     /**
  20.      * 服务网关, 服务名称和服务网关和服务拓展器一一对应, 服务网关和服务拓展器没有则用default代替
  21.      */
  22.     List<String> serviceRoot;
  23.     /**
  24.      * 服务拓展器, 服务名称和服务网关和服务拓展器一一对应, 服务网关和服务拓展器没有则用default代替
  25.      */
  26.     List<String> serviceExtractor;
  27.     public String getRootPath() {
  28.         return rootPath;
  29.     }
  30.     public void setRootPath(String rootPath) {
  31.         this.rootPath = rootPath;
  32.     }
  33.     public List<String> getServiceName() {
  34.         return serviceName;
  35.     }
  36.     public void setServiceName(List<String> serviceName) {
  37.         this.serviceName = serviceName;
  38.     }
  39.     public List<String> getServiceRoot() {
  40.         return serviceRoot;
  41.     }
  42.     public void setServiceRoot(List<String> serviceRoot) {
  43.         this.serviceRoot = serviceRoot;
  44.     }
  45.     public List<String> getServiceExtractor() {
  46.         return serviceExtractor;
  47.     }
  48.     public void setServiceExtractor(List<String> serviceExtractor) {
  49.         this.serviceExtractor = serviceExtractor;
  50.     }
  51. }
复制代码


  • 1.2 实体DTO
  1. import java.io.Serializable;
  2. /**
  3. * 代理路由配置 DTO
  4. */
  5. public class RouterDelegateConfigDTO implements Serializable {
  6.     private static final long serialVersionUID = 1L;
  7.     /**
  8.      * 网关地址
  9.      */
  10.     private String rootPath;
  11.     /**
  12.      * 服务名称
  13.      */
  14.     private String serviceName;
  15.     /**
  16.      * 服务名称地址
  17.      */
  18.     private String serviceRoot;
  19.     /**
  20.      * 服务名称处理器
  21.      */
  22.     private String serviceExtractor;
  23.     public String getRootPath() {
  24.         return rootPath;
  25.     }
  26.     public void setRootPath(String rootPath) {
  27.         this.rootPath = rootPath;
  28.     }
  29.     public String getServiceName() {
  30.         return serviceName;
  31.     }
  32.     public void setServiceName(String serviceName) {
  33.         this.serviceName = serviceName;
  34.     }
  35.     public String getServiceRoot() {
  36.         return serviceRoot;
  37.     }
  38.     public void setServiceRoot(String serviceRoot) {
  39.         this.serviceRoot = serviceRoot;
  40.     }
  41.     public String getServiceExtractor() {
  42.         return serviceExtractor;
  43.     }
  44.     public void setServiceExtractor(String serviceExtractor) {
  45.         this.serviceExtractor = serviceExtractor;
  46.     }
  47. }
复制代码


  • 1.3 路由代理拓展器
  1. import org.springframework.http.HttpHeaders;
  2. import zsoft.gov.datacenter.biztable.common.dto.router.RouterDelegateConfigDTO;
  3. import javax.servlet.http.HttpServletRequest;
  4. /**
  5. * 路由代理拓展器
  6. */
  7. public interface RouterDelegateExtractor {
  8.     /**
  9.      * 处理请求url, 返回null则使用通用处理逻辑
  10.      *
  11.      * @param request   请求体对象
  12.      * @param configDTO 服务配置对象
  13.      * @param prefix    代理前缀
  14.      * @return
  15.      */
  16.     String getRequestRootUrl(HttpServletRequest request, RouterDelegateConfigDTO configDTO, String prefix);
  17.     /**
  18.      * 处理请求头
  19.      *
  20.      * @param request 请求体对象
  21.      * @param headers 请求头
  22.      */
  23.     void parseRequestHeader(HttpServletRequest request, HttpHeaders headers);
  24.     /**
  25.      * 处理请求体, 返回null则使用通用处理逻辑
  26.      *
  27.      * @param request 请求体对象
  28.      * @return
  29.      */
  30.     byte[] parseRequestBody(HttpServletRequest request);
  31. }
复制代码


  • 1.4 请求对象 RestTemplate
  1. import org.springframework.beans.factory.annotation.Autowired;
  2. import org.springframework.beans.factory.annotation.Qualifier;
  3. import org.springframework.context.annotation.Bean;
  4. import org.springframework.context.annotation.Configuration;
  5. import org.springframework.http.client.ClientHttpRequestFactory;
  6. import org.springframework.http.client.ClientHttpResponse;
  7. import org.springframework.http.client.SimpleClientHttpRequestFactory;
  8. import org.springframework.http.converter.HttpMessageConverter;
  9. import org.springframework.http.converter.StringHttpMessageConverter;
  10. import org.springframework.web.client.DefaultResponseErrorHandler;
  11. import org.springframework.web.client.RestTemplate;
  12. import java.io.IOException;
  13. import java.nio.charset.Charset;
  14. import java.util.List;
  15. @Configuration
  16. public class FetchApiRestTemplateConfig {
  17.     @Bean({"fetchApiRestTemplate"})
  18.     @Autowired
  19.     public RestTemplate restTemplate(@Qualifier("fetchApiClientHttpRequestFactory") ClientHttpRequestFactory factory) {
  20.         RestTemplate restTemplate = new RestTemplate(factory);
  21.         restTemplate.setErrorHandler(new DefaultResponseErrorHandler() {
  22.             @Override
  23.             public void handleError(ClientHttpResponse response) throws IOException {
  24. //                if (response.getRawStatusCode() != 401 && response.getRawStatusCode() != 404) {
  25. //                    super.handleError(response);
  26. //                }
  27.                 // 处理返回 4xx 的状态码时不抛出异常
  28.                 if (!response.getStatusCode().is4xxClientError()) {
  29.                     super.handleError(response);
  30.                 }
  31.             }
  32.         });
  33.         // 中文乱码问题
  34.         List<HttpMessageConverter<?>> httpMessageConverters = restTemplate.getMessageConverters();
  35.         httpMessageConverters.stream().forEach(httpMessageConverter -> {
  36.             if (httpMessageConverter instanceof StringHttpMessageConverter) {
  37.                 StringHttpMessageConverter messageConverter = (StringHttpMessageConverter) httpMessageConverter;
  38.                 messageConverter.setDefaultCharset(Charset.forName("UTF-8"));
  39.             }
  40.         });
  41.         return restTemplate;
  42.     }
  43.     @Bean({"fetchApiClientHttpRequestFactory"})
  44.     public ClientHttpRequestFactory simpleClientHttpRequestFactory() {
  45.         SimpleClientHttpRequestFactory factory = new SimpleClientHttpRequestFactory();
  46.         factory.setReadTimeout(1000 * 50); // 读取超时(毫秒)
  47.         factory.setConnectTimeout(1000 * 10); // 连接超时(毫秒)
  48.         return factory;
  49.     }
  50. }
复制代码


  • 2、焦点转发代码
  1. import org.apache.commons.collections.CollectionUtils;
  2. import org.apache.commons.lang3.StringUtils;
  3. import org.slf4j.Logger;
  4. import org.slf4j.LoggerFactory;
  5. import org.springframework.beans.factory.annotation.Qualifier;
  6. import org.springframework.boot.ApplicationArguments;
  7. import org.springframework.boot.ApplicationRunner;
  8. import org.springframework.http.*;
  9. import org.springframework.stereotype.Service;
  10. import org.springframework.util.StreamUtils;
  11. import org.springframework.web.client.RestTemplate;
  12. import zsoft.gov.datacenter.biztable.common.config.RouterDelegateProperties;
  13. import zsoft.gov.datacenter.biztable.common.dto.router.RouterDelegateConfigDTO;
  14. import zsoft.gov.datacenter.biztable.common.response.Result;
  15. import javax.annotation.Resource;
  16. import javax.servlet.http.HttpServletRequest;
  17. import javax.servlet.http.HttpServletResponse;
  18. import java.io.IOException;
  19. import java.io.InputStream;
  20. import java.net.URI;
  21. import java.net.URISyntaxException;
  22. import java.util.Collections;
  23. import java.util.List;
  24. import java.util.Map;
  25. import java.util.concurrent.ConcurrentHashMap;
  26. /**
  27. * 路由代理
  28. */
  29. @Service
  30. public class RouterDelegate implements ApplicationRunner {
  31.     protected Logger logger = LoggerFactory.getLogger(getClass());
  32.     private static Map<String, RouterDelegateConfigDTO> configMap;
  33.     @Resource
  34.     @Qualifier("fetchApiRestTemplate")
  35.     private RestTemplate restTemplate;
  36.     @Resource
  37.     private RouterDelegateProperties properties;
  38.     @Resource
  39.     private Map<String, RouterDelegateExtractor> stringRouterDelegateExtractorMap;
  40.     /**
  41.      * 初始化配置类
  42.      *
  43.      * @param args
  44.      * @throws Exception
  45.      */
  46.     @Override
  47.     public void run(ApplicationArguments args) throws Exception {
  48.         boolean intiFlag = false;
  49.         logger.info(">>> -----开始初始化路由代理配置类!");
  50.         /**
  51.          * 最终configMap效果
  52.          * {
  53.          *     服务名称: {
  54.          *         rootPath: "系统网关地址",
  55.          *         serviceName: "服务名称",
  56.          *         serviceRoot: "服务网关",
  57.          *         serviceExtractor: "服务拓展器",
  58.          *     }
  59.          * }
  60.          */
  61.         String rootPath = properties.getRootPath();
  62.         List<String> serviceName = properties.getServiceName();
  63.         List<String> serviceRoot = properties.getServiceRoot();
  64.         List<String> serviceExtractor = properties.getServiceExtractor();
  65.         // 服务名称, 服务名称和服务网关和服务处理器一一对应, 如果没有对应的服务网关和服务处理器, 则用英文逗号隔开
  66.         if (StringUtils.isNotBlank(rootPath)
  67.                 && CollectionUtils.isNotEmpty(serviceName)
  68.                 && CollectionUtils.isNotEmpty(serviceExtractor)
  69.                 && CollectionUtils.isNotEmpty(serviceRoot)
  70.                 && serviceName.size() == serviceRoot.size()
  71.                 && serviceName.size() == serviceExtractor.size()) {
  72.             intiFlag = true;
  73.             // 初始化大小避免扩容
  74.             int initialCapacity = (int) (serviceName.size() / 0.75) + 1;
  75.             configMap = new ConcurrentHashMap<>(initialCapacity);
  76.             for (int i = 0; i < serviceName.size(); i++) {
  77.                 RouterDelegateConfigDTO dto = new RouterDelegateConfigDTO();
  78.                 String serName = serviceName.get(i);
  79.                 dto.setRootPath(rootPath);
  80.                 dto.setServiceName(serName);
  81.                 // default 是占位符, 配置成default相当于没有配置
  82.                 dto.setServiceRoot("default".equals(serviceRoot.get(i)) ? null : serviceRoot.get(i));
  83.                 dto.setServiceExtractor("default".equals(serviceExtractor.get(i)) ? null : serviceExtractor.get(i));
  84.                 configMap.put(serName, dto);
  85.             }
  86.         }
  87.         if (intiFlag) logger.info(">>> 初始化路由代理配置类成功!");
  88.         else logger.error(">>> 初始化路由代理配置类失败!");
  89.     }
  90.     public ResponseEntity<byte[]> redirect(HttpServletRequest request, HttpServletResponse response, String prefix, String serviceName) {
  91.         String requestURI = request.getRequestURI();
  92.         RouterDelegateConfigDTO currentConfig = getCurrentServiceConfig(serviceName);
  93.         if (currentConfig == null) {
  94.             return buildErrorResponseEntity("SERVICE ERROR! 服务不存在!", HttpStatus.NOT_FOUND);
  95.         }
  96.         RouterDelegateExtractor extractorCallBack = getRouterDelegateExtractor(serviceName);
  97.         try {
  98.             // 创建url
  99.             String redirectUrl = createRequestUrl(request, currentConfig, prefix, extractorCallBack);
  100.             logger.info(">>> redirectUrl代理后的完整地址: [{}]", redirectUrl);
  101.             RequestEntity requestEntity = createRequestEntity(request, redirectUrl, extractorCallBack);
  102.             // return route(request, redirectUrl, extractorCallBack);
  103.             ResponseEntity<byte[]> result = route(requestEntity);
  104.             if (result.getHeaders() != null && result.getHeaders().containsKey(HttpHeaders.TRANSFER_ENCODING)) {
  105.                 // 移除响应头 Transfer-Encoding, 因为高版本的nginx会自动添加该响应头, 多个响应值nginx会报错
  106.                 // 多个响应值nginx报错: *6889957 upstream sent duplicate header line: "Transfer-Encoding: chunked", previous value: "Transfer-Encoding: chunked" while reading response header from upstream
  107.                 HttpHeaders headers = HttpHeaders.writableHttpHeaders(result.getHeaders());
  108.                 headers.remove(HttpHeaders.TRANSFER_ENCODING);
  109.             }
  110.             //logger.info(">>> [{}] 代理成功, 请求耗时: [{}]", requestURI, System.currentTimeMillis() - l1);
  111.             return result;
  112.         } catch (Exception e) {
  113.             logger.error("REDIRECT ERROR", e);
  114.             //logger.error(">>> [{}] 代理失败, 请求耗时: [{}]", requestURI, System.currentTimeMillis() - l1);
  115.             return buildErrorResponseEntity("REDIRECT ERROR! " + e.getMessage(), HttpStatus.INTERNAL_SERVER_ERROR);
  116.         }
  117.     }
  118.     private ResponseEntity buildErrorResponseEntity(String msg, HttpStatus httpStatus) {
  119.         HttpHeaders headers = new HttpHeaders();
  120.         headers.setContentType(MediaType.APPLICATION_JSON);
  121.         Result body = Result.build(httpStatus.value(), msg);
  122.         return new ResponseEntity(body, headers, httpStatus);
  123.     }
  124.     /**
  125.      * 获取当前服务配置
  126.      *
  127.      * @param serviceName
  128.      * @return
  129.      */
  130.     public RouterDelegateConfigDTO getCurrentServiceConfig(String serviceName) {
  131.         if (configMap == null || !configMap.containsKey(serviceName)) {
  132.             return null;
  133.         }
  134.         return configMap.get(serviceName);
  135.     }
  136.     /**
  137.      * 获取当前路由服务拓展器
  138.      *
  139.      * @param serviceName
  140.      * @return
  141.      */
  142.     private RouterDelegateExtractor getRouterDelegateExtractor(String serviceName) {
  143.         RouterDelegateConfigDTO currentConfig = getCurrentServiceConfig(serviceName);
  144.         if (currentConfig == null) {
  145.             return null;
  146.         }
  147.         String serviceExtractor = currentConfig.getServiceExtractor();
  148.         if (StringUtils.isBlank(serviceExtractor)) {
  149.             return null;
  150.         }
  151.         RouterDelegateExtractor extractor = stringRouterDelegateExtractorMap.get(serviceExtractor + "RouterDelegateExtractor");
  152.         return extractor;
  153.     }
  154.     /**
  155.      * 创建请求地址
  156.      *
  157.      * @param request
  158.      * @param configDTO
  159.      * @param prefix
  160.      * @param extractorCallback
  161.      * @return
  162.      */
  163.     private String createRequestUrl(HttpServletRequest request, RouterDelegateConfigDTO configDTO, String prefix, RouterDelegateExtractor extractorCallback) {
  164.         String routeUrl = configDTO.getRootPath();
  165.         // 拓展器不为null, 并且有返回结果才使用
  166.         if (extractorCallback != null) {
  167.             String hostUrl = extractorCallback.getRequestRootUrl(request, configDTO, prefix);
  168.             if (hostUrl != null) routeUrl = hostUrl;
  169.         }
  170.         String queryString = request.getQueryString();
  171. //        return routeUrl + request.getRequestURI().replace(prefix, "") +
  172. //                (queryString != null ? "?" + queryString : "");
  173.         // request.getRequestURI() 包括 server.servlet.context-path
  174.         // request.getServletPath() 不包括 server.servlet.context-path
  175.         // http://127.0.0.1/databook-api/graphdb/sj/tianda/openapi/v1/applets?name=ts
  176.         // request.getRequestURI() = /databook-api/graphdb/sj/tianda/openapi/v1/applets
  177.         // request.getServletPath() = /graphdb/sj/tianda/openapi/v1/applets
  178.         String serviceName = configDTO.getServiceName();
  179.         return routeUrl + request.getServletPath().replaceFirst(prefix + "/" + serviceName, "") +
  180.                 (queryString != null ? "?" + queryString : "");
  181.     }
  182.     private RequestEntity createRequestEntity(HttpServletRequest request, String url, RouterDelegateExtractor extractorCallBack) throws URISyntaxException, IOException {
  183.         String method = request.getMethod();
  184.         HttpMethod httpMethod = HttpMethod.resolve(method);
  185.         HttpHeaders headers = parseRequestHeader(request, extractorCallBack);
  186.         byte[] body = parseRequestBody(request, extractorCallBack);
  187.         return new RequestEntity<>(body, headers, httpMethod, new URI(url));
  188.     }
  189.     private ResponseEntity<byte[]> route(HttpServletRequest request, String url, RouterDelegateExtractor extractorCallBack) throws IOException, URISyntaxException {
  190.         String method = request.getMethod();
  191.         HttpMethod httpMethod = HttpMethod.resolve(method);
  192.         HttpHeaders headers = parseRequestHeader(request, extractorCallBack);
  193.         byte[] body = parseRequestBody(request, extractorCallBack);
  194.         // 设置请求实体
  195.         HttpEntity<byte[]> httpEntity = new HttpEntity<>(body, headers);
  196.         URI uri = new URI(url);
  197.         return restTemplate.exchange(uri, httpMethod, httpEntity, byte[].class);
  198.     }
  199.     private ResponseEntity<byte[]> route(RequestEntity requestEntity) {
  200.         return restTemplate.exchange(requestEntity, byte[].class);
  201.     }
  202.     /**
  203.      * 处理请求头
  204.      *
  205.      * @param request
  206.      * @param extractorCallBack
  207.      * @return
  208.      */
  209.     private HttpHeaders parseRequestHeader(HttpServletRequest request, RouterDelegateExtractor extractorCallBack) {
  210.         List<String> headerNames = Collections.list(request.getHeaderNames());
  211.         HttpHeaders headers = new HttpHeaders();
  212.         for (String headerName : headerNames) {
  213.             List<String> headerValues = Collections.list(request.getHeaders(headerName));
  214.             for (String headerValue : headerValues) {
  215.                 headers.add(headerName, headerValue);
  216.             }
  217.         }
  218.         if (extractorCallBack != null) {
  219.             extractorCallBack.parseRequestHeader(request, headers);
  220.         }
  221.         // 移除请求头accept-encoding, 不移除会导致响应体转成String时会乱码
  222.         headers.remove("accept-encoding");
  223.         return headers;
  224.     }
  225.     /**
  226.      * 处理请求体
  227.      *
  228.      * @param request
  229.      * @param extractorCallBack
  230.      * @return
  231.      * @throws IOException
  232.      */
  233.     private byte[] parseRequestBody(HttpServletRequest request, RouterDelegateExtractor extractorCallBack) throws IOException {
  234.         // 拓展器不为null, 并且返回的结果也不为null才使用返回结果, 否则使用通用处理逻辑
  235.         if (extractorCallBack != null) {
  236.             byte[] body = extractorCallBack.parseRequestBody(request);
  237.             if (body != null) return body;
  238.         }
  239.         InputStream inputStream = request.getInputStream();
  240.         return StreamUtils.copyToByteArray(inputStream);
  241.     }
  242. }
复制代码


  • 3、袒露接口
  1. import org.springframework.beans.factory.annotation.Autowired;
  2. import org.springframework.http.ResponseEntity;
  3. import org.springframework.web.bind.annotation.PathVariable;
  4. import org.springframework.web.bind.annotation.RequestMapping;
  5. import org.springframework.web.bind.annotation.RequestMethod;
  6. import org.springframework.web.bind.annotation.RestController;
  7. import zsoft.gov.datacenter.biztable.common.router.RouterDelegate;
  8. import javax.servlet.http.HttpServletRequest;
  9. import javax.servlet.http.HttpServletResponse;
  10. /**
  11. * 路由代理接口
  12. */
  13. @RestController
  14. @RequestMapping
  15. public class RouterDelegateController {
  16.     public final static String DELEGATE_PREFIX = "/delegate";
  17.     @Autowired
  18.     private RouterDelegate routerDelegate;
  19.     /**
  20.      * 路由代理接口
  21.      *
  22.      * @param serviceName
  23.      * @param request
  24.      * @param response
  25.      * @return
  26.      */
  27.     @RequestMapping(value = DELEGATE_PREFIX + "/{serviceName}/**", method = {RequestMethod.GET, RequestMethod.POST, RequestMethod.PUT, RequestMethod.DELETE})
  28.     public ResponseEntity redirect(@PathVariable("serviceName") String serviceName, HttpServletRequest request, HttpServletResponse response) {
  29.         return routerDelegate.redirect(request, response, DELEGATE_PREFIX, serviceName);
  30.     }
  31. }
复制代码


  • 4、底子配置
  1. #路由代理配置-网关地址
  2. delegate.config.api.rootPath=http://192.168.50.43:7612
  3. #路由代理配置-服务名称, 服务名称和服务网关和服务拓展器一一对应, 服务网关和服务拓展器没有则用default代替
  4. delegate.config.api.serviceName=common,csdn
  5. #路由代理配置-服务网关, 服务名称和服务网关和服务拓展器一一对应, 服务网关和服务拓展器没有则用default代替
  6. delegate.config.api.serviceRoot=default,https://csdn.net
  7. #路由代理配置-服务拓展器, 服务名称和服务网关和服务拓展器一一对应, 服务网关和服务拓展器没有则用default代替
  8. delegate.config.api.serviceExtractor=default,csdnBlog
复制代码
免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!更多信息从访问主页:qidao123.com:ToB企服之家,中国第一个企服评测及商务社交产业平台。
回复

使用道具 举报

0 个回复

倒序浏览

快速回复

您需要登录后才可以回帖 登录 or 立即注册

本版积分规则

西河刘卡车医

论坛元老
这个人很懒什么都没写!
快速回复 返回顶部 返回列表