• 一个简单的Http根据规则自动路由


    在日常项目中,有时候会根据一些规则/标识进行Http路由匹配,下面我实现一个简单的Http根据systemCode自动路由;

    Controller

    import com.example.demo.service.GatewayRoutingService;
    import org.apache.commons.lang3.StringUtils;
    import org.slf4j.Logger;
    import org.slf4j.LoggerFactory;
    import org.springframework.beans.factory.annotation.Autowired;
    import org.springframework.web.bind.annotation.RequestMapping;
    import org.springframework.web.bind.annotation.RestController;
    
    import javax.servlet.http.HttpServletRequest;
    import javax.servlet.http.HttpServletResponse;
    
    @RestController
    @RequestMapping("/gateway-routing")
    public class GatewayRoutingController {
        private Logger log = LoggerFactory.getLogger(GatewayRoutingController.class);
    
        @Autowired
        private GatewayRoutingService service;
    
        /**
         * 根据systemCode,自行匹配路由到systemCode对应的域名上
         *
         * @param request
         * @param response
         */
        @RequestMapping("/invoke/**")
        public void invoke(HttpServletRequest request, HttpServletResponse response) {
            String systemCode = request.getHeader("system-code");
            if (StringUtils.isEmpty(systemCode)) {
                // return 404
                response.setStatus(HttpServletResponse.SC_NOT_FOUND);
                return;
            }
    
            try {
    
                // 路由调用
                service.invoke(systemCode, request, response);
            } catch (Exception e) {
                log.info("genericInvoke>>>exception", e);
                throw new RuntimeException("系统错误,请联系管理");
            }
        }
    }
    

    Service

    import com.example.demo.config.RequestInvokeProperties;
    import org.apache.commons.lang3.StringUtils;
    import org.slf4j.Logger;
    import org.slf4j.LoggerFactory;
    import org.springframework.beans.factory.annotation.Autowired;
    import org.springframework.http.HttpMethod;
    import org.springframework.http.client.ClientHttpRequest;
    import org.springframework.http.client.ClientHttpResponse;
    import org.springframework.http.client.SimpleClientHttpRequestFactory;
    import org.springframework.stereotype.Service;
    import org.springframework.util.Assert;
    import org.springframework.util.StreamUtils;
    
    import javax.servlet.ServletOutputStream;
    import javax.servlet.http.HttpServletRequest;
    import javax.servlet.http.HttpServletResponse;
    import java.io.IOException;
    import java.io.InputStream;
    import java.io.OutputStream;
    import java.io.UnsupportedEncodingException;
    import java.net.URI;
    import java.net.URISyntaxException;
    import java.net.URLEncoder;
    import java.util.ArrayList;
    import java.util.Enumeration;
    import java.util.List;
    
    @Service
    public class GatewayRoutingService {
        private Logger log = LoggerFactory.getLogger(GatewayRoutingService.class);
    
        /**
         * 系统域名映射 key :系统唯一标识, value 系统
         */
        @Autowired
        private RequestInvokeProperties properties;
    
        /**
         * 路由调用
         */
        public void invoke(String systemCode, HttpServletRequest request, HttpServletResponse response) throws Exception {
            // 解析获取真实请求路径
            final URI realUrl = this.resolveRealUrl(systemCode, request);
    
            final HttpMethod httpMethod = HttpMethod.resolve(request.getMethod());
            if (httpMethod == null || realUrl == null) {
                throw new RuntimeException("当前请求不合法, 无法转发");
            }
            log.info("routing and forwarding>>>originalUrl = {}, realUrl = {}", request.getRequestURI(), realUrl);
            ClientHttpRequest delegateRequest = new SimpleClientHttpRequestFactory().createRequest(realUrl, httpMethod);
            // set header
            setRequestHeader(request, delegateRequest);
            // set body
            setRequestBody(request, delegateRequest);
    
            // http调用
            try (ClientHttpResponse clientHttpResponse = delegateRequest.execute();
                 ServletOutputStream responseOutputStream = response.getOutputStream()) {
                response.setStatus(clientHttpResponse.getStatusCode().value());
                clientHttpResponse.getHeaders().forEach((key, values) -> {
                    // 处理响应头重复情况,在请求返回时该响应头会出现重复,postman 调用成功,但是实际前端调用会出错
                    if (!"Transfer-Encoding".equalsIgnoreCase(key)) {
                        values.forEach(value -> response.setHeader(key, value));
                    }
                });
                // 拷贝响应流
                ioCopy(clientHttpResponse.getBody(), responseOutputStream, 2024);
            }
        }
    
        /**
         * 拷贝响应流
         *
         * @param in
         * @param out
         */
        private long ioCopy(InputStream in, OutputStream out, int bufferSize) throws IOException {
            Assert.notNull(in, "InputStream is null !");
            Assert.notNull(out, "OutputStream is null !");
    
            long size = 0L;
            try {
                if (bufferSize <= 0) {
                    bufferSize = 2048;
                }
                byte[] buffer = new byte[bufferSize];
                int readSize;
                while ((readSize = in.read(buffer)) != -1) {
                    out.write(buffer, 0, readSize);
                    size += (long) readSize;
                    out.flush();
                }
            } finally {
                in.close();
                out.close();
            }
            return size;
        }
    
        /**
         * 解析获取真实请求路径
         *
         * @param system
         * @param request
         * @return
         */
        private URI resolveRealUrl(String system, HttpServletRequest request) throws URISyntaxException, UnsupportedEncodingException {
            if (properties.getSystemMap() == null || properties.getSystemMap().isEmpty()) {
                return null;
            }
            // 获取真实的请求url:去除前缀 /invoke/
            StringBuilder requestUrl = new StringBuilder(StringUtils.substringAfter(request.getRequestURI(), "/invoke/"));
            // 根据systemCode获取对应的域名
            String domain = properties.getSystemMap().get(system);
            if (StringUtils.isNoneBlank(requestUrl.toString(), domain)) {
                final Enumeration<String> parameterNames = request.getParameterNames();
                StringBuilder uriVariables = new StringBuilder("?");
                while (parameterNames.hasMoreElements()) {
                    // 转义部分特殊字符,如果请求参数里带有 +、空格等不转义请求路由过去会出问题
                    final String parameterName = URLEncoder.encode(parameterNames.nextElement(), "UTF-8");
                    final String parameter = URLEncoder.encode(request.getParameter(parameterName), "UTF-8");
                    uriVariables.append(parameterName).append("=").append(parameter).append("&");
                }
                domain = domain.endsWith("/") ? domain : domain + "/";
                return new URI(domain + requestUrl + (uriVariables.length() == 1 ? "" : uriVariables.substring(0, uriVariables.length() - 1)));
            }
            return null;
        }
    
        /**
         * 设置请求体
         *
         * @throws IOException
         */
        private void setRequestBody(HttpServletRequest request, ClientHttpRequest delegateRequest) throws IOException {
            StreamUtils.copy(request.getInputStream(), delegateRequest.getBody());
        }
    
        /**
         * 设置请求头
         */
        private void setRequestHeader(HttpServletRequest request, ClientHttpRequest delegateRequest) {
            Enumeration<String> headerNames = request.getHeaderNames();
            // 设置请求头
            while (headerNames.hasMoreElements()) {
                String headerName = headerNames.nextElement();
                Enumeration<String> header = request.getHeaders(headerName);
                List<String> arr = new ArrayList<>();
                while (header.hasMoreElements()) {
                    arr.add(header.nextElement());
                }
                delegateRequest.getHeaders().addAll(headerName, arr);
            }
            if (!delegateRequest.getHeaders().containsKey("Content-Type")) {
                delegateRequest.getHeaders().add("Content-Type", "application/json; charset=utf-8");
            }
        }
    }
    

    Properties

    Properties类

    @Data
    @Component
    @ConfigurationProperties(prefix = "request.invoke")
    public class RequestInvokeProperties {
    
        private Map<String, String> systemMap;
    }
    

    Yaml

    request:
      invoke:
        systemMap:
          baidu: http://www.baidu.com/
    

    测试

    路由到baidu

    请求:

    curl --location --request GET 'http://localhost:8080/gateway-routing/invoke/s?wd=spring' \
    --header 'system-code: baidu' \
    --header 'User-Agent: Apifox/1.0.0 (https://apifox.com)' \
    --header 'Content-Type: application/json' \
    --header 'Accept: */*' \
    --header 'Host: localhost:8080' \
    --header 'Connection: keep-alive' \
    --data-raw ''
    

    实际请求:

    http://www.baidu.com/s?wd=spring

  • 相关阅读:
    阿里本地生活全域日志平台 Xlog 的思考与实践
    Linux的socket通信
    【深度学习】PyTorch深度学习笔记02-线性模型
    如何向PDB文件添加双键
    记录一次WhatTheFuck经历
    [LeetCode85双周赛] [滑动窗口] [差分数组] [并查集]
    如何把在本地存储sessionStorage.setItem()上存的值渲染在输入框中js
    16.Hystrix 实例(springcloud)
    Ubuntu中如何设置IP地址
    CSP-S2023 总结
  • 原文地址:https://blog.csdn.net/qq_33375499/article/details/143227093