Java 및 Docker를 사용하여 온라인 코드 컴파일러를 개발하는 방법
37154 단어 serverlessprogrammingdockerjava
시작하기 전에 다음 링크https://github.com/zakariamaaraki/RemoteCodeCompiler에서 프로젝트의 소스 코드를 찾을 수 있습니다.
왜 자바와 도커인가?
Java의 경우 개인적인 선택입니다. 저는 이 언어로 작업하는 것을 정말 좋아하지만 선호하는 다른 프로그래밍 언어를 선택할 수 있습니다.
다른 소스 코드에서 실행 환경을 분리하고(악성 코드가 시스템에 영향을 주지 않도록) 리소스를 제한하려면 가상 시스템 또는 컨테이너를 사용해야 하는데 이 두 가지 선택의 차이점은 무엇입니까?
가상 머신 대 컨테이너
VM(가상 머신)은 컴퓨터 시스템의 에뮬레이션입니다. 간단히 말해서 실제로는 한 대의 컴퓨터인 하드웨어에서 여러 대의 개별 컴퓨터처럼 보이는 것을 실행할 수 있습니다.
컨테이너를 사용하면 가상 머신(VM)과 같은 기본 컴퓨터를 가상화하는 대신 OS만 가상화됩니다.
컨테이너는 물리적 서버와 해당 호스트 OS(일반적으로 Linux 또는 Windows) 위에 위치합니다. 각 컨테이너는 호스트 OS 커널과 일반적으로 바이너리 및 라이브러리도 공유합니다. 공유 구성 요소는 읽기 전용입니다.
이제 컨테이너를 사용하는 것이 Vms보다 가볍고 도커가 가장 많이 사용되는 컨테이너화 솔루션이므로 이 프로젝트에서 도커를 선택한 이유가 분명합니다.
API
먼저 API를 설계해야 합니다. 4가지 프로그래밍 언어(Java, C, C++ 및 Python)용 온라인 컴파일러를 제공하고 싶다고 가정해 보겠습니다.
따라서 API는 다음과 같아야 합니다.
4개의 컨트롤러는 Java용, C용, C++용, Python용입니다.
이러한 컨트롤러에 대한 호출은 다음 URL에 대한 POST 요청을 통해 수행됩니다.
입력으로 5개의 필드가 필요합니다.
output : 예상 출력.
sourceCode : java, c, c++ 또는 python의 소스 코드입니다.
timeLimit : 실행 중에 소스 코드가 초과하지 않아야 하는 시간 제한(초)입니다(0~15초 사이여야 함).
memoryLimit : 실행 중에 소스 코드가 초과하지 않아야 하는 Mb 단위의 메모리 제한(0에서 1000Mb 사이여야 함).
inputFile : 별도의 줄에 작성된 입력(선택 사항).
// Python Compiler
@RequestMapping(
value = "python",
method = RequestMethod.POST
)
public ResponseEntity<Object> compile_python(@RequestPart(value = "output", required = true) MultipartFile output,
@RequestPart(value = "sourceCode", required = true) MultipartFile sourceCode,
@RequestParam(value = "inputFile", required = false) MultipartFile inputFile,
@RequestParam(value = "timeLimit", required = true) int timeLimit,
@RequestParam(value = "memoryLimit", required = true) int memoryLimit
) throws Exception {
return compiler(output, sourceCode, inputFile, timeLimit, memoryLimit, Langage.Python);
}
// C Compiler
@RequestMapping(
value = "c",
method = RequestMethod.POST
)
public ResponseEntity<Object> compile_c(@RequestPart(value = "output", required = true) MultipartFile output,
@RequestPart(value = "sourceCode", required = true) MultipartFile sourceCode,
@RequestParam(value = "inputFile", required = false) MultipartFile inputFile,
@RequestParam(value = "timeLimit", required = true) int timeLimit,
@RequestParam(value = "memoryLimit", required = true) int memoryLimit
) throws Exception {
return compiler(output, sourceCode, inputFile, timeLimit, memoryLimit, Langage.C);
}
// C++ Compiler
@RequestMapping(
value = "cpp",
method = RequestMethod.POST
)
public ResponseEntity<Object> compile_cpp(@RequestPart(value = "output", required = true) MultipartFile output,
@RequestPart(value = "sourceCode", required = true) MultipartFile sourceCode,
@RequestParam(value = "inputFile", required = false) MultipartFile inputFile,
@RequestParam(value = "timeLimit", required = true) int timeLimit,
@RequestParam(value = "memoryLimit", required = true) int memoryLimit
) throws Exception {
return compiler(output, sourceCode, inputFile, timeLimit, memoryLimit, Langage.Cpp);
}
// Java Compiler
@RequestMapping(
value = "java",
method = RequestMethod.POST
)
public ResponseEntity<Object> compile_java(@RequestPart(value = "output", required = true) MultipartFile output,
@RequestPart(value = "sourceCode", required = true) MultipartFile sourceCode,
@RequestParam(value = "inputFile", required = false) MultipartFile inputFile,
@RequestParam(value = "timeLimit", required = true) int timeLimit,
@RequestParam(value = "memoryLimit", required = true) int memoryLimit
) throws Exception {
return compiler(output, sourceCode, inputFile, timeLimit, memoryLimit, Langage.Java);
}
사용자는 어떤 유형의 응답을 기대해야 합니까?
자, 이 점에 대해 잠시 살펴보겠습니다. Codeforces, Leetcode 등과 같은 플랫폼에서 경쟁 프로그래밍을 수행하는 경우 6가지 유형의 평결(허용됨, 오답, 컴파일 오류, 런타임 오류, 메모리 부족 오류 및 시간 제한 초과)이 있음을 알 수 있습니다.
도커 컨테이너 내에서 소스 코드 컴파일
여기서 아이디어는 사용자가 제공한 소스 코드를 가져와 언어에 따라 도커 이미지를 만든 다음 이 이미지의 컨테이너를 실행하여 소스 코드를 컴파일하고 실행하는 것입니다. 컨테이너의 반환 코드에 따라 이전에 논의한 판정을 결정할 수 있지만 컨테이너가 무한 실행을 피하기 위한 시간 제한이나 메모리 제한(악성 코드를 방지하기 위한)을 초과하지 않도록 해야 합니다.
// Compile method
private ResponseEntity<Object> compiler(
MultipartFile output,
MultipartFile sourceCode,
MultipartFile inputFile,
int timeLimit,
int memoryLimit,
Langage langage
) throws Exception {
String folder = "utility";
String file = "main";
if(langage == Langage.C) {
folder += "_c";
file += ".c";
} else if(langage == Langage.Java) {
file += ".java";
} else if(langage == Langage.Cpp) {
folder += "_cpp";
file += ".cpp";
} else {
folder += "_py";
file += ".py";
}
if(memoryLimit < 0 || memoryLimit > 1000)
return ResponseEntity
.badRequest()
.body("Error memoryLimit must be between 0Mb and 1000Mb");
if(timeLimit < 0 || timeLimit > 15)
return ResponseEntity
.badRequest()
.body("Error timeLimit must be between 0 Sec and 15 Sec");
LocalDateTime date = LocalDateTime.now();
createEntrypointFile(sourceCode, inputFile, timeLimit, memoryLimit, langage);
logger.info("entrypoint.sh file has been created");
saveUploadedFiles(sourceCode, folder + "/" + file);
saveUploadedFiles(output, folder + "/" + output.getOriginalFilename());
if(inputFile != null)
saveUploadedFiles(inputFile, folder + "/" + inputFile.getOriginalFilename());
logger.info("Files have been uploaded");
String imageName = "compile" + new Date().getTime();
logger.info("Building the docker image");
String[] dockerCommand = new String[] {"docker", "image", "build", folder, "-t", imageName};
ProcessBuilder probuilder = new ProcessBuilder(dockerCommand);
Process process = probuilder.start();
int status = process.waitFor();
if(status == 0)
logger.info("Docker image has been built");
else
logger.info("Error while building image");
logger.info("Running the container");
dockerCommand = new String[] {"docker", "run", "--rm", imageName};
probuilder = new ProcessBuilder(dockerCommand);
process = probuilder.start();
status = process.waitFor();
logger.info("End of the execution of the container");
BufferedReader outputReader = new BufferedReader(new InputStreamReader(output.getInputStream()));
StringBuilder outputBuilder = new StringBuilder();
BufferedReader reader = new BufferedReader(new InputStreamReader(process.getInputStream()));
StringBuilder builder = new StringBuilder();
boolean ans = runCode(outputReader, outputBuilder, reader, builder);
String result = builder.toString();
// delete files
deleteFile(folder, file);
new File(folder + "/" + output.getOriginalFilename()).delete();
if(inputFile != null)
new File(folder + "/" + inputFile.getOriginalFilename()).delete();
logger.info("files have been deleted");
String statusResponse = statusResponse(status, ans);
logger.info("Status response is " + statusResponse);
return ResponseEntity
.status(HttpStatus.OK)
.body(new Response(builder.toString(), outputBuilder.toString(), statusResponse, ans, date));
}
컨테이너의 진입점
도커 이미지의 진입점을 구성하기 위해 실행 중에 시간 제한, 메모리 제한 및 실행 명령을 포함하는 진입점 파일을 생성합니다.
다음은 Java 실행을 위해 entrypoint.sh 파일을 생성하는 방법의 예입니다.
// create Java entrypoint.sh file
private void createJavaEntrypointFile(String fileName, int timeLimit, int memoryLimit, MultipartFile inputFile) {
String executionCommand = inputFile == null
? "timeout --signal=SIGTERM " + timeLimit + " java " + fileName.substring(0,fileName.length() - 5) + "\n"
: "timeout --signal=SIGTERM " + timeLimit + " java " + fileName.substring(0,fileName.length() - 5) + " < " + inputFile.getOriginalFilename() + "\n";
String content = "#!/usr/bin/env bash\n" +
"mv main.java " + fileName+ "\n" +
"javac " + fileName + "\n" +
"ret=$?\n" +
"if [ $ret -ne 0 ]\n" +
"then\n" +
" exit 2\n" +
"fi\n" +
"ulimit -s " + memoryLimit + "\n" +
executionCommand +
"exit $?\n";
OutputStream os = null;
try {
os = new FileOutputStream(new File("utility/entrypoint.sh"));
os.write(content.getBytes(), 0, content.length());
} catch (IOException e) {
e.printStackTrace();
}finally{
try {
os.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
톡 싸요 코드 보여주세요 😃
이것은 단지 개요일 뿐이며 이 모든 것을 코딩하는 방법을 설명하는 대신 내 Github 저장소https://github.com/zakariamaaraki/RemoteCodeCompiler의 링크를 방문하는 것이 좋습니다.
질문이 있으시면 언제든지 질문해 주세요!
Reference
이 문제에 관하여(Java 및 Docker를 사용하여 온라인 코드 컴파일러를 개발하는 방법), 우리는 이곳에서 더 많은 자료를 발견하고 링크를 클릭하여 보았다 https://dev.to/zakariamaaraki/how-to-develop-an-online-code-compiler-using-java-and-docker-1gpi텍스트를 자유롭게 공유하거나 복사할 수 있습니다.하지만 이 문서의 URL은 참조 URL로 남겨 두십시오.
우수한 개발자 콘텐츠 발견에 전념 (Collection and Share based on the CC Protocol.)