changeset 514:ff326752e0c3

HttpMvelFilter - initial version
author Devel 1
date Fri, 18 Aug 2017 15:21:08 +0200
parents f74d8efe85b9
children 7edad974d3f8
files stress-tester/src/main/java/com/passus/st/client/http/filter/HttpMvelFilter.java stress-tester/src/test/java/com/passus/st/client/http/filter/HttpMvelFilterTest.java stress-tester/src/test/resources/mvel/return1.mvel
diffstat 3 files changed, 351 insertions(+), 0 deletions(-) [+]
line wrap: on
line diff
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/stress-tester/src/main/java/com/passus/st/client/http/filter/HttpMvelFilter.java	Fri Aug 18 15:21:08 2017 +0200
@@ -0,0 +1,256 @@
+package com.passus.st.client.http.filter;
+
+import com.passus.commons.ConversionException;
+import com.passus.commons.annotations.Plugin;
+import com.passus.config.CMapNode;
+import com.passus.config.CNode;
+import com.passus.config.CTupleNode;
+import com.passus.config.CValueNode;
+import com.passus.config.Configuration;
+import com.passus.config.ValueTransformer;
+import com.passus.config.annotations.NodeDefinitionCreate;
+import static com.passus.config.schema.ConfigurationSchemaBuilder.mapDef;
+import static com.passus.config.schema.ConfigurationSchemaBuilder.mixedDef;
+import static com.passus.config.schema.ConfigurationSchemaBuilder.tupleDef;
+import static com.passus.config.schema.ConfigurationSchemaBuilder.valueDef;
+import com.passus.config.schema.MixedNodeDefinition;
+import com.passus.config.schema.NodeDefinition;
+import com.passus.config.schema.NodeDefinitionCreator;
+import com.passus.config.schema.NodeTransformer;
+import com.passus.config.validation.Errors;
+import com.passus.net.http.HttpRequest;
+import com.passus.net.http.HttpResponse;
+import com.passus.st.client.http.HttpFlowContext;
+import com.passus.st.plugin.PluginConstants;
+import java.io.File;
+import java.io.IOException;
+import java.nio.file.Files;
+import java.nio.file.Paths;
+import java.util.Collection;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import org.apache.logging.log4j.LogManager;
+import org.apache.logging.log4j.Logger;
+import org.mvel2.CompileException;
+import org.mvel2.MVEL;
+import org.mvel2.compiler.ExecutableStatement;
+import org.mvel2.integration.VariableResolverFactory;
+import org.mvel2.integration.impl.CachingMapVariableResolverFactory;
+import org.mvel2.integration.impl.MapVariableResolverFactory;
+
+/**
+ *
+ * @author mikolaj.podbielski
+ */
+@Plugin(name = HttpMvelFilter.TYPE, category = PluginConstants.CATEGORY_HTTP_FILTER)
+@NodeDefinitionCreate(HttpMvelFilter.NodeDefCreator.class)
+public class HttpMvelFilter extends HttpFilter {
+
+    public static final String TYPE = "mvel";
+
+    private static final Logger LOGGER = LogManager.getLogger(HttpMvelFilter.class);
+
+    private VariableResolverFactory globalFactory;
+    private ExecutableStatement outbound;
+    private ExecutableStatement inbound;
+
+    VariableResolverFactory getGlobalFactory() {
+        return globalFactory;
+    }
+
+    void setGlobalFactory(VariableResolverFactory globalFactory) {
+        this.globalFactory = globalFactory;
+    }
+
+    ExecutableStatement getOutbound() {
+        return outbound;
+    }
+
+    void setOutbound(ExecutableStatement outbound) {
+        this.outbound = outbound;
+    }
+
+    ExecutableStatement getInbound() {
+        return inbound;
+    }
+
+    void setInbound(ExecutableStatement inbound) {
+        this.inbound = inbound;
+    }
+
+    @Override
+    public void configure(Configuration config) {
+        outbound = (ExecutableStatement) config.get("outbound", null);
+        inbound = (ExecutableStatement) config.get("inbound", null);
+    }
+
+    @Override
+    public int filterOutbound(HttpRequest request, HttpResponse resp, HttpFlowContext context) {
+        return filter(outbound, request, resp, context);
+    }
+
+    @Override
+    public int filterInbound(HttpRequest request, HttpResponse resp, HttpFlowContext context) {
+        return filter(inbound, request, resp, context);
+    }
+
+    private int filter(ExecutableStatement expression, HttpRequest request, HttpResponse resp, HttpFlowContext context) {
+        if (expression == null) {
+            return DUNNO;
+        }
+
+        Map<String, Object> vars = new HashMap<>(3);
+        vars.put("$req", request);
+        vars.put("$resp", resp);
+        vars.put("$ctx", context);
+
+        CachingMapVariableResolverFactory factory = new CachingMapVariableResolverFactory(vars);
+        if (globalFactory != null) {
+            factory.setNextFactory(globalFactory);
+        }
+
+        try {
+            Object out = expression.getValue(null, factory);
+            if (out instanceof Integer) {
+                int value = (Integer) out;
+                if (value == DENY || value == DUNNO || value == ACCEPT) {
+                    return value;
+                }
+            }
+        } catch (Throwable th) {
+            LOGGER.trace(th);
+        }
+        return DUNNO;
+    }
+
+    public static ExecutableStatement compile(String expression) {
+        try {
+            return (ExecutableStatement) MVEL.compileExpression(expression);
+        } catch (Exception ex) {
+            LOGGER.warn("Compilation error:\n" + ex.getMessage());
+            throw ex;
+        }
+    }
+
+    public static void validate(String expression) throws CompileException {
+        MVEL.compileExpression(expression);
+    }
+
+    public static int validate(Collection<String> includes) throws CompileException, RuntimeException {
+        Map variables = new HashMap();
+        MapVariableResolverFactory globalFactory = new MapVariableResolverFactory(variables);
+        for (String include : includes) {
+            ExecutableStatement es = (ExecutableStatement) MVEL.compileExpression(include);
+            es.getValue(null, globalFactory);
+        }
+        return variables.size();
+    }
+
+    public static VariableResolverFactory global(Collection<String> includes) {
+        MapVariableResolverFactory globalFactory = new MapVariableResolverFactory();
+//        GlobalExpressionList compiledGlobalExpressions;
+        for (String include : includes) {
+            if (include != null) {
+//                GlobalExpression globalExpression = compiledGlobalExpressions.getByName(include);
+//                if (globalExpression != null) {
+//                    ExecutableStatement compiledGlobal = globalExpression.getCompiled();
+//                    compiledGlobal.getValue(null, globalFactory);
+//                }
+            }
+        }
+        return globalFactory;
+    }
+
+    @Override
+    public HttpMvelFilter instanceForWorker(int index) {
+        HttpMvelFilter filter = new HttpMvelFilter();
+        filter.globalFactory = globalFactory;
+        filter.outbound = outbound;
+        filter.inbound = inbound;
+        return filter;
+    }
+
+    // TODO: replace ScriptNodeTransformer and ScriptTransformer with custom NodeDefinition
+    public static class NodeDefCreator implements NodeDefinitionCreator {
+
+        @Override
+        public NodeDefinition create() {
+            MixedNodeDefinition scriptDef = mixedDef(
+                    valueDef().setTransformer(new ScriptTransformer()),
+                    mapDef(tupleDef("file", valueDef())).setTransformer(new ScriptNodeTransformer())
+            );
+
+            return mapDef(
+                    //                    tupleDef("includes", null),
+                    tupleDef("outbound", scriptDef),
+                    tupleDef("inbound", scriptDef)
+            );
+        }
+
+    }
+
+    private static class ScriptNodeTransformer implements NodeTransformer<CValueNode> {
+
+        @Override
+        public CValueNode transform(CNode node, Errors errors) {
+            CMapNode mapNode = (CMapNode) node;
+
+            List<CTupleNode> tuples = mapNode.getChildren();
+            if (tuples.size() != 1) {
+                errors.reject(node, "Node should contain one child.");
+                return null;
+            }
+
+            String script = null;
+
+            CTupleNode tupleNode = tuples.get(0);
+            CValueNode valueNode = (CValueNode) tupleNode.getNode();
+            switch (tupleNode.getName()) {
+                case "file":
+                    String fileName = (String) valueNode.getValue();
+                    try {
+                        script = new String(Files.readAllBytes(Paths.get(new File(fileName).toURI())));
+                    } catch (IOException ex) {
+                        errors.reject(valueNode, "Could not read file \"%s\".", fileName);
+                    }
+                    break;
+                case "script":
+                    script = (String) valueNode.getValue();
+                    break;
+                default:
+                    errors.reject(tupleNode, "Invalid key.");
+            }
+
+            if (script != null) {
+                ExecutableStatement es = compile(script);
+                return new CValueNode(es);
+            } else {
+                return new CValueNode(null);
+            }
+        }
+
+        @Override
+        public CValueNode reverseTransform(CNode node, Errors errors) {
+            throw new UnsupportedOperationException("Not supported yet.");
+        }
+
+    }
+
+    private static class ScriptTransformer implements ValueTransformer {
+
+        @Override
+        public Object transform(Object obj) throws ConversionException {
+            if (obj instanceof String) {
+                return compile((String) obj);
+            }
+            throw new ConversionException("Invalid token store type.");
+        }
+
+        @Override
+        public Object reverseTransform(Object obj) throws ConversionException {
+            throw new UnsupportedOperationException("Not supported yet."); //To change body of generated methods, choose Tools | Templates.
+        }
+
+    }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/stress-tester/src/test/java/com/passus/st/client/http/filter/HttpMvelFilterTest.java	Fri Aug 18 15:21:08 2017 +0200
@@ -0,0 +1,94 @@
+package com.passus.st.client.http.filter;
+
+import com.passus.commons.utils.ResourceUtils;
+import com.passus.config.validation.Errors;
+import com.passus.net.http.HttpMethod;
+import com.passus.net.http.HttpRequest;
+import com.passus.net.http.HttpRequestBuilder;
+import com.passus.net.http.HttpResponse;
+import com.passus.net.http.HttpResponseBuilder;
+import com.passus.st.ParametersBag;
+import com.passus.st.client.http.HttpFlowContext;
+import java.io.File;
+import java.util.List;
+import org.mvel2.MVEL;
+import org.mvel2.compiler.ExecutableStatement;
+import static org.testng.AssertJUnit.*;
+import org.testng.annotations.Test;
+
+/**
+ *
+ * @author mikolaj.podbielski
+ */
+public class HttpMvelFilterTest {
+
+    @Test
+    public void testFilterOutbound() {
+        HttpRequest req = HttpRequestBuilder.get("http://example.com/index.html").build();
+
+        String expression = "$req.setMethod(com.passus.net.http.HttpMethod.HEAD);"
+                + "$req.getUri().toString().equals(\"/index.html\") ? 1 : 0";
+
+        HttpMvelFilter filter = new HttpMvelFilter();
+        filter.setOutbound(es(expression));
+
+        int result = filter.filterOutbound(req, null, null);
+
+        assertEquals(1, result);
+        assertEquals(HttpMethod.HEAD, req.getMethod());
+    }
+
+    @Test
+    public void testFilterInbound() {
+        HttpRequest req = HttpRequestBuilder.get("http://example.com/index.html").build();
+        HttpResponse resp = HttpResponseBuilder.ok().cookie("id", "123").build();
+        HttpFlowContext mockContext = HttpFilterTestUtils.createMockContext();
+        HttpFilterTestUtils.tagMessages(req, resp);
+
+        String expression = "hlp = com.passus.net.http.HttpMessageHelper.STRICT;\n"
+                + "c = hlp.getCookie($resp, \"id\");\n"
+                + "if (c != null) {\n"
+                + "   $ctx.scopes().getSession($req).set(\"token\", c.getValue());\n"
+                + "}";
+
+        HttpMvelFilter filter = new HttpMvelFilter();
+        filter.setInbound(es(expression));
+
+        int result = filter.filterInbound(req, resp, mockContext);
+        ParametersBag session = mockContext.scopes().getSession(req);
+
+        assertEquals(0, result);
+        assertEquals("123", session.get("token").toString());
+    }
+
+//    @Test
+//    public void testGlobal() {
+//    }
+    @Test
+    public void testConfigure() throws Exception {
+        File file = ResourceUtils.getFile("mvel/return1.mvel");
+
+        String filterConfig = "filters:\n"
+                + "  - type: mvel\n"
+                + "    outbound:\n"
+                + "      \"return -1\"\n"
+                + "    inbound:\n"
+                + "      file: '" + file.getAbsolutePath() + "'";
+
+        Errors errors = new Errors();
+        List<HttpFilter> filters = HttpFiltersConfigurator.getFilters(filterConfig, errors);
+        HttpFilterTestUtils.printErrors(errors);
+
+        assertEquals(0, errors.getErrorCount());
+        assertEquals(1, filters.size());
+        assertTrue(filters.get(0) instanceof HttpMvelFilter);
+
+        HttpMvelFilter filter = (HttpMvelFilter) filters.get(0);
+        assertEquals(-1, filter.filterOutbound(null, null, null));
+        assertEquals(1, filter.filterInbound(null, null, null));
+    }
+
+    private static ExecutableStatement es(String expression) {
+        return (ExecutableStatement) MVEL.compileExpression(expression);
+    }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/stress-tester/src/test/resources/mvel/return1.mvel	Fri Aug 18 15:21:08 2017 +0200
@@ -0,0 +1,1 @@
+return 1
\ No newline at end of file