package quant.rich.emoney.patch.okhttp; import java.util.*; import java.util.function.*; import java.util.regex.Pattern; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import lombok.Getter; import lombok.Setter; import lombok.experimental.Accessors; import quant.rich.emoney.patch.okhttp.PatchOkHttp.HeaderInterceptor; public class PatchOkHttpRule { @Getter @Setter @Accessors(chain=true) private Integer id; private final Predicate condition; private final List actions; public PatchOkHttpRule(Predicate condition, List actions) { this.condition = condition; this.actions = actions; } public boolean matches(RequestContext ctx) { return condition.test(ctx); } public void apply(RequestContext ctx, String currentHeader, Consumer overrideSetter) { for (HeaderAction action : actions) { action.apply(ctx, currentHeader, overrideSetter); } } @FunctionalInterface public interface HeaderAction { void apply(RequestContext ctx, String currentHeader, Consumer overrideValueSetter); } /** * 起始语句,创建一个 Builder * @return */ public static Builder when() { return new Builder(ctx -> true); } public static class Builder { public static final Logger log = LoggerFactory.getLogger(HeaderInterceptor.class); private Predicate condition; private final List actions = new ArrayList<>(); public Builder(Predicate initial) { this.condition = initial; } public Builder hasHeaderPair(String name, String value) { return and(ctx -> value.equals(ctx.headers.get(name))); } public Builder hasHeaderName(String name) { return and(ctx -> ctx.headers.containsKey(name)); } public Builder hasNotHeaderName(String name) { return not(r -> r.and(ctx -> ctx.headers.containsKey(name))); } public Builder hasHeaderValueMatch(String name, String regex) { Pattern pattern = Pattern.compile(regex); return and(ctx -> { String val = ctx.headers.get(name); return val != null && pattern.matcher(val).matches(); }); } public Builder hostEndsWith(String suffix) { return and(ctx -> ctx.host != null && ctx.host.endsWith(suffix)); } public Builder hostContains(String keyword) { return and(ctx -> ctx.host != null && ctx.host.contains(keyword)); } /** * * @param pattern * @return */ public Builder hostMatches(String pattern) { return and(ctx -> ctx.host != null && Pattern.matches(pattern, ctx.host)); } public Builder isHttp() { return and(ctx -> "http".equalsIgnoreCase(ctx.scheme)); } public Builder isHttps() { return and(ctx -> "https".equalsIgnoreCase(ctx.scheme)); } /** * 接收一个 not 条件
* 例:Rule.when().not(r -> r.isHttps()).build() * * @param block * @return */ public Builder not(Consumer block) { Builder sub = new Builder(ctx -> true); // starts with true block.accept(sub); this.condition = this.condition .and(sub.condition.negate()); return this; } public Builder or(Consumer block) { Builder sub = new Builder(ctx -> true); block.accept(sub); this.condition = this.condition.or(sub.condition); return this; } public Builder or(Builder other) { this.condition = this.condition.or(other.condition); return this; } public Builder or(PatchOkHttpRule rule) { this.condition = this.condition.or(rule::matches); return this; } public Builder and() { return this; } public Builder and(Predicate other) { this.condition = this.condition.and(other); return this; } public Builder then() { return this; } public Builder setHeader(String name, String value) { actions.add((ctx, curr, setter) -> ctx.headers.put(name, value)); return this; } public Builder overrideHeader(String headerName, String value) { actions.add((ctx, curr, setter) -> { if (curr.equalsIgnoreCase(headerName)) { log.debug("matches and applying - host: {}, currHeader {}, targetHeader {}, value: {}, classLoader: {}", ctx.host, curr, headerName, value, this.getClass().getClassLoader()); setter.accept(value); } }); return this; } /** * 如果满足条件则覆写指定 Header。当覆写值可能动态变化时,使用本方法提供 supplier * @param headerName * @param valueSupplier * @return */ public Builder overrideHeader(String headerName, Supplier valueSupplier) { actions.add((ctx, curr, setter) -> { if (curr.equalsIgnoreCase(headerName)) { String value = valueSupplier.get(); log.debug("matches and applying - host: {}, currHeader {}, targetHeader {}, value: {}, classLoader: {}", ctx.host, curr, headerName, value, this.getClass().getClassLoader()); setter.accept(value); } }); return this; } public PatchOkHttpRule build() { return new PatchOkHttpRule(condition, actions); } } public boolean equals(PatchOkHttpRule other) { if (other == null) return false; if (this.id == null || other.id == null) return false; return this.id.equals(other.id); } public int hashCode() { return Objects.hash(id); } }