2023年4月

你好,我是刘牌!

介绍

今天要分享的是Spring的注解@Conditional,@Conditional是一个条件注解,它的作用是判断Bean是否满足条件,如果满足条件,则将Bean注册进IOC中,如果不满足条件,则不进行注册,这个注解在SpringBoot中衍生出很多注解,比如
@ConditionalOnProperty

@ConditionalOnBean

@ConditionalOnClass
等等,在SpringBoot中,这些注解用得很多。

文件服务场景

下面我们演示一些@Conditional的使用,在软件开发中,文件系统是必须的,但是系统的特点不一样,有些用户希望将文件保存在自己的服务器上,有些用户则没这种要求,这时候,文件可以保存在云上,也可以保存在自建文件系统上,那么面对不同用户的需求,我们的软件也要能够适配不同的环境,只需要简单的配置即可。

假设我们在开发过程中,我们的文件全部托管在云服务厂商的OSS上,代码逻辑也没有预留扩展,那么当用户需要私有化部署,我们可能就需要更改文件存储这边的逻辑,这样的设计是不合理的。

我们想一想,文件存储的代码逻辑是不同的,各个文件系统的实现方式和使用API各不相同,但是它们有一个共性,那就是能够上传文件,下载文件的,所以我们就应该抽象出一个公共接口,下面有不同的实现,比如Minio的文件上传下载等逻辑就使用Minio API去实现,FastDFS就使用FastDFS,OSS就使用OSS,下面我们就编写对应的代码。

编码实现

以下通过编码实现不同文件系统的逻辑实现隔离,统一提供接口的方案,一般我们都会将配置信息写在配置文件中,在配置文件中,使用storageType代表文件存储类型。

文件上传接口

在StorageService接口中,只简单定义了两个方法init()和put(),init()就是做一些初始化操作,比如参数配置,连接等,put()就是上传文件接口。

/**
 * 功能说明: 文件上传接口
 * <p>
 * Original @Author: steakliu-刘牌, 2023-04-03  09:54
 * <p>
 */
public interface StorageService {
    /**
     * 初始化文件存储
     */
    void init();
    /**
     * 上传文件
     * @param file
     */
    void put(MultipartFile file);
}

具体文件系统实现

以下是Minio的具体实现,在类上面使用了@Conditional注解,value值为
MinioStorageCondition

@Component
@Conditional(value = MinioStorageCondition.class)
public class MinioStorageService implements StorageService {

    @Override
    public void init() {
        // 初始化操作
    }

    @Override
    public void put(MultipartFile file) {
        
    }
}

MinioStorageCondition条件判断

MinioStorageCondition的作用就是判断条件是否匹配,它实现
Condition
接口,要使用@Conditional,其判断类必须要实现Condition接口,然后自己实现
matches
方法逻辑,以下就是判断storageType是否为minio,如果为minio,那么就返回true,就代表要创建
MinioStorageService
这个bean,为false则不创建。

public class MinioStorageCondition implements Condition {
    @Override
    public boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) {
        String storageType = context.getEnvironment().getProperty("storageType");
        return "minio".equals(storageType);
    }
}

源码解析

spring在扫描bean的时候,会判断对应的bean是否有@Conditional注解,如果有,则会进入value中的类,进去判断是否符合条件,如果符合,则返回true,就能够注册,实际上如果符合条件,那么就能将BeanDefinition注册进BeanFactory,如果不符合,自然不能注册进。

如下是源码的时序图

从上面的时序图中可以看出,整个过程涉及的类还是挺多的,不过这还不是完整流程,只是从扫描类开始,Spring会扫描工程路径下的类,这个路径可以通过@ComponentScan进行指定,如果是SpringBoot项目,则就为当前工程,然后筛选出需要注册的bean并注册到BeanFactory,对于标注有@Conditional注解的类,会进入@Conditional中value的类中,就是上面的
MinioStorageCondition
或者
FastDFSStorageCondition
,然后进行匹配,不满足条件的则不会被注册。

@Conditional的具体流程也比较简单,就不一一赘述,可以看着上面的时序图去看源码实现。

总结

上面对@Conditional的使用,原理等进行简单的介绍,@Conditional注解在SpringBoot中用得还是比较多的,特别是它衍生出来的一些注解,这些注解都是基于它来进行二次封装的,在SpringBoot中,对于很多starter,里面几乎都会有@Conditional和@Conditional衍生注解的使用,我们后续会挑选出一些来说。

今天的分享就到这里,感谢你的观看,我们下期见!

golang pprof 监控系列(5) —— cpu 占用率 统计原理

大家好,我是蓝胖子。

经过前面的几节对pprof的介绍,对pprof统计的原理算是掌握了七八十了,我们对memory,block,mutex,trace,goroutine,threadcreate这些维度的统计原理都进行了分析,但唯独还没有分析pprof 工具是如何统计cpu使用情况的,今天我们来分析下这部分。

http 接口暴露的方式

还记得
golang pprof监控系列(2) —— memory,block,mutex 使用
里我们启动了一个http服务来暴露各种性能指标信息。让我们再回到当时启动http服务看到的网页图。

image.png

当点击上图中profile链接时,便会下载一个关于cpu指标信息的二进制文件。这个二进制文件同样可以用go tool pprof 工具去分析,同样,关于go tool pprof的使用不是本文的重点,网上的资料也相当多,所以我略去了这部分。

紧接着,我们来快速看下如何用程序代码的方式生成cpu的profile文件。

程序代码生成profile

os.Remove("cpu.out")
	f, _ := os.Create("cpu.out")
	defer f.Close()
	pprof.StartCPUProfile(f)
	defer 	pprof.StopCPUProfile()
	// .... do other things

代码比较简单,pprof.StartCPUProfile 则开始统计 cpu使用情况,pprof.StopCPUProfile则停止统计cpu使用情况,将程序使用cpu的情况写入cpu.out文件。cpu.out文件我们则可以用go tool pprof去分析了。

好的,在快速的看完如何在程序中暴露cpu性能指标后,我们来看看golang是如何统计各个函数cpu使用情况的。接下来,正戏开始。

cpu 统计原理分析

首先要明白,我们究竟要统计的是什么内容?我们需要知道cpu的使用情况,换言之就是
cpu的工作时间花在了哪些函数上,最后是不是就是看函数在cpu上的工作时长

那么函数的在cpu上工作时长应该如何去进行统计?

golang还是采用部分采样的方式,通过
settimmer
系统调用设置了 发送SIGPROF 的定时器,当达到
runtime.SetCPUProfileRate设置的周期间隔
时,操作系统就会向进程发送SIGPROF 信号,默认情况下是100Mz(10毫秒)。

一旦设置了 发送SIGPROF信号的定时器,操作系统便会定期向进程发送SIGPROF信号。

设置定时器的代码便是在我们调用pprof.StartCPUProfile方法开启cpu信息采样的时候。代码如下,

// src/runtime/pprof/pprof.go:760
func StartCPUProfile(w io.Writer) error {
	const hz = 100
	cpu.Lock()
	defer cpu.Unlock()
	if cpu.done == nil {
		cpu.done = make(chan bool)
	}
	// Double-check.
	if cpu.profiling {
		return fmt.Errorf("cpu profiling already in use")
	}
	cpu.profiling = true
	runtime.SetCPUProfileRate(hz)
	go profileWriter(w)
	return nil
}

在倒数第三行的时候调用了设置采样的周期,并且紧接着profileWriter 就是用一个协程启动后去不断的读取cpu的采样数据写到文件里。而调用settimer的地方就是在runtime.SetCPUProfileRate里,runtime.SetCPUProfileRate最终会调用 setcpuprofilerate方法 ,setcpuprofilerate 又会去调用setProcessCPUProfiler方法设置settimer 定时器。

// src/runtime/signal_unix.go:269
func setProcessCPUProfiler(hz int32) {
  .....
		var it itimerval
		it.it_interval.tv_sec = 0
		it.it_interval.set_usec(1000000 / hz)
		it.it_value = it.it_interval
		setitimer(_ITIMER_PROF, &it, nil)
....	

经过上述步骤后,cpu的采样就真正开始了,之后就是定时器被触发送SIGPROF信号,进程接收到这个信号后,会对当前函数的调用堆栈进行记录,由于默认的采样周期设置的是100Mz,所以,你可以理解为每10ms,golang就会统计下当前正在运行的是哪个函数,在采样的这段时间内,哪个函数被统计的次数越多,是不是就能说明这个函数在这段时间内占用cpu的工作时长就越多了。

由于golang借助了linux的信号机制去进行cpu执行函数的采样,这里有必要额外介绍下linux 进程与信号相关的知识。首先来看下线程处理信号的时机在什么时候。

线程处理信号的时机

线程对信号的处理时机一般 是在由内核态返回到用户态之前
,也就是说,当线程由于系统调用或者中断进入内核态后, 当系统调用结束或者中断处理完成后,在返回到用户态之前,操作系统会检查这个线程是不是有未处理的信号,如果有的话,那么会先切回到用户态让 线程会首先处理信号,信号处理完毕后 又返回内核态,
内核此时才会将调用栈设置为中断或者系统调用时 用户进程中断的地方
,然后切换到用户态后就继续在用户进程之前中断的地方继续执行程序逻辑了。由于进程几乎每时每刻都在进行诸如系统调用的工作,可以认为,信号的处理是几乎实时的。 如下是线程内核态与用户态切换的过程,正式信号处理检查的地方。整个过程可以用下面的示意图表示。

信号处理时机.jpg

知道了信号是如何被线程处理的,还需要了解下,内核会如何发送信号给进程。

内核发送信号的方式

内核向进程发信号的方式是对进程中的一个线程发送信号,而通过
settimmer
系统调用设置定时器 发送SIGPROF 信号的方式就是随机的对进程中的一个运行中线程去进行发送。而运行中线程接收到这个信号后,就调用自身的处理函数对这个信号去进行处理,对于SIGPROF 信号而言,则是将线程中断前的函数栈记录下来,用于后续分析函数占用cpu的工作时长。

由于只是随机的向一个运行中的线程发送SIGPROF 信号,这里涉及到了两个问题?

第一
因为同一个进程中只有一个线程在进行采样,所以在随机选择运行线程发送SIGPROF信号时,要求选择线程时的公平性,不然可能会出现A,B两个线程,A线程接收到SIGPROF信号的次数远远大于B 线程接收SIGPROF信号的次数,这样对A线程进行采样的次数将会变多,影响了我们采样的结果。

而golang用settimmer 设置定时器发送SIGPROF 信号 的方式的确被证实在linux上存在线程选择公平性问题(但是mac os上没有这个问题)
关于这个问题的讨论在github上有记录,
这是链接
这个问题已经在go1.18上得到了解决,解决方式我会在下面给出,我们先来看随机的向一个运行中的线程发送SIGPROF 信号 引发的第二个问题。

第二
因为是向一个运行中的线程去发送信号,所以我们只能统计到采样时间段内在cpu上运行的函数,而那些io阻塞的函数将不能被统计到,关于这点业内已经有开源库帮助解决,
https://github.com/felixge/fgprof
,不过由于这个库进行采样时会stop the world ,所以其作者强烈建议如果go协程数量比较多时,将go版本升级到1.19再使用。后续有机会再来探讨这个库的实现吧,我们先回到如何解决settimer函数在选择线程的公平性问题上。

采样数据的公平性

为了解决公平性问题,golang在settimer的系统调用的基础上增加了timer_create系统调用timer_create 可以单独的为每一个线程都创建定时器,这样每个运行线程都会采样到自己的函数堆栈了。所以在go1.18版本对pprof.StartCPUProfile内部创建定时器的代码进行了改造。刚才有提到pprof.StartCPUProfile 底层其实是调用setcpuprofilerate 这个方法去设置的定时器,所以我们来看看go1.18和go1.17版本在这个方法的实现上主要是哪里不同。

// go1.17 版本 src/runtime/proc.go:4563 
func setcpuprofilerate(hz int32) {
	if hz < 0 {
		hz = 0
	}
	_g_ := getg()
	_g_.m.locks++
	setThreadCPUProfiler(0)
	for !atomic.Cas(&prof.signalLock, 0, 1) {
		osyield()
	}
	if prof.hz != hz {
	   // 设置进程维度的 SIGPROF 信号发送器
		setProcessCPUProfiler(hz)
		prof.hz = hz
	}
	atomic.Store(&prof.signalLock, 0)
	lock(&sched.lock)
	sched.profilehz = hz
	unlock(&sched.lock)
	if hz != 0 {
	   // 设置线程维度的SIGPROF 信号定时器
		setThreadCPUProfiler(hz)
	}
	_g_.m.locks--
}

上述是go1.17版本的setcpuprofilerate 代码,如果你再去看 go1.18版本的代码,会发现他们在这个方法上是一模一样的,都是调用了setProcessCPUProfiler 和setThreadCPUProfiler,setProcessCPUProfiler 设置进程维度的发送SIGPROF信号定时器,setThreadCPUProfiler设置线程维度的发送SIGPROF信号的定时器,但其实setThreadCPUProfiler 在go1.17的实现上并不完整。

// go 1.17  src/runtime/signal_unix.go:314
func setThreadCPUProfiler(hz int32) {
	getg().m.profilehz = hz
}

go1.17版本上仅仅是为协程里代表线程的m变量设置了一个profilehz(采样的频率),并没有真正实现线程维度的采样。

// go 1.18 src/runtime/os_linux.go:605 
....
// setThreadCPUProfiler 方法内部 timer_create的代码段
var timerid int32
	var sevp sigevent
	sevp.notify = _SIGEV_THREAD_ID
	sevp.signo = _SIGPROF
	sevp.sigev_notify_thread_id = int32(mp.procid)
	ret := timer_create(_CLOCK_THREAD_CPUTIME_ID, &sevp, &timerid)
	if ret != 0 {
		return
	}
	....

在go1.18版本上的setThreadCPUProfiler则真正实现了这部分逻辑,由于go1.18版本它同时调用了setProcessCPUProfiler以及setThreadCPUProfiler,这样在接收SIGPROF信号时就会出现重复计数的问题。

所以go1.18在处理SIGPROF信号的时候也做了去重处理,所以在golang信号处理的方法sighandler 内部有这样一段逻辑。

func sighandler(sig uint32, info *siginfo, ctxt unsafe.Pointer, gp *g) {
	_g_ := getg()
	c := &sigctxt{info, ctxt}

	if sig == _SIGPROF {
		mp := _g_.m
		// Some platforms (Linux) have per-thread timers, which we use in
		// combination with the process-wide timer. Avoid double-counting.
		if validSIGPROF(mp, c) {
			sigprof(c.sigpc(), c.sigsp(), c.siglr(), gp, mp)
		}
		return
	}
	.....

如果发现信号是_SIGPROF 那么会通过validSIGPROF 去检测此次的_SIGPROF信号是否应该被统计。validSIGPROF的检测逻辑这里就不展开了。

总结

cpu的统计原理与前面所讲的指标统计的原理稍微复杂点,涉及到了linux信号处理相关的内容,cpu统计的原理,简而言之,就是通过设置一个发送SIGPROF信号的定时器,然后用户程序通过接收操作系统定时发送的SIGPROF信号来对用户程序正在执行的堆栈函数进行统计。在采样时间内,同一个函数被统计的越多,说明该函数占用的cpu工作时长就越长。

漏洞说明:
跨站脚本攻击(Cross Site Scripting),为了不和层叠样式表(Cascading Style Sheets, CSS)的缩写混淆,故将跨站脚本攻击缩写为XSS。恶意攻击者往Web页面里插入恶意Web脚本代码(html、javascript、css等),当用户浏览该页面时,嵌入其中的Web脚本代码会被执行,从而达到恶意攻击用户的特殊目的。

测试步骤
访问系统网站,点击基础报告库进行编辑,使用Burp抓包并重新构造数据包

在这里插入图片描述

重新访问,成功触发了XSS弹窗

在这里插入图片描述

解决方法:

将危险内容过滤去除,用HTML转义字符串(Escape Sequence)表达的则保留
添加脚本过滤类

    /// <summary>
    /// Html 脚本过滤
    /// </summary>
    public class NHtmlFilter
    {
        protected static readonly RegexOptions REGEX_FLAGS_SI = RegexOptions.IgnoreCase | RegexOptions.Singleline | RegexOptions.Compiled;

        private static string P_COMMENTS = "<!--(.*?)-->";
        private static Regex P_COMMENT = new Regex("^!--(.*)--$", REGEX_FLAGS_SI);
        private static string P_TAGS = "<(.*?)>";
        private static Regex P_END_TAG = new Regex("^/([a-z0-9]+)", REGEX_FLAGS_SI);
        private static Regex P_START_TAG = new Regex("^([a-z0-9]+)(.*?)(/?)$", REGEX_FLAGS_SI);
        private static Regex P_QUOTED_ATTRIBUTES = new Regex("([a-z0-9|(a-z0-9\\-a-z0-9)]+)=([\"'])(.*?)\\2", REGEX_FLAGS_SI);
        private static Regex P_UNQUOTED_ATTRIBUTES = new Regex("([a-z0-9]+)(=)([^\"\\s']+)", REGEX_FLAGS_SI);
        private static Regex P_PROTOCOL = new Regex("^([^:]+):", REGEX_FLAGS_SI);
        private static Regex P_ENTITY = new Regex("&#(\\d+);?");
        private static Regex P_ENTITY_UNICODE = new Regex("&#x([0-9a-f]+);?");
        private static Regex P_ENCODE = new Regex("%([0-9a-f]{2});?");
        private static Regex P_VALID_ENTITIES = new Regex("&([^&;]*)(?=(;|&|$))");
        private static Regex P_VALID_QUOTES = new Regex("(>|^)([^<]+?)(<|$)", RegexOptions.Singleline | RegexOptions.Compiled);
        private static string P_END_ARROW = "^>";
        private static string P_BODY_TO_END = "<([^>]*?)(?=<|$)";
        private static string P_XML_CONTENT = "(^|>)([^<]*?)(?=>)";
        private static string P_STRAY_LEFT_ARROW = "<([^>]*?)(?=<|$)";
        private static string P_STRAY_RIGHT_ARROW = "(^|>)([^<]*?)(?=>)";
        private static string P_AMP = "&";
        private static string P_QUOTE = "\"";
        private static string P_LEFT_ARROW = "<";
        private static string P_RIGHT_ARROW = ">";
        private static string P_BOTH_ARROWS = "<>";

        // @xxx could grow large... maybe use sesat's ReferenceMap
        private static Dictionary<string, string> P_REMOVE_PAIR_BLANKS = new Dictionary<string, string>();
        private static Dictionary<string, string> P_REMOVE_SELF_BLANKS = new Dictionary<string, string>();
        /** 
         * flag determining whether to try to make tags when presented with "unbalanced"
         * angle brackets (e.g. "<b text </b>" becomes "<b> text </b>").  If set to false,
         * unbalanced angle brackets will be html escaped.
         */
        protected static bool alwaysMakeTags = true;

        /**
         * flag determing whether comments are allowed in input String.
         */
        protected static bool stripComment = true;


        /// <summary>
        /// 不允许
        /// </summary>
        private string[] vDisallowed { get; set; }
        /// <summary>
        /// 允许
        /// </summary>
        protected Dictionary<string, List<string>> vAllowed { get; set; }

        /** counts of open tags for each (allowable) html element **/
        protected Dictionary<string, int> vTagCounts;

        /** html elements which must always be self-closing (e.g. "<img />") **/
        protected string[] vSelfClosingTags;

        /** html elements which must always have separate opening and closing tags (e.g. "<b></b>") **/
        protected string[] vNeedClosingTags;

        /** attributes which should be checked for valid protocols **/
        protected string[] vProtocolAtts;

        /** allowed protocols **/
        protected string[] vAllowedProtocols;

        /** tags which should be removed if they contain no content (e.g. "<b></b>" or "<b />") **/
        protected string[] vRemoveBlanks;

        /** entities allowed within html markup **/
        protected string[] vAllowedEntities;


        /// <summary>
        /// 是否为调试
        /// </summary>
        protected bool vDebug;

        public NHtmlFilter() : this(false) { }

        public NHtmlFilter(bool debug)
        {
            //List<Item> vAllowed = new List<Item>();
            vAllowed = new Dictionary<string, List<string>>();
            #region 允许通过数组

            vAllowed.Add("a", new List<string>() { "target", "href", "title", "class", "style" });
            vAllowed.Add("addr", new List<string>() { "title", "class", "style" });
            vAllowed.Add("address", new List<string>() { "class", "style" });
            vAllowed.Add("area", new List<string>() { "shape", "coords", "href", "alt" });
            vAllowed.Add("article", new List<string>() { });
            vAllowed.Add("aside", new List<string>() { });
            vAllowed.Add("audio", new List<string>() { "autoplay", "controls", "loop", "preload", "src", "class", "style" });
            vAllowed.Add("b", new List<string>() { "class", "style" });
            vAllowed.Add("bdi", new List<string>() { "dir" });
            vAllowed.Add("bdo", new List<string>() { "dir" });
            vAllowed.Add("big", new List<string>() { });
            vAllowed.Add("blockquote", new List<string>() { "cite", "class", "style" });
            vAllowed.Add("br", new List<string>() { });
            vAllowed.Add("caption", new List<string>() { "class", "style" });
            vAllowed.Add("center", new List<string>() { });
            vAllowed.Add("cite", new List<string>() { });
            vAllowed.Add("code", new List<string>() { "class", "style" });
            vAllowed.Add("col", new List<string>() { "align", "valign", "span", "width", "class", "style" });
            vAllowed.Add("colgroup", new List<string>() { "align", "valign", "span", "width", "class", "style" });
            vAllowed.Add("dd", new List<string>() { "class", "style" });
            vAllowed.Add("del", new List<string>() { "datetime" });
            vAllowed.Add("details", new List<string>() { "open" });
            vAllowed.Add("div", new List<string>() { "class", "style" });
            vAllowed.Add("dl", new List<string>() { "class", "style" });
            vAllowed.Add("dt", new List<string>() { "class", "style" });
            vAllowed.Add("em", new List<string>() { "class", "style" });
            vAllowed.Add("font", new List<string>() { "color", "size", "face" });
            vAllowed.Add("footer", new List<string>() { });
            vAllowed.Add("h1", new List<string>() { "class", "style" });
            vAllowed.Add("h2", new List<string>() { "class", "style" });
            vAllowed.Add("h3", new List<string>() { "class", "style" });
            vAllowed.Add("h4", new List<string>() { "class", "style" });
            vAllowed.Add("h5", new List<string>() { "class", "style" });
            vAllowed.Add("h6", new List<string>() { "class", "style" });
            vAllowed.Add("header", new List<string>() { });
            vAllowed.Add("hr", new List<string>() { });
            vAllowed.Add("i", new List<string>() { "class", "style" });
            vAllowed.Add("img", new List<string>() { "src", "alt", "title", "style", "width", "height", "id", "_src", "loadingclass", "class", "data-latex", "data-id", "data-type", "data-s" });
            vAllowed.Add("ins", new List<string>() { "datetime" });
            vAllowed.Add("li", new List<string>() { "class", "style" });
            vAllowed.Add("mark", new List<string>() { });
            vAllowed.Add("nav", new List<string>() { });
            vAllowed.Add("ol", new List<string>() { "class", "style" });
            vAllowed.Add("p", new List<string>() { "class", "style" });
            vAllowed.Add("pre", new List<string>() { "class", "style" });
            vAllowed.Add("s", new List<string>() { });
            vAllowed.Add("section", new List<string>() { });
            vAllowed.Add("small", new List<string>() { });
            vAllowed.Add("span", new List<string>() { "class", "style" });
            vAllowed.Add("sub", new List<string>() { "class", "style" });
            vAllowed.Add("sup", new List<string>() { "class", "style" });
            vAllowed.Add("strong", new List<string>() { "class", "style" });
            vAllowed.Add("table", new List<string>() { "width", "border", "align", "valign", "class", "style" });
            vAllowed.Add("tbody", new List<string>() { "align", "valign", "class", "style" });
            vAllowed.Add("td", new List<string>() { "width", "rowspan", "colspan", "align", "valign", "class", "style" });
            vAllowed.Add("tfoot", new List<string>() { "align", "valign", "class", "style" });
            vAllowed.Add("th", new List<string>() { "width", "rowspan", "colspan", "align", "valign", "class", "style" });
            vAllowed.Add("thead", new List<string>() { "align", "valign", "class", "style" });
            vAllowed.Add("tr", new List<string>() { "rowspan", "align", "valign", "class", "style" });
            vAllowed.Add("tt", new List<string>() { });
            vAllowed.Add("u", new List<string>() { });
            vAllowed.Add("ul", new List<string>() { "class", "style" });
            vAllowed.Add("video", new List<string>() { "autoplay", "controls", "loop", "preload", "src", "height", "width", "class", "style" });
            #endregion


            vDebug = debug;
            vTagCounts = new Dictionary<string, int>();

            vSelfClosingTags = new string[] { "img" };
            vNeedClosingTags = new string[] { "a", "b", "strong", "i", "em" };
            vDisallowed = new string[] { "script" };
            vAllowedProtocols = new string[] { "http", "mailto" }; // no ftp.
            vProtocolAtts = new string[] { "src", "href" };
            vRemoveBlanks = new string[] { "a", "b", "strong", "i", "em" };
            vAllowedEntities = new string[] { "amp", "gt", "lt", "quot" };
            stripComment = true;
            alwaysMakeTags = true;
        }


        protected void reset()
        {
            vTagCounts = new Dictionary<string, int>();
        }

        protected void debug(string msg)
        {
            if (vDebug)
                System.Diagnostics.Debug.WriteLine(msg);
        }

        //---------------------------------------------------------------
        // my versions of some PHP library functions

        public static string chr(int dec)
        {
            return "" + (char)dec;
        }

        /// <summary>
        /// 转换成实体字符
        /// </summary>
        /// <param name="str"></param>
        /// <returns></returns>
        public static string htmlSpecialChars(string str)
        {
            str = str.Replace(P_QUOTE, "\"");

            str = str.Replace(P_LEFT_ARROW, "<");
            str = str.Replace(P_RIGHT_ARROW, ">");
            str = str.Replace("\n", "<br>");
            return str;
        }

        //---------------------------------------------------------------

        /**
         * given a user submitted input String, filter out any invalid or restricted
         * html.
         * 
         * @param input text (i.e. submitted by a user) than may contain html
         * @return "clean" version of input, with only valid, whitelisted html elements allowed
         */
        public string filter(string input)
        {
            reset();
            string s = input;

            debug("************************************************");
            debug("              INPUT: " + input);

            s = escapeComments(s);
            debug("     escapeComments: " + s);

            s = balanceHTML(s);
            debug("        balanceHTML: " + s);

            s = checkTags(s);
            debug("          checkTags: " + s);

            s = processRemoveBlanks(s);
            debug("processRemoveBlanks: " + s);

            s = validateEntities(s);
            debug("    validateEntites: " + s);

            debug("************************************************\n\n");
            return s;
        }

        protected string escapeComments(string s)
        {
            return Regex.Replace(s, P_COMMENTS, new MatchEvaluator(ConverMatchComments), RegexOptions.Singleline);
        }

        protected string regexReplace(string regex_pattern, string replacement, string s)
        {
            return Regex.Replace(s, regex_pattern, replacement);
        }

        protected string balanceHTML(string s)
        {
            if (alwaysMakeTags)
            {
                //
                // try and form html
                //
                s = regexReplace(P_END_ARROW, "", s);
                s = regexReplace(P_BODY_TO_END, "<$1>", s);
                s = regexReplace(P_XML_CONTENT, "$1<$2", s);

            }
            else
            {
                //
                // escape stray brackets
                //
                s = regexReplace(P_STRAY_LEFT_ARROW, "<$1", s);
                s = regexReplace(P_STRAY_RIGHT_ARROW, "$1$2><", s);

                //
                // the last regexp causes '<>' entities to appear
                // (we need to do a lookahead assertion so that the last bracket can
                // be used in the next pass of the regexp)
                //
                s = s.Replace(P_BOTH_ARROWS, "");
            }
            return s;
        }

        protected string checkTags(string s)
        {
            //替换不允许标签
            foreach (var item in vDisallowed)
            {
                s = Regex.Replace(s, string.Format(@"<{0}\b(.)*?>(.)+?</{0}>", item), "");
            }
            s = Regex.Replace(s, P_TAGS, new MatchEvaluator(ConverMatchTags), RegexOptions.Singleline);

            // these get tallied in processTag
            // (remember to reset before subsequent calls to filter method)
            foreach (string key in vTagCounts.Keys)
            {
                for (int ii = 0; ii < vTagCounts[key]; ii++)
                {
                    s += "</" + key + ">";
                }
            }

            return s;
        }

        protected string processRemoveBlanks(string s)
        {
            foreach (string tag in vRemoveBlanks)
            {
                s = regexReplace("<" + tag + "(\\s[^>]*)?></" + tag + ">", "", s);
                s = regexReplace("<" + tag + "(\\s[^>]*)?/>", "", s);
            }
            return s;
        }

        private string processTag(string s)
        {
            // ending tags
            Match m = P_END_TAG.Match(s);
            if (m.Success)
            {
                string name = m.Groups[1].Value.ToLower();
                if (allowed(name))
                {
                    if (!inArray(name, vSelfClosingTags))
                    {
                        if (vTagCounts.ContainsKey(name))
                        {
                            vTagCounts[name] = vTagCounts[name] - 1;
                            return "</" + name + ">";
                        }
                    }
                }
            }


            // starting tags
            m = P_START_TAG.Match(s);
            if (m.Success)
            {
                string name = m.Groups[1].Value.ToLower();
                string body = m.Groups[2].Value;
                string ending = m.Groups[3].Value;

                //debug( "in a starting tag, name='" + name + "'; body='" + body + "'; ending='" + ending + "'" );
                if (allowed(name))
                {
                    string params1 = "";

                    MatchCollection m2 = P_QUOTED_ATTRIBUTES.Matches(body);
                    MatchCollection m3 = P_UNQUOTED_ATTRIBUTES.Matches(body);
                    List<string> paramNames = new List<string>();
                    List<string> paramValues = new List<string>();
                    foreach (Match match in m2)
                    {
                        paramNames.Add(match.Groups[1].Value); //([a-z0-9]+)
                        paramValues.Add(match.Groups[3].Value); //(.*?)
                    }
                    foreach (Match match in m3)
                    {
                        paramNames.Add(match.Groups[1].Value); //([a-z0-9]+)
                        paramValues.Add(match.Groups[3].Value); //([^\"\\s']+)
                    }

                    string paramName, paramValue;
                    for (int ii = 0; ii < paramNames.Count; ii++)
                    {
                        paramName = paramNames[ii].ToLower();
                        paramValue = paramValues[ii];

                        if (allowedAttribute(name, paramName))
                        {
                            if (inArray(paramName, vProtocolAtts))
                            {
                                paramValue = processParamProtocol(paramValue);
                            }
                            params1 += " " + paramName + "=\"" + paramValue + "\"";
                        }
                    }

                    if (inArray(name, vSelfClosingTags))
                    {
                        ending = " /";
                    }

                    if (inArray(name, vNeedClosingTags))
                    {
                        ending = "";
                    }

                    if (ending == null || ending.Length < 1)
                    {
                        if (vTagCounts.ContainsKey(name))
                        {
                            vTagCounts[name] = vTagCounts[name] + 1;
                        }
                        else
                        {
                            vTagCounts.Add(name, 1);
                        }
                    }
                    else
                    {
                        ending = " /";
                    }
                    return "<" + name + params1 + ending + ">";
                }
                else
                {
                    return "";
                }
            }

            // comments
            m = P_COMMENT.Match(s);
            if (!stripComment && m.Success)
            {
                return "<" + m.Value + ">";
            }

            return "";
        }

        private string processParamProtocol(string s)
        {
            s = decodeEntities(s);
            Match m = P_PROTOCOL.Match(s);
            if (m.Success)
            {
                string protocol = m.Groups[1].Value;
                if (!inArray(protocol, vAllowedProtocols))
                {
                    // bad protocol, turn into local anchor link instead
                    s = "#" + s.Substring(protocol.Length + 1, s.Length - protocol.Length - 1);
                    if (s.StartsWith("#//"))
                    {
                        s = "#" + s.Substring(3, s.Length - 3);
                    }
                }
            }
            return s;
        }

        private string decodeEntities(string s)
        {

            s = P_ENTITY.Replace(s, new MatchEvaluator(ConverMatchEntity));

            s = P_ENTITY_UNICODE.Replace(s, new MatchEvaluator(ConverMatchEntityUnicode));

            s = P_ENCODE.Replace(s, new MatchEvaluator(ConverMatchEntityUnicode));

            s = validateEntities(s);
            return s;
        }

        private string validateEntities(string s)
        {
            s = P_VALID_ENTITIES.Replace(s, new MatchEvaluator(ConverMatchValidEntities));
            s = P_VALID_QUOTES.Replace(s, new MatchEvaluator(ConverMatchValidQuotes));
            return s;
        }

        private static bool inArray(string s, string[] array)
        {
            foreach (string item in array)
            {
                if (item != null && item.Equals(s))
                {
                    return true;
                }
            }
            return false;
        }

        private bool allowed(string name)
        {
            return (vAllowed.Count == 0 || vAllowed.ContainsKey(name)) && !inArray(name, vDisallowed);
        }

        private bool allowedAttribute(string name, string paramName)
        {
            return allowed(name) && (vAllowed.Count == 0 || vAllowed[name].Contains(paramName));
        }

        private string checkEntity(string preamble, string term)
        {

            return ";".Equals(term) && isValidEntity(preamble)
                    ? '&' + preamble
                    : "&" + preamble;
        }
        private bool isValidEntity(string entity)
        {
            return inArray(entity, vAllowedEntities);
        }
        private static string ConverMatchComments(Match match)
        {
            string matchValue = "<!--" + htmlSpecialChars(match.Groups[1].Value) + "-->";
            return matchValue;
        }

        private string ConverMatchTags(Match match)
        {
            string matchValue = processTag(match.Groups[1].Value);
            return matchValue;
        }

        private string ConverMatchEntity(Match match)
        {
            string v = match.Groups[1].Value;
            int decimal1 = int.Parse(v);
            return chr(decimal1);
        }

        private string ConverMatchEntityUnicode(Match match)
        {
            string v = match.Groups[1].Value;
            int decimal1 = Convert.ToInt32("0x" + v, 16);
            return chr(decimal1);
        }

        private string ConverMatchValidEntities(Match match)
        {
            string one = match.Groups[1].Value; //([^&;]*)
            string two = match.Groups[2].Value; //(?=(;|&|$))
            return checkEntity(one, two);
        }
        private string ConverMatchValidQuotes(Match match)
        {
            string one = match.Groups[1].Value; //(>|^)
            string two = match.Groups[2].Value; //([^<]+?)
            string three = match.Groups[3].Value;//(<|$)
            return one + regexReplace(P_QUOTE, "\"", two) + three;
        }

        public bool isAlwaysMakeTags()
        {
            return alwaysMakeTags;
        }

        public bool isStripComments()
        {
            return stripComment;
        }

        class Item
        {
            public string name { get; set; }
            public List<string> parameter { get; set; }
        }

    }

源代码出自:
https://www.cnblogs.com/OleRookie/p/5970167.html

在请求时对参数的内容进行过滤:

var nHtmlFilter = new NHtmlFilter(false);
surveyPayload.PayloadContent = nHtmlFilter.filter(surveyPayload.PayloadContent);

再次请求时,已将危险代码转成HTML转义字符串的形式了

在这里插入图片描述

在这里插入图片描述

前情提要:
在用jmeter做接口测试时,对请求体进行参数化,执行结果报错。但在不参数化的情况下,执行结果成功,而且参数化后,请求中读取到的参数是正确的(执行失败与执行成功时的参数一致)。

问题排查:
参数化后,请求中的两个反斜杠(\)变成了一个反斜杠(\)。

问题原因:
参数化的时候,${}把反斜杠给转义了。
解决办法:
把单反斜杠替换成双反斜杠。


问题发现、排查及解决过程,如下:


1. 请求未参数化

1.1 【线程组】请求消息体数据

1.2 【查看结果树】请求

1.3 【查看结果树】响应数据

2. 请求参数化

2.1 【线程组】请求消息体数据

2.2 【查看结果树】请求

2.3 【查看结果树】响应数据

3. 排查分析

3.1 排查

对比两种情况下的【查看结果树】请求,发现参数化后的三个反斜杠变成了两个。

3.2 分析

参数化的时候,${}把反斜杠给转义了。即:请求中的两个反斜杠(\)变成了一个反斜杠(\)。

4. 解决办法

参数化后,将请求中的\\\修改为\\\\\,再次执行,执行成功。

4.1 【线程组】请求消息体数据

4.2 【查看结果树】请求

4.3 【查看结果树】响应数据


综上,解决。这应该是jmeter一个bug。

本文已收录到
AndroidFamily
,技术和职场问题,请关注公众号 [彭旭锐] 提问。

大家好,我是小彭。

上周跟大家讲到小彭文章风格的问题,和一些朋友聊过以后,至少在算法题解方面确定了小彭的风格。虽然竞赛算法题的文章受众非常小,但却有很多像我一样的初学者,他们有兴趣参加但容易被题目难度和大神选手的题解劝退。

考虑到这些跟我一样的小白,我决定算法题解风格会向这些初学者倾斜,我们不会强调最优解法,而是强调从题意分析到问题抽象,再从暴力解法一步步升级到最优解法的推导过程,希望能帮到喜欢算法的朋友,向 Guardian 出发。


好一波强行自证价值?