java实现代码沙盒(docker-java)

代码沙盒是一种提供安全、隔离环境来运行代码的工具,本篇实现采用docker-java。

具体思路就是基于特定的镜像将代码包成一个容器,其次就是用docker运行这个容器,运行期间可以获取代码运行的时间、消耗的内存、使用cpu的情况、运行的结果等等信息。

首先引入依赖,要想使用docker-java,必须选择一个transport,httpclient就是其中之一。

这里看全部的transport------docker-java/docs/transports.md at main · docker-java/docker-java

xml 复制代码
<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

java 复制代码
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构建镜像

java 复制代码
BuildImageCmd imageCmd = dockerClient.buildImageCmd(new File("Dockerfile"))
        .withTags(Set.of("my-test-run"));
BuildImageResultCallback buildImageResultCallback = new BuildImageResultCallback();

imageCmd.exec(buildImageResultCallback);
String imageId = buildImageResultCallback.awaitImageId();

基于镜像创建容器

java 复制代码
CreateContainerResponse response = dockerClient.createContainerCmd(imageId).withHostConfig(
        HostConfig.newHostConfig()
                .withMemory(1024L * 1024L * 6)// 最大6M
                .withAutoRemove(true)
).exec();
String containerId = response.getId();

启动容器,并且检查各种状态

java 复制代码
long start = System.currentTimeMillis();// 记录开始时间
dockerClient.startContainerCmd(containerId).exec();
dockerClient.statsCmd(containerId).withNoStream(false).exec(new ResultCallback.Adapter<Statistics>() {
    @Override
    public 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>() {
            @Override
            public void onStart(Closeable closeable) {
                System.out.println("开始");
            }

            @Override
            public void onNext(Frame object) {
                in.append(new String(object.getPayload()));
                if (System.currentTimeMillis() - start >= 1000) {
                    // 超时1s超时,结束容器
                    throw new RuntimeException("运行超时");
                }
            }

            @Override
            public void onError(Throwable throwable) {
                System.err.println(throwable.getMessage());
            }

            @Override
            public 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("内容不一致");
                }
            }

            @Override
            public void close() throws IOException {
                System.out.println("关闭");
            }
        });
Thread.sleep(10000);

示例👇

这是一个Dockerfile,简单的a+b程序,输入input.in,会打印在控制台输出。

dockerfile 复制代码
# 选择基础镜像,这里使用官方的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为输入,跑程序,输出到控制台

python 复制代码
import sys

try:
    with open(f"./static/{sys.argv[1]}", "r") as file:
        sys.stdin = file
        for _ in range(int(input())):
            a, b = map(int, input().split())
            print(a + b)

except FileNotFoundError:
    print(f"文件未找到")

通过上面写的监听,将输入和输出进行比较,可以知道运行结果是否和预期一致。

超时会直接结束程序,后续输出不会捕获,也会删除掉容器。

xml 复制代码
开始
完毕,删除容器
耗时:356ms
内容一致
相关推荐
WeiXin_DZbishe4 小时前
基于springboot宠物领养系统-计算机毕设 附源码【课程设计】94217
vue.js·spring boot·python·django·php·课程设计
vx_bisheyuange4 小时前
基于SpringBoot的体育商品推荐系统
spring boot·毕业设计
别动我齐刘海5 小时前
C++ 工程化学习
java·linux·前端·c++·学习·机器学习·机器人
Wx-bishekaifayuan5 小时前
express皖美特色农产品网售系统53118-计算机课程设计、毕业设计
人工智能·spring boot·后端·python·课程设计·express·旅游
Wx-bishekaifayuan6 小时前
springboot汉中旅游服务平台41442-计算机课程设计、毕业设计
spring boot·后端·python·django·课程设计·express·旅游
code2cat6 小时前
Java进阶篇之AtomicLongFieldUpdater:把累计额度留在对象字段中
java·cas·并发编程
WeiXin_DZbishe6 小时前
基于springboot城市公交实时运营与调度系统-计算机毕设 附源码【课程设计】66173
java·vue.js·spring boot·后端·python·php·课程设计
泡茶喝茶写代码6 小时前
A股量化数据工程:从 REST 接口到策略信号(第 18 篇):放量缩量异动扫描
java·python·股票数据api·股票数据api接口·股票api数据接口·股票量化数据api·股票量化数据接口