Mercurial > stress-tester
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); + } +}