changeset 542:24749a1ca61d

HttpSequenceFilter - in progress
author Devel 1
date Wed, 13 Sep 2017 13:26:20 +0200
parents 83f45c3b4c3d
children 85a2cf51b9da
files stress-tester/src/main/java/com/passus/st/client/http/filter/HttpFilterMessageWrapper.java stress-tester/src/main/java/com/passus/st/client/http/filter/HttpSequenceEvent.java stress-tester/src/main/java/com/passus/st/client/http/filter/HttpSequenceFilter.java stress-tester/src/main/java/com/passus/st/client/http/filter/HttpSequenceListener.java stress-tester/src/test/java/com/passus/st/client/http/filter/HttpMessageWrapperTest.java stress-tester/src/test/java/com/passus/st/client/http/filter/HttpSequenceFilterTest.java
diffstat 6 files changed, 464 insertions(+), 0 deletions(-) [+]
line wrap: on
line diff
--- a/stress-tester/src/main/java/com/passus/st/client/http/filter/HttpFilterMessageWrapper.java	Tue Sep 12 16:22:53 2017 +0200
+++ b/stress-tester/src/main/java/com/passus/st/client/http/filter/HttpFilterMessageWrapper.java	Wed Sep 13 13:26:20 2017 +0200
@@ -2,6 +2,7 @@
 
 import com.passus.commons.Assert;
 import com.passus.data.ByteString;
+import com.passus.net.http.HttpCookie;
 import com.passus.net.http.HttpHeaders;
 import com.passus.net.http.HttpMessage;
 import com.passus.net.http.HttpMessageHelper;
@@ -41,4 +42,9 @@
         HttpHeaders headers = message.getHeaders();
         return headers.get(name);
     }
+
+    public ByteString getCookie(CharSequence name) {
+        HttpCookie cookie = HELPER.getCookie(message, name);
+        return cookie == null ? null : cookie.getValue();
+    }
 }
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/stress-tester/src/main/java/com/passus/st/client/http/filter/HttpSequenceEvent.java	Wed Sep 13 13:26:20 2017 +0200
@@ -0,0 +1,20 @@
+package com.passus.st.client.http.filter;
+
+import java.util.Map;
+
+/**
+ *
+ * @author mikolaj.podbielski
+ */
+public class HttpSequenceEvent {
+
+    private final Map<String, Object> values;
+
+    public HttpSequenceEvent(Map<String, Object> values) {
+        this.values = values;
+    }
+
+    public Map<String, Object> getValues() {
+        return values;
+    }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/stress-tester/src/main/java/com/passus/st/client/http/filter/HttpSequenceFilter.java	Wed Sep 13 13:26:20 2017 +0200
@@ -0,0 +1,316 @@
+package com.passus.st.client.http.filter;
+
+import com.passus.commons.annotations.Plugin;
+import com.passus.filter.ValueExtractor;
+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.util.ArrayList;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Map;
+import java.util.function.Predicate;
+
+/**
+ *
+ * @author mikolaj.podbielski
+ */
+@Plugin(name = HttpSequenceFilter.TYPE, category = PluginConstants.CATEGORY_HTTP_FILTER)
+public class HttpSequenceFilter extends HttpFilter {
+
+    public static class SequenceItem {
+
+        private final Predicate predicate;
+        private boolean mustOccur = true;
+        private long time = 10_000;
+        private final int num;
+        private String alias;
+        private String[] aliases;
+
+        public SequenceItem(Predicate predicate, int num) {
+            if (predicate == null) {
+                throw new NullPointerException();
+            }
+            this.predicate = predicate;
+            this.num = num;
+        }
+
+        public long getTime() {
+            return time;
+        }
+
+        public void setTime(long time) {
+            if (time <= 0) {
+                throw new IllegalArgumentException("Time must be greater than zero.");
+            }
+            this.time = time;
+        }
+
+        public String getAlias() {
+            return alias;
+        }
+
+        public void setAlias(String alias) {
+            this.alias = alias;
+            aliases = null;
+        }
+
+        public boolean isMustOccur() {
+            return mustOccur;
+        }
+
+        public void setMustOccur(boolean mustOccur) {
+            this.mustOccur = mustOccur;
+        }
+
+        private boolean match(Map<String, Object> value) {
+            return predicate.test(value);
+        }
+
+        private String[] getAliases() {
+            if (aliases == null) {
+                if (alias != null) {
+                    aliases = new String[2];
+                    aliases[1] = alias;
+                } else {
+                    aliases = new String[1];
+                }
+
+                aliases[0] = "_i" + num;
+            }
+
+            return aliases;
+        }
+    }
+
+    private static class SeqChainItem {
+
+        private final SequenceItem seq;
+        private SeqChainItem next;
+        private final long startTime;
+        private final long endTime;
+        private boolean active = true;
+
+        public SeqChainItem(SequenceItem seq, long startTime) {
+            this.seq = seq;
+            this.startTime = startTime;
+            this.endTime = startTime + seq.time;
+        }
+
+        public void setNext(SeqChainItem next) {
+            this.next = next;
+        }
+
+        public long getStartTime() {
+            return startTime;
+        }
+
+        public long getEndTime() {
+            return endTime;
+        }
+
+        public boolean match(Map<String, Object> value) {
+            return seq.match(value);
+        }
+
+        public SeqChainItem getNext() {
+            return next;
+        }
+
+        public boolean inTimeRange(long time) {
+            return (startTime <= time && time < endTime);
+        }
+    }
+
+    private static class SeqChain {
+
+        private SeqChainItem seqItem;
+
+        private final Map<String, Object> valueMap;
+//        private Filterable value = new FilterableMap();
+//        private FlowMarker flowMarker;
+
+        public SeqChain(SequenceItem[] seqItems, long startTime, Map<String, Object> valueMap) {
+            this.valueMap = valueMap;
+
+            SeqChainItem curItem = new SeqChainItem(seqItems[1], startTime);
+            seqItem = curItem;
+
+            for (int i = 2; i < seqItems.length; i++) {
+                SeqChainItem nexItem = new SeqChainItem(seqItems[i], curItem.getStartTime());
+                curItem.setNext(nexItem);
+                curItem = nexItem;
+            }
+
+//            if (isMarkFlow()) {
+//                try {
+//                    if (getPipeline() == null) {
+//                        flowMarker = FlowMarker.create();
+//                    } else {
+//                        flowMarker = FlowMarker.create(getPipeline().getFlowId());
+//                    }
+//
+//                    value.addFlowMarker(flowMarker);
+//                } catch (Throwable e) {
+//                    if (LOGGER.isDebugEnabled()) {
+//                        LOGGER.debug(e.getMessage(), e);
+//                    }
+//                }
+//            }
+        }
+
+        public void updateValue(HttpMessageWrapper wrapper) {
+            valueMap.put("req", wrapper.getReq());
+            valueMap.put("resp", wrapper.getResp());
+        }
+
+        public void persistsValue(HttpMessageWrapper wrapper, String[] aliases) {
+            for (String alias : aliases) {
+                valueMap.put(alias, wrapper);
+            }
+        }
+
+        public boolean hasNext() {
+            return (seqItem != null && seqItem.getNext() != null);
+        }
+
+        public boolean next() {
+            if (seqItem == null) {
+                return false;
+            }
+
+            seqItem = seqItem.getNext();
+            return true;
+        }
+
+        public boolean rewind(long time) {
+            if (seqItem == null) {
+                return false;
+            }
+
+            while (hasNext()) {
+                if (seqItem.inTimeRange(time)) {
+                    return true;
+                } else if (seqItem.seq.mustOccur) {
+                    return false;
+                }
+
+                next();
+            }
+
+            return false;
+        }
+    }
+
+    public static final String TYPE = "sequence";
+
+    private final List<SeqChain> chains = new ArrayList<>();
+    private HttpSequenceListener listener = (e) -> {
+    };
+    private SequenceItem[] seqItems;
+    private Map<String, ValueExtractor> values = Collections.EMPTY_MAP;
+
+    public void setListener(HttpSequenceListener listener) {
+        this.listener = listener;
+    }
+
+    public void setSeqItems(SequenceItem[] seqItems) {
+        this.seqItems = seqItems;
+    }
+
+    public void setValues(Map<String, ValueExtractor> values) {
+        this.values = values;
+    }
+
+    private void processValue(long now, HttpMessageWrapper value) {
+        Iterator<SeqChain> iterator = chains.iterator();
+        while (iterator.hasNext()) {
+            SeqChain chain = iterator.next();
+
+            if (chain.seqItem.startTime <= now) {
+                if (!chain.seqItem.inTimeRange(now) && !chain.rewind(now)) {
+                    chain.persistsValue(value, chain.seqItem.seq.getAliases());
+                    fireEvent(chain);
+                    iterator.remove();
+                } else if (chain.seqItem.active) {
+                    chain.updateValue(value);
+
+                    if (chain.seqItem.match(chain.valueMap)) {
+                        if (!chain.seqItem.seq.mustOccur) {
+                            iterator.remove();
+                        } else {
+//                            if (chain.flowMarker != null) {
+//                                value.addFlowMarker(chain.flowMarker);
+//                            }
+
+                            chain.persistsValue(value, chain.seqItem.seq.getAliases());
+                            if (!chain.hasNext()) {
+                                fireEvent(chain);
+                                iterator.remove();
+                            } else if (!chain.next()) {
+                                fireEvent(chain);
+                                iterator.remove();
+                            }
+                        }
+                    }
+                }
+            }
+        }
+
+        // TODO: refactor
+        final Map<String, Object> valueMap = new HashMap<>();
+        valueMap.put("req", value.getReq());
+        valueMap.put("resp", value.getResp());
+
+        if (seqItems[0].match(valueMap)) {
+            SeqChain chain = new SeqChain(seqItems, now, valueMap);
+            chain.persistsValue(value, seqItems[0].getAliases());
+//            if (chain.flowMarker != null) {
+//                value.addFlowMarker(chain.flowMarker);
+//            }
+            chains.add(chain);
+        }
+    }
+
+    private void fireEvent(SeqChain chain) {
+        Map<String, Object> result;
+
+        if (values.isEmpty()) {
+            result = Collections.emptyMap();
+        } else {
+            result = new HashMap<>();
+            Map<String, Object> persisted = chain.valueMap;
+            for (Map.Entry<String, ValueExtractor> e : values.entrySet()) {
+                Object value = e.getValue().extract(persisted);
+                result.put(e.getKey(), value);
+            }
+        }
+
+        listener.sequenceDetected(new HttpSequenceEvent(result));
+    }
+
+    @Override
+    public int filterInbound(HttpRequest req, HttpResponse resp, HttpFlowContext context) {
+        HttpMessageWrapper wrapper = new HttpMessageWrapper(req, resp, context);
+        processValue(resp.getTimestamp(), wrapper);
+        return DUNNO;
+    }
+
+    @Override
+    public void reset() {
+        chains.clear();
+    }
+
+    @Override
+    public HttpSequenceFilter instanceForWorker(int index) {
+        HttpSequenceFilter filter = new HttpSequenceFilter();
+        filter.listener = listener;
+        filter.seqItems = seqItems;
+        filter.values = values;
+        return filter;
+    }
+
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/stress-tester/src/main/java/com/passus/st/client/http/filter/HttpSequenceListener.java	Wed Sep 13 13:26:20 2017 +0200
@@ -0,0 +1,10 @@
+package com.passus.st.client.http.filter;
+
+/**
+ *
+ * @author mikolaj.podbielski
+ */
+public interface HttpSequenceListener {
+
+    public void sequenceDetected(HttpSequenceEvent event);
+}
--- a/stress-tester/src/test/java/com/passus/st/client/http/filter/HttpMessageWrapperTest.java	Tue Sep 12 16:22:53 2017 +0200
+++ b/stress-tester/src/test/java/com/passus/st/client/http/filter/HttpMessageWrapperTest.java	Wed Sep 13 13:26:20 2017 +0200
@@ -16,6 +16,7 @@
 import com.passus.st.client.http.HttpFlowContext;
 import com.passus.st.client.http.HttpScopes;
 import java.text.ParseException;
+import java.util.HashMap;
 import static org.testng.AssertJUnit.*;
 import org.testng.annotations.Test;
 
@@ -124,6 +125,18 @@
     }
 
     @Test
+    public void testBeanValueExtractorWithMap() throws ParseException {
+        BeanValueExtractor e;
+        Object value;
+        HashMap<String, Object> map = new HashMap<>();
+        map.put("request", wrapper.getReq());
+
+        e = bean("$request.version");
+        value = e.extract(map);
+        assertEquals("HTTP/1.1", value.toString());
+    }
+
+    @Test
     public void testMvelValueExtractor() throws ParseException {
         MvelValueExtractor e;
         Object value;
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/stress-tester/src/test/java/com/passus/st/client/http/filter/HttpSequenceFilterTest.java	Wed Sep 13 13:26:20 2017 +0200
@@ -0,0 +1,99 @@
+package com.passus.st.client.http.filter;
+
+import com.passus.filter.ValueExtractor;
+import com.passus.filter.ValueExtractorParser;
+import com.passus.filter.config.PredicateNodeTransformer;
+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.AppUtils;
+import com.passus.st.client.http.filter.HttpSequenceFilter.SequenceItem;
+import java.text.ParseException;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.function.Predicate;
+import static org.testng.AssertJUnit.*;
+import org.testng.annotations.AfterClass;
+import org.testng.annotations.BeforeClass;
+import org.testng.annotations.Test;
+
+/**
+ *
+ * @author mikolaj.podbielski
+ */
+public class HttpSequenceFilterTest {
+
+    private static final PredicateNodeTransformer TRANSFORMER = new PredicateNodeTransformer();
+
+    @BeforeClass
+    public static void beforeClass() {
+        AppUtils.registerAll();
+    }
+
+    @AfterClass
+    public static void afterClass() {
+        AppUtils.unregisterAll();
+    }
+
+    @Test
+    public void testFilterInbound() throws Exception {
+        HttpRequest req0 = HttpRequestBuilder.get("http://example.com/res1").build();
+        HttpResponse resp0 = HttpResponseBuilder.ok().cookie("id", "123").build();
+
+        HttpRequest req1 = HttpRequestBuilder.get("http://example.com/res2")
+                .cookie("id", "123").header("Xyz", "abc").build();
+        HttpResponse resp1 = HttpResponseBuilder.ok().build();
+
+        TestHttpSequenceListener listener = new TestHttpSequenceListener();
+
+        SequenceItem[] seqItems = {
+            item("{\"req.uri\": \"/res1\", \"resp.getCookie('id')\": \"123\"}", 0),
+            item("{\"req.getHeader('xyz')\": \"abc\"}", 1),};
+
+        Map<String, ValueExtractor> values = new HashMap<>();
+        values.put("code", value("$_i0.resp.status.code"));
+        values.put("xhost", value("$_i0.req.url.host"));
+        values.put("header", value("$_i1.req.getHeader('xyz')"));
+        values.put("cookie", value("$_i1.req.getCookie('id')"));
+
+        HttpSequenceFilter filter = new HttpSequenceFilter().instanceForWorker(0);
+        filter.setListener(listener);
+        filter.setSeqItems(seqItems);
+        filter.setValues(values);
+
+        filter.filterInbound(req0, resp0, null);
+        filter.filterInbound(req1, resp1, null);
+
+        assertEquals(1, listener.events.size());
+        Map<String, Object> extracted = listener.events.get(0).getValues();
+        assertEquals(200, extracted.get("code"));
+        assertEquals("example.com", extracted.get("xhost").toString());
+        assertEquals("abc", extracted.get("header").toString());
+        assertEquals("123", extracted.get("cookie").toString());
+    }
+
+    private static SequenceItem item(String predicateString, int num) throws Exception {
+        Predicate predicate = TRANSFORMER.transform(predicateString);
+        return new SequenceItem(predicate, num);
+    }
+
+    private static ValueExtractor value(String s) throws ParseException {
+        return ValueExtractorParser.DEFAULT.parse(s);
+    }
+
+    public static class TestHttpSequenceListener implements HttpSequenceListener {
+
+        ArrayList<HttpSequenceEvent> events = new ArrayList<>();
+
+        @Override
+        public void sequenceDetected(HttpSequenceEvent event) {
+            events.add(event);
+        }
+
+        public void reset() {
+            events.clear();
+        }
+    }
+}