代码沙盒是一种提供安全、隔离环境来运行代码的工具,本篇实现采用docker-java。
具体思路就是基于特定的镜像将代码包成一个容器,其次就是用docker运行这个容器,运行期间可以获取代码运行的时间、消耗的内存、使用cpu的情况、运行的结果等等信息。
首先引入依赖,要想使用docker-java,必须选择一个transport
,httpclient
就是其中之一。
这里看全部的transport——docker-java/docs/transports.md at main · docker-java/docker-java
<dependency><groupId>com.github.docker-java</groupId><artifactId>docker-java</artifactId><version>3.3.0</version>
</dependency><dependency><groupId>com.github.docker-java</groupId><artifactId>docker-java-transport-httpclient5</artifactId><version>3.3.0</version>
</dependency><dependency><groupId>org.apache.httpcomponents.client5</groupId><artifactId>httpclient5</artifactId>
</dependency>
配置DockerClient
DockerClientConfig config = DefaultDockerClientConfig.createDefaultConfigBuilder().withDockerHost("tcp://127.0.0.1:2375").withDockerTlsVerify(false).withRegistryUsername("").withRegistryPassword("").withRegistryEmail("").withRegistryUrl("https://index.docker.io/v1/").build();
DockerHttpClient httpClient = new ApacheDockerHttpClient.Builder().dockerHost(config.getDockerHost()).sslConfig(config.getSSLConfig()).maxConnections(100).connectionTimeout(Duration.ofSeconds(30)).responseTimeout(Duration.ofSeconds(45)).build();DockerClient dockerClient = DockerClientImpl.getInstance(config, httpClient);
基于Dockerfile构建镜像
BuildImageCmd imageCmd = dockerClient.buildImageCmd(new File("Dockerfile")).withTags(Set.of("my-test-run"));
BuildImageResultCallback buildImageResultCallback = new BuildImageResultCallback();imageCmd.exec(buildImageResultCallback);
String imageId = buildImageResultCallback.awaitImageId();
基于镜像创建容器
CreateContainerResponse response = dockerClient.createContainerCmd(imageId).withHostConfig(HostConfig.newHostConfig().withMemory(1024L * 1024L * 6)// 最大6M.withAutoRemove(true)
).exec();
String containerId = response.getId();
启动容器,并且检查各种状态
long start = System.currentTimeMillis();// 记录开始时间
dockerClient.startContainerCmd(containerId).exec();
dockerClient.statsCmd(containerId).withNoStream(false).exec(new ResultCallback.Adapter<Statistics>() {@Overridepublic void onNext(Statistics statistics) {System.out.println("cpu:" + statistics.getCpuStats().getCpuUsage());System.out.println("内存消耗:" + statistics.getMemoryStats().getUsage() / 1024 / 1024 + "MB");}
});
StringBuilder in = new StringBuilder();
dockerClient.logContainerCmd(containerId).withStdOut(true).withStdErr(true).withFollowStream(true).exec(new ResultCallback<Frame>() {@Overridepublic void onStart(Closeable closeable) {System.out.println("开始");}@Overridepublic void onNext(Frame object) {in.append(new String(object.getPayload()));if (System.currentTimeMillis() - start >= 1000) {// 超时1s超时,结束容器throw new RuntimeException("运行超时");}}@Overridepublic void onError(Throwable throwable) {System.err.println(throwable.getMessage());}@Overridepublic void onComplete() {System.out.println("完毕,删除容器");System.out.println("耗时:" + (System.currentTimeMillis() - start) + "ms");String out = null;try {out = FileUtils.readFileToString(new File("input.out"), StandardCharsets.UTF_8);} catch (IOException e) {throw new RuntimeException(e);}if (!out.endsWith("\n")) {out += "\n";}out = out.replaceAll("\r", "");if (StringUtils.equals(in, out)) {System.out.println("内容一致");} else {System.out.println("内容不一致");}}@Overridepublic void close() throws IOException {System.out.println("关闭");}});
Thread.sleep(10000);
示例👇
这是一个Dockerfile,简单的a+b程序,输入input.in,会打印在控制台输出。
# 选择基础镜像,这里使用官方的Python镜像,以Python 3.9为例
FROM python:3.9# 设置工作目录,后续的操作都将在该目录下进行
WORKDIR /app# 将当前目录下的所有文件(包括Python脚本和可能的依赖配置文件等)复制到容器内的工作目录
COPY . /app# 设置容器启动时要执行的命令,这里是执行你的Python脚本main.py
CMD ["python", "main.py", "input.in"]
目录
-----resources
----------static
---------------input.in
---------------input.out
main.py 以input.in为输入,跑程序,输出到控制台
import systry:with open(f"./static/{sys.argv[1]}", "r") as file:sys.stdin = filefor _ in range(int(input())):a, b = map(int, input().split())print(a + b)except FileNotFoundError:print(f"文件未找到")
通过上面写的监听,将输入和输出进行比较,可以知道运行结果是否和预期一致。
超时会直接结束程序,后续输出不会捕获,也会删除掉容器。
开始
完毕,删除容器
耗时:356ms
内容一致