Sanjok
Sanjok

Reputation: 437

Problem Passing HTTP Request Data to jPOS Filters

I'm working on a Spring-based application that uses jPOS to handle ISO8583 messages. I want is to allow scripts (in JavaScript or Python) to modify these ISO messages. To achieve this, I have set up a mechanism to execute scripts that can alter the ISO messages.

However, I am facing an issue when trying to pass data received from an HTTP request (via a Spring controller) to the jPOS filter, as the filter is invoked by jPOS itself.

mport 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---");
        }
    }
}

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;
        }
    }
}

Problem:

Since jPOS operates outside the HTTP request context, using RequestContextHolder.currentRequestAttributes() to access request-specific data results in the following error:

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)

Any guidance or suggestions would be greatly appreciated.

Regarding Andrés Alcarraz`s query,why applying filters when creating the message isn’t ideal for my situation:

Centralized Configuration: I currently have a centralized configuration for both incoming and outgoing messages, like this:

<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>

Splitting this configuration to different parts of the application would complicate maintenance and reduce coherence.

Middleware Application: My application is a middleware that deals with multiple clients. Some clients do not echo back fields correctly (e.g., sending back 000000111111 instead of 111111). I need to modify the response before qmux makes equality comparison for the provided key field.

Upvotes: 4

Views: 95

Answers (0)

Related Questions