You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Spring集成jPOS时,HTTP请求数据传递至ISO8583过滤器的问题求助

处理ISO8583消息时Spring请求上下文传递至jPOS过滤器的问题

背景

我正在开发基于Spring的应用,使用jPOS处理ISO8583消息,希望允许通过JavaScript或Python脚本动态修改出入站ISO消息,为此搭建了脚本执行机制,但在尝试将Spring控制器接收的HTTP请求数据传递到jPOS过滤器时遇到了上下文不匹配的问题。

核心代码实现

MessageModifyFilter.java

import jakarta.servlet.http.HttpServletRequest;
import lombok.extern.slf4j.Slf4j;
import org.jpos.core.Configurable;
import org.jpos.core.Configuration;
import org.jpos.core.ConfigurationException;
import org.jpos.iso.ISOChannel;
import org.jpos.iso.ISOException;
import org.jpos.iso.ISOFilter;
import org.jpos.iso.ISOMsg;
import org.jpos.util.LogEvent;
import org.springframework.util.StringUtils;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;

import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Paths;

@Slf4j
public class MessageModifyFilter implements ISOFilter, Configurable {
    private String requestScriptPath;
    private String responseScriptPath;

    private String loadScript(String path) throws IOException {
        return new String(Files.readAllBytes(Paths.get(path)));
    }

    @Override
    public ISOMsg filter(ISOChannel channel, ISOMsg m, LogEvent logEvent) throws ISOFilter.VetoException {
        try {
            HttpServletRequest request = ((ServletRequestAttributes) RequestContextHolder.currentRequestAttributes()).getRequest();

            String scriptPath = isRequest(m) ? requestScriptPath : responseScriptPath;
            String script = loadScript(scriptPath);

            String functionName = isRequest(m) ? "modifyRequest" : "modifyResponse";
            log.info("Applying " + functionName + " script");
            return (ISOMsg) ScriptExecutor.executeScript(script, functionName, m, scriptPath, request.getAttribute("EXPOSED_PARAMS"));

        } catch (IOException e) {
            log.warn("Possibly failed to execute script or the execution returned null!");
            log.warn("Therefore returning original object without modification");
            return m;
        } catch (ISOException e) {
            log.info("There was error processing the ISOFilter", e);
            log.warn("Therefore returning original object without modification");
            return m;
        }
    }

    private boolean isRequest(ISOMsg m) throws ISOException {
        return m.getDirection() == ISOMsg.OUTGOING;
    }

    @Override
    public void setConfiguration(Configuration cfg) throws ConfigurationException {
        if (StringUtils.hasText(cfg.get("request-modifier-script-Path"))) {
            this.requestScriptPath = cfg.get("request-modifier-script-Path");
        }
        if (StringUtils.hasText(cfg.get("response-modifier-script-Path"))) {
            this.responseScriptPath = cfg.get("response-modifier-script-Path");
        }
        if (requestScriptPath == null) {
            log.warn("---REQUEST MODIFIER SCRIPT FILE NOT PROVIDED---");
        }
        if (responseScriptPath == null) {
            log.warn("---RESPONSE MODIFIER SCRIPT FILE NOT PROVIDED---");
        }
    }
}

ScriptExecutor.java

import lombok.experimental.UtilityClass;
import lombok.extern.slf4j.Slf4j;
import org.graalvm.polyglot.Context;
import org.graalvm.polyglot.Value;

@UtilityClass
@Slf4j
public class ScriptExecutor {
    private String getScriptType(final String scriptPath) {
        int lastDotIndex = scriptPath.lastIndexOf('.');
        if (lastDotIndex != -1) {
            String extension = scriptPath.substring(lastDotIndex + 1);
            if ("py".equals(extension)) {
                return "python";
            } else if ("js".equals(extension)) {
                return "js";
            }
        }
        return "java";
    }

    public static Object executeScript(final String script, final String functionName, final Object... args) {
        try (Context context = Context.newBuilder().allowAllAccess(true).build()) {
            context.eval(getScriptType((String) args[0]), script);
            Value function = context.getBindings(getScriptType((String) args[0])).getMember(functionName);
            if (function.canExecute()) {
                return function.execute(args).as(Object.class);
            } else {
                throw new RuntimeException("Function " + functionName + " is not executable please check your script");
            }
        } catch (Exception e) {
            log.info("There was an error executing the message modifier script");
            return null;
        }
    }
}

jPOS过滤器配置XML

<filter class="com.test.iso8583.MessageModifyFilter"  logger="Q2">
    <property name="request-modifier-script-Path" value="${catalina.base}/conf/iso8583/request-modifier.js"/>
    <property name="response-modifier-script-Path" value="${catalina.base}/conf/iso8583/response-modifier.js"/>
</filter>

遇到的错误

No thread-bound request found: Are you referring to request attributes outside of an actual web request, or processing a request outside of the originally receiving thread? If you are actually operating within a web request and still receive this message, your code is probably running outside of DispatcherServlet: In this case, use RequestContextListener or RequestContextFilter to expose the current request. java.lang.IllegalStateException: No thread-bound request found: Are you referring to request attributes outside of an actual web request, or processing a request outside of the originally receiving thread? If you are actually operating within a web request and still receive this message, your code is probably running outside of DispatcherServlet: In this case, use RequestContextListener or RequestContextFilter to expose the current request.
at org.springframework.web.context.request.RequestContextHolder.currentRequestAttributes(RequestContextHolder.java:131)
at com.sanjok.iso8583.MessageModifyFilter.filter(MessageModifyFilter.java:37)
at org.jpos.iso.BaseChannel.applyOutgoingFilters(BaseChannel.java:963)

场景限制

  • 集中配置需求:当前对出入站消息采用集中式XML配置,拆分配置会增加维护复杂度、降低一致性。
  • 中间件场景需求:应用是对接多客户端的中间件,部分客户端无法正确回显字段(例如返回000000111111而非111111),需要在qmux进行关键字段相等性校验前修改响应。

解决方案建议

1. 将请求属性直接绑定到ISOMsg

这是最直接且安全的方案,避免线程上下文依赖:

  • 在Spring控制器处理HTTP请求时,把需要传递的EXPOSED_PARAMS直接存入ISOMsg的自定义属性中:
    // 控制器中创建ISOMsg后添加
    m.setAttribute("EXPOSED_PARAMS", request.getAttribute("EXPOSED_PARAMS"));
    
  • 修改MessageModifyFilter的filter方法,移除RequestContextHolder相关逻辑,直接从ISOMsg获取属性:
    @Override
    public ISOMsg filter(ISOChannel channel, ISOMsg m, LogEvent logEvent) throws ISOFilter.VetoException {
        try {
            String scriptPath = isRequest(m) ? requestScriptPath : responseScriptPath;
            String script = loadScript(scriptPath);
    
            String functionName = isRequest(m) ? "modifyRequest" : "modifyResponse";
            log.info("Applying " + functionName + " script");
            // 从ISOMsg获取参数
            Object exposedParams = m.getAttribute("EXPOSED_PARAMS");
            return (ISOMsg) ScriptExecutor.executeScript(script, functionName, m, scriptPath, exposedParams);
    
        } catch (IOException e) {
            log.warn("Possibly failed to execute script or the execution returned null!");
            log.warn("Therefore returning original object without modification");
            return m;
        } catch (ISOException e) {
            log.info("There was error processing the ISOFilter", e);
            log.warn("Therefore returning original object without modification");
            return m;
        }
    }
    

2. 手动传递线程上下文(需注意线程安全)

如果必须依赖线程上下文传递,需在jPOS处理线程中手动设置和清理Spring请求上下文:

  • 在Spring控制器中获取当前请求上下文,并在提交jPOS任务时携带:
    ServletRequestAttributes attrs = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
    // 提交jPOS处理任务
    isoMessageService.processMessage(m, attrs);
    
  • 在jPOS处理逻辑中设置上下文:
    public void processMessage(ISOMsg m, ServletRequestAttributes attrs) {
        RequestContextHolder.setRequestAttributes(attrs);
        try {
            // 执行jPOS消息发送/接收逻辑,触发过滤器
        } finally {
            // 及时清理上下文,避免线程池污染
            RequestContextHolder.resetRequestAttributes();
        }
    }
    
    注意:此方法仅适用于jPOS处理线程由Spring管理的场景,若使用jPOS自身线程池,需额外处理上下文传递。

3. 调整过滤器执行顺序以满足响应修改需求

确保MessageModifyFilter在qmux的入站过滤器链中优先级最高,这样可以在qmux执行关键字段校验前完成响应修改。在jPOS的Q2配置中,将过滤器配置放在qmux的in-filter-list最前面:

<qmux name="qmux" logger="Q2">
    <in-filter-list>
        <filter class="com.test.iso8583.MessageModifyFilter" logger="Q2">
            <property name="request-modifier-script-Path" value="${catalina.base}/conf/iso8583/request-modifier.js"/>
            <property name="response-modifier-script-Path" value="${catalina.base}/conf/iso8583/response-modifier.js"/>
        </filter>
        <!-- 其他过滤器 -->
    </in-filter-list>
    <!-- 其他qmux配置 -->
</qmux>

内容的提问来源于stack exchange,提问作者Sanjok

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.23 00:09:51