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