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

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

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

  • Controller
  • Service
  • Properties
    • Properties类
    • Yaml
  • 测试
    • 路由到baidu

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


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

相关文章:

  • Linux_03 Linux 常用命令——find、ls
  • 外包干了7天,技术明显退步。。。。。
  • 大语言模型(LLM)快速理解
  • 数据结构-希尔排序(ShellSort)笔记
  • AcWing 1303:斐波那契前 n 项和 ← 矩阵快速幂加速递推
  • facebook账号类型有哪些?
  • 沈阳乐晟睿浩科技有限公司抖音小店实力电商新星
  • c语言水仙花,超简单讲解
  • Java方法重写
  • C语言的知识框架
  • CSS秘籍-高效样式技巧
  • 【成都新篇】龙信科技电子取证实验室,引领科技取证新时代
  • PIDNet(语义分割)排坑
  • HarmonyOS生命周期
  • 基于局部近似的模型解释方法
  • 【数据结构】ArrayList的模拟实现--Java
  • android12属性设置
  • 使用 NCC 和 PKG 打包 Node.js 项目为可执行文件(Linux ,macOS,Windows)
  • 设计一个灵活的RPC架构
  • AI代币是什么?AI与Web3结合的未来方向在哪里?
  • Transformer-BiGRU多特征输入时间序列预测(Pytorch)
  • WSGI、uwsgi与uWSGI
  • 【深度学习】用LSTM写诗,生成式的方式写诗系列之一
  • 下一代「自动化测试框架」WebdriverIO
  • STM32--STM32 微控制器详解
  • unity3d————Mathf.Lerp() 函数详解