Spring集成jPOS时,HTTP请求数据传递至ISO8583过滤器的问题求助
背景
我正在开发基于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处理逻辑中设置上下文:
注意:此方法仅适用于jPOS处理线程由Spring管理的场景,若使用jPOS自身线程池,需额外处理上下文传递。public void processMessage(ISOMsg m, ServletRequestAttributes attrs) { RequestContextHolder.setRequestAttributes(attrs); try { // 执行jPOS消息发送/接收逻辑,触发过滤器 } finally { // 及时清理上下文,避免线程池污染 RequestContextHolder.resetRequestAttributes(); } }
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

