You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多
文档控制台
免费开始使用

如何基于SOAP消息体字段配置Spring Cloud Gateway的RouteLocator路由?

好问题!Spring Cloud Gateway确实没有内置针对SOAP消息体的路由断言工厂,不过我们可以通过自定义RoutePredicateFactory来实现这个需求。下面是一步步的实现方案:

实现基于SOAP消息体字段的Spring Cloud Gateway路由

1. 自定义SOAP Body路由断言工厂

首先,我们需要创建一个自定义的RoutePredicateFactory,用来解析SOAP请求体并提取指定字段的值,当和预期值匹配时触发路由。

import org.springframework.cloud.gateway.handler.predicate.AbstractRoutePredicateFactory;
import org.springframework.cloud.gateway.handler.predicate.GatewayPredicate;
import org.springframework.http.server.reactive.ServerHttpRequest;
import org.springframework.stereotype.Component;
import org.springframework.web.server.ServerWebExchange;
import javax.xml.namespace.NamespaceContext;
import javax.xml.xpath.XPath;
import javax.xml.xpath.XPathFactory;
import org.w3c.dom.Document;
import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import java.io.ByteArrayInputStream;
import java.util.Map;
import java.util.function.Predicate;

@Component
public class SoapBodyRoutePredicateFactory extends AbstractRoutePredicateFactory<SoapBodyRoutePredicateFactory.Config> {

    public SoapBodyRoutePredicateFactory() {
        super(Config.class);
    }

    @Override
    public Predicate<ServerWebExchange> apply(Config config) {
        return exchange -> {
            // 缓存请求体,避免流式请求体被读取后无法重复使用
            return ServerWebExchangeUtils.cacheRequestBody(exchange, (serverHttpRequest) -> {
                return serverHttpRequest.getBody()
                        .collectList()
                        .map(dataBuffers -> {
                            // 将DataBuffer集合转换为字节数组
                            byte[] bytes = new byte[dataBuffers.stream().mapToInt(dataBuffer -> dataBuffer.readableByteCount()).sum()];
                            int offset = 0;
                            for (var buffer : dataBuffers) {
                                int readable = buffer.readableByteCount();
                                buffer.read(bytes, offset, readable);
                                offset += readable;
                                buffer.release();
                            }

                            try {
                                // 启用命名空间支持的XML解析器
                                DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
                                factory.setNamespaceAware(true);
                                DocumentBuilder builder = factory.newDocumentBuilder();
                                Document doc = builder.parse(new ByteArrayInputStream(bytes));

                                // 配置XPath的命名空间上下文,匹配SOAP消息中的命名空间
                                XPath xpath = XPathFactory.newInstance().newXPath();
                                xpath.setNamespaceContext(new NamespaceContext() {
                                    @Override
                                    public String getNamespaceURI(String prefix) {
                                        if ("ser".equals(prefix)) {
                                            return "http://service.viettel.com/";
                                        } else if ("soapenv".equals(prefix)) {
                                            return "http://schemas.xmlsoap.org/soap/envelope/";
                                        }
                                        return null;
                                    }

                                    @Override
                                    public String getPrefix(String namespaceURI) {
                                        return null;
                                    }

                                    @Override
                                    public java.util.Iterator<?> getPrefixes(String namespaceURI) {
                                        return null;
                                    }
                                });

                                // 提取目标节点的文本值并和预期值对比
                                String nodeValue = xpath.evaluate(config.getNodeXpath(), doc);
                                return config.getExpectedValue().equals(nodeValue);
                            } catch (Exception e) {
                                // 解析失败或节点不存在时,不匹配当前路由
                                return false;
                            }
                        });
            });
        };
    }

    // 配置类,用于接收路由配置中的参数
    public static class Config {
        private String nodeXpath;
        private String expectedValue;

        public String getNodeXpath() {
            return nodeXpath;
        }

        public void setNodeXpath(String nodeXpath) {
            this.nodeXpath = nodeXpath;
        }

        public String getExpectedValue() {
            return expectedValue;
        }

        public void setExpectedValue(String expectedValue) {
            this.expectedValue = expectedValue;
        }
    }
}

2. 配置路由规则

接下来在application.yml中配置路由,使用我们自定义的断言工厂:

spring:
  cloud:
    gateway:
      routes:
        # 匹配msg=John的SOAP请求,路由到指定服务
        - id: soap-john-route
          uri: http://john-specific-service:8080
          predicates:
            - name: SoapBody
              args:
                nodeXpath: "/soapenv:Envelope/soapenv:Body/ser:sayHello/ser:msg/text()"
                expectedValue: "John"
        # 默认SOAP请求路由
        - id: default-soap-route
          uri: http://default-soap-service:8080
          predicates:
            - Path=/soap/**

3. 关键注意事项

  • 命名空间处理:SOAP消息大多包含命名空间,必须在XPath的命名空间上下文中正确配置,否则无法定位到目标节点。你需要根据自己的SOAP消息命名空间调整代码中的配置。
  • 请求体缓存:Spring Cloud Gateway的请求体是流式的,必须用ServerWebExchangeUtils.cacheRequestBody缓存后才能重复读取,避免后续流程无法获取请求体。
  • 性能优化:DOM解析XML会带来一定性能开销,如果你的SOAP请求量极大,可以考虑使用更轻量的StAX解析方式替代。
  • 请求大小限制:如果SOAP消息体过大,需要调整Gateway的内存缓存上限,比如配置spring.cloud.gateway.httpclient.max-in-memory-size=10MB

4. 测试验证

用curl发送示例SOAP请求测试路由效果:

curl -X POST \
  http://your-gateway-host:8080/soap \
  -H 'Content-Type: text/xml' \
  -d '<soapenv:Envelope xmlns:soapenv="http://schemas.xmlsoap.org/soap/envelope/" xmlns:ser="http://service.viettel.com/"> 
<soapenv:Header/> 
<soapenv:Body> 
<ser:sayHello> 
<msg>John</msg> 
</ser:sayHello> 
</soapenv:Body> 
</soapenv:Envelope>'

msg字段为John时,请求会路由到指定服务;其他值则会走默认路由。

内容的提问来源于stack exchange,提问作者Cuong T.Tran

火山引擎 最新活动