searchusermenu
  • 发布文章
  • 消息中心
点赞
收藏
评论
分享
原创

Nashorn沙箱技术

2023-05-26 01:49:28
116
0

从Java 8开始(nashorn在Java 15中去掉了),可以使用nashorn来执行Javascript代码,使用方法如下:

ScriptEngine engine = new ScriptEngineManager().getEngineByName("nashorn");
engine.eval("var s = 'Hello World!'; print(s);");

Nashorn提供了非常简单的方式让用户可以在JS代码中引用Java的方法

public class NashornTest {
	@Test
	public void test() throws ScriptException {
		String js = "var test = Java.type('test.NashornTest'); test.printName('abc')";
		ScriptEngine engine = new ScriptEngineManager().getEngineByName("nashorn");
		engine.eval(js);
	}

	public static void printName(String name) {
		System.out.println("name is " + name);
	}
}

Nashorn虽然强大,但如果在生产环境中使用Nashorn来执行用户自己写的JS代码还是得考虑以下问题:

1,如何防止用户在JS中使用比较危险的Java方法,如System.exit()?

2,如何防止用户的JS占用过多的内存和CPU资源?

3,如何提升JS执行的并发性能?

 

delight.nashornsandbox项目为开发者提供了Nashorn沙箱环境来执行JS代码,解决以上问题

1,可以在初始化Nashorn沙箱时定义一个白名单,只允许JS引用白名单中的Java类

public class NashornTest {
	public static void printName(String name) {
		System.out.println("name is " + name);
	}

	@Test
	public void testSandbox() throws ScriptCPUAbuseException, ScriptException {
		String js = "var test = Java.type('test.NashornTest'); test.printName('abc')";
		NashornSandbox sandbox = NashornSandboxes.create("--language=es6");
		// 允许JS中引用NashornTest类
		sandbox.allow(NashornTest.class);
		sandbox.eval(js);
	}
}

2,设置线程每次执行JS使用的CPU和内存上限,需要指定ExecutorService作为JS的线程执行器

ExecutorService executor = Executors.newCachedThreadPool();
NashornSandbox sandbox = NashornSandboxes.create("--language=es6");
// 允许JS中引用NashornTest类
sandbox.allow(NashornTest.class);
// 设置CPU时间上限为3000毫秒,在JS中sleep并不占用这个时间
sandbox.setMaxCPUTime(3000);
// 设置内存上限为5MB
sandbox.setMaxMemory(5 * 1024 * 1024);
sandbox.allowNoBraces(false);
sandbox.allowLoadFunctions(true);
sandbox.allowPrintFunctions(true);
// 设置缓存JS代码数量,JS预编译后缓存起来
sandbox.setMaxPreparedStatements(50);
sandbox.setExecutor(executor);

如果设置了CPU和内存上限,Nashorn沙箱在执行JS的时候,就会额外启动一个监控线程,这个监控线程就会循环去调用ThreadMXBean.getThreadAllocatedBytes(执行线程Id) 和ThreadMXBean.getThreadCpuTime(执行线程Id) 这两个方法来判断CPU和内存是否达到上限,如果其中一个达到上限,则会中断JS的执行并抛出异常。

需要注意的是,ThreadMXBean.getThreadAllocatedBytes并不是返回指定线程所创建的对象目前占用的堆内存大小,而是返回指定线程已经分配的内存大小(已分配的内存可能已经被释放)

3,并发调用的性能问题,

1)可以创建一个Nashorn沙箱池,省去频繁创建和销毁沙箱的开销

2)设置缓存JS代码数量,提高JS执行速度,sandbox.setMaxPreparedStatements(maxCacheSize);

public class NashornSandboxFactory implements PoolObjectFactory<NashornSandbox> {

	private ExecutorService executor;
	private long maxCpuTime;
	private long maxMemorySize;
	private int maxCacheSize;

	/**
	 * @param executor
	 * @param maxCpuTime    最大CPU时间,毫秒
	 * @param maxMemorySize 最大内存,byte
	 * @param maxCacheSize  最大脚本缓存个数
	 */
	public NashornSandboxFactory(ExecutorService executor, long maxCpuTime, long maxMemorySize, int maxCacheSize) {
		this.executor = executor;
		this.maxCpuTime = maxCpuTime;
		this.maxMemorySize = maxMemorySize;
		this.maxCacheSize = maxCacheSize;
	}

	@Override
	public NashornSandbox create() {
		NashornSandbox sandbox = NashornSandboxes.create("--language=es6");
		// 沙箱初始化
		return sandbox;
	}

	@Override
	public boolean readyToTake(NashornSandbox obj) {
		return true;
	}

	@Override
	public boolean readyToRestore(NashornSandbox obj) {
		return true;
	}

	@Override
	public void destroy(NashornSandbox obj) {
		// do nothing
	}
PoolService<NashornSandbox> pool = new ConcurrentPool<>(new ConcurrentLinkedQueueCollection<>(), factory, coreSize, maxSize, false);
NashornSandbox sandbox = pool.tryTake(timeout, TimeUnit.MILLISECONDS);
sandbox.eval(js);

虽然Java 15已经去掉了nashorn,但可以在pom.xml中单独引入

        <dependency>
            <groupId>org.openjdk.nashorn</groupId>
            <artifactId>nashorn-core</artifactId>
            <version>15.3</version>
        </dependency>
0条评论
0 / 1000
白龙马
14文章数
0粉丝数
白龙马
14 文章 | 0 粉丝
原创

Nashorn沙箱技术

2023-05-26 01:49:28
116
0

从Java 8开始(nashorn在Java 15中去掉了),可以使用nashorn来执行Javascript代码,使用方法如下:

ScriptEngine engine = new ScriptEngineManager().getEngineByName("nashorn");
engine.eval("var s = 'Hello World!'; print(s);");

Nashorn提供了非常简单的方式让用户可以在JS代码中引用Java的方法

public class NashornTest {
	@Test
	public void test() throws ScriptException {
		String js = "var test = Java.type('test.NashornTest'); test.printName('abc')";
		ScriptEngine engine = new ScriptEngineManager().getEngineByName("nashorn");
		engine.eval(js);
	}

	public static void printName(String name) {
		System.out.println("name is " + name);
	}
}

Nashorn虽然强大,但如果在生产环境中使用Nashorn来执行用户自己写的JS代码还是得考虑以下问题:

1,如何防止用户在JS中使用比较危险的Java方法,如System.exit()?

2,如何防止用户的JS占用过多的内存和CPU资源?

3,如何提升JS执行的并发性能?

 

delight.nashornsandbox项目为开发者提供了Nashorn沙箱环境来执行JS代码,解决以上问题

1,可以在初始化Nashorn沙箱时定义一个白名单,只允许JS引用白名单中的Java类

public class NashornTest {
	public static void printName(String name) {
		System.out.println("name is " + name);
	}

	@Test
	public void testSandbox() throws ScriptCPUAbuseException, ScriptException {
		String js = "var test = Java.type('test.NashornTest'); test.printName('abc')";
		NashornSandbox sandbox = NashornSandboxes.create("--language=es6");
		// 允许JS中引用NashornTest类
		sandbox.allow(NashornTest.class);
		sandbox.eval(js);
	}
}

2,设置线程每次执行JS使用的CPU和内存上限,需要指定ExecutorService作为JS的线程执行器

ExecutorService executor = Executors.newCachedThreadPool();
NashornSandbox sandbox = NashornSandboxes.create("--language=es6");
// 允许JS中引用NashornTest类
sandbox.allow(NashornTest.class);
// 设置CPU时间上限为3000毫秒,在JS中sleep并不占用这个时间
sandbox.setMaxCPUTime(3000);
// 设置内存上限为5MB
sandbox.setMaxMemory(5 * 1024 * 1024);
sandbox.allowNoBraces(false);
sandbox.allowLoadFunctions(true);
sandbox.allowPrintFunctions(true);
// 设置缓存JS代码数量,JS预编译后缓存起来
sandbox.setMaxPreparedStatements(50);
sandbox.setExecutor(executor);

如果设置了CPU和内存上限,Nashorn沙箱在执行JS的时候,就会额外启动一个监控线程,这个监控线程就会循环去调用ThreadMXBean.getThreadAllocatedBytes(执行线程Id) 和ThreadMXBean.getThreadCpuTime(执行线程Id) 这两个方法来判断CPU和内存是否达到上限,如果其中一个达到上限,则会中断JS的执行并抛出异常。

需要注意的是,ThreadMXBean.getThreadAllocatedBytes并不是返回指定线程所创建的对象目前占用的堆内存大小,而是返回指定线程已经分配的内存大小(已分配的内存可能已经被释放)

3,并发调用的性能问题,

1)可以创建一个Nashorn沙箱池,省去频繁创建和销毁沙箱的开销

2)设置缓存JS代码数量,提高JS执行速度,sandbox.setMaxPreparedStatements(maxCacheSize);

public class NashornSandboxFactory implements PoolObjectFactory<NashornSandbox> {

	private ExecutorService executor;
	private long maxCpuTime;
	private long maxMemorySize;
	private int maxCacheSize;

	/**
	 * @param executor
	 * @param maxCpuTime    最大CPU时间,毫秒
	 * @param maxMemorySize 最大内存,byte
	 * @param maxCacheSize  最大脚本缓存个数
	 */
	public NashornSandboxFactory(ExecutorService executor, long maxCpuTime, long maxMemorySize, int maxCacheSize) {
		this.executor = executor;
		this.maxCpuTime = maxCpuTime;
		this.maxMemorySize = maxMemorySize;
		this.maxCacheSize = maxCacheSize;
	}

	@Override
	public NashornSandbox create() {
		NashornSandbox sandbox = NashornSandboxes.create("--language=es6");
		// 沙箱初始化
		return sandbox;
	}

	@Override
	public boolean readyToTake(NashornSandbox obj) {
		return true;
	}

	@Override
	public boolean readyToRestore(NashornSandbox obj) {
		return true;
	}

	@Override
	public void destroy(NashornSandbox obj) {
		// do nothing
	}
PoolService<NashornSandbox> pool = new ConcurrentPool<>(new ConcurrentLinkedQueueCollection<>(), factory, coreSize, maxSize, false);
NashornSandbox sandbox = pool.tryTake(timeout, TimeUnit.MILLISECONDS);
sandbox.eval(js);

虽然Java 15已经去掉了nashorn,但可以在pom.xml中单独引入

        <dependency>
            <groupId>org.openjdk.nashorn</groupId>
            <artifactId>nashorn-core</artifactId>
            <version>15.3</version>
        </dependency>
文章来自个人专栏
Java
14 文章 | 1 订阅
0条评论
0 / 1000
请输入你的评论
0
0