提交 5d40c97d 编写于 作者: J Jason Park

Compile and run java code in docker

上级 396ef5f3
FROM baekjoon/onlinejudge-java:1.8
\ No newline at end of file
......@@ -3,6 +3,7 @@
"version": "2.0.0",
"description": "Algorithm Visualizer",
"scripts": {
"postinstall": "docker pull baekjoon/onlinejudge-java:1.8",
"dev": "NODE_ENV=development node bin/www",
"start": "NODE_ENV=production node bin/www",
"build": "npm run build:frontend && npm run build:backend",
......
import Promise from 'bluebird';
import axios from 'axios';
import fs from 'fs-extra';
import fs from 'fs';
import { githubClientId, githubClientSecret } from '/environment';
axios.interceptors.request.use(request => {
const instance = axios.create();
instance.interceptors.request.use(request => {
request.params = { client_id: githubClientId, client_secret: githubClientSecret, ...request.params };
return request;
});
axios.interceptors.response.use(response => {
instance.interceptors.response.use(response => {
return response.data;
}, error => {
return Promise.reject(error.response.data);
......@@ -28,43 +30,43 @@ const request = (url, process) => {
const GET = URL => {
return request(URL, (mappedURL, args) => {
const [params] = args;
return axios.get(mappedURL, { params });
return instance.get(mappedURL, { params });
});
};
const DELETE = URL => {
return request(URL, (mappedURL, args) => {
const [params] = args;
return axios.delete(mappedURL, { params });
return instance.delete(mappedURL, { params });
});
};
const POST = URL => {
return request(URL, (mappedURL, args) => {
const [body, params] = args;
return axios.post(mappedURL, body, { params });
return instance.post(mappedURL, body, { params });
});
};
const PUT = URL => {
return request(URL, (mappedURL, args) => {
const [body, params] = args;
return axios.put(mappedURL, body, { params });
return instance.put(mappedURL, body, { params });
});
};
const PATCH = URL => {
return request(URL, (mappedURL, args) => {
const [body, params] = args;
return axios.patch(mappedURL, body, { params });
return instance.patch(mappedURL, body, { params });
});
};
const GitHubApi = {
listCommits: GET('/repos/:owner/:repo/commits'),
getAccessToken: code => axios.post('https://github.com/login/oauth/access_token', { code }, { headers: { Accept: 'application/json' } }),
getAccessToken: code => instance.post('https://github.com/login/oauth/access_token', { code }, { headers: { Accept: 'application/json' } }),
getLatestRelease: GET('/repos/:owner/:repo/releases/latest'),
download: (url, path) => axios({
download: (url, path) => instance({
method: 'get',
url,
responseType: 'stream',
......
class ApplicationError extends Error {
toJSON() {
return { message: this.message };
}
}
class NotFoundError extends ApplicationError {
......@@ -10,9 +13,17 @@ class PermissionError extends ApplicationError {
class AuthorizationError extends ApplicationError {
}
class CompileError extends ApplicationError {
}
class RuntimeError extends ApplicationError {
}
export {
ApplicationError,
NotFoundError,
PermissionError,
AuthorizationError,
CompileError,
RuntimeError,
};
\ No newline at end of file
import express from 'express';
import fs from 'fs-extra';
import fs from 'fs';
import path from 'path';
import { NotFoundError } from '/common/error';
import { exec } from 'child_process';
......
export { default as auth } from './auth';
export { default as categories } from './categories';
export { default as compilers } from './compilers';
export { default as tracers } from './tracers';
......@@ -3,42 +3,54 @@ import express from 'express';
import fs from 'fs-extra';
import uuid from 'uuid';
import path from 'path';
import child_process from 'child_process';
import { GitHubApi } from '/apis';
import { __PROD__ } from '/environment';
import { CompileError, RuntimeError } from '/common/error';
const router = express.Router();
const getLibPath = (...args) => path.resolve(__dirname, '..', 'public', 'libs', ...args);
const createTempDir = () => {
const dirPath = path.resolve(__dirname, '..', 'public', 'codes', uuid.v4());
fs.mkdirSync(dirPath);
return dirPath;
};
const downloadLibs = () => {
GitHubApi.getLatestRelease('algorithm-visualizer', 'tracers').then(release => {
return Promise.each(release.assets, asset => GitHubApi.download(asset.browser_download_url, getLibPath(asset.name)));
});
};
downloadLibs(); // TODO: download again when webhooked
if (__PROD__) downloadLibs(); // TODO: download again when webhooked
const getJsWorker = (req, res, next) => {
res.sendFile(getLibPath('js.js'));
};
const compileJava = (req, res, next) => {
const dirPath = createTempDir();
fs.writeFileSync(dirPath, req.body);
/* TODO:
1. Write into a source file
2. Execute in Docker
3. Read the output file
*/
const execute = (imageId, srcPath, command, ErrorClass) => new Promise((resolve, reject) => {
const libPath = getLibPath();
const dockerCommand = `docker run --rm -w=/usr/judge -t -v=${libPath}:/usr/bin/tracers:ro -v=${srcPath}:/usr/judge:rw -e MAX_TRACES=1000000 -e MAX_TRACERS=100 ${imageId}`;
child_process.exec(`${dockerCommand} ${command}`, (error, stdout, stderr) => {
if (error) return reject(new ErrorClass(stdout));
resolve();
});
});
const trace = ({ imageId, compileCommand, runCommand }) => (req, res, next) => {
const { code } = req.body;
const srcPath = path.resolve(__dirname, '..', 'public', 'codes', uuid.v4());
fs.outputFile(path.resolve(srcPath, 'code.java'), code)
.then(() => execute(imageId, srcPath, compileCommand, CompileError))
.then(() => execute(imageId, srcPath, runCommand, RuntimeError))
.then(() => res.sendFile(path.resolve(srcPath, 'traces.json')))
.catch(next)
.finally(() => fs.remove(srcPath));
};
router.route('/js')
.get(getJsWorker);
router.route('/java')
.post(compileJava);
.post(trace({
imageId: 'baekjoon/onlinejudge-java:1.8',
compileCommand: 'javac -cp /usr/bin/tracers/java.jar code.java',
runCommand: 'java -cp /usr/bin/tracers/java.jar:. Main',
}));
export default router;
\ No newline at end of file
......@@ -69,18 +69,19 @@ const GitHubApi = {
};
let jsWorker = null;
const CompilerApi = {
js: code => new Promise((resolve, reject) => {
const TracerApi = {
js: ({ code }) => new Promise((resolve, reject) => {
if (jsWorker) jsWorker.terminate();
jsWorker = new Worker('/api/compilers/js');
jsWorker = new Worker('/api/tracers/js');
jsWorker.onmessage = e => resolve(e.data);
jsWorker.onerror = reject;
jsWorker.postMessage(code);
}),
java: POST('/tracers/java'),
};
export {
CategoryApi,
GitHubApi,
CompilerApi,
TracerApi,
};
\ No newline at end of file
......@@ -13,6 +13,7 @@
padding: 16px;
margin: 8px;
font-size: $font-size-large;
white-space: pre-line;
&.success {
border-color: rgb(0, 150, 0);
......
......@@ -3,7 +3,7 @@ import Promise from 'bluebird';
import { extension } from '/common/util';
import { Array1DData, Array2DData, ChartData, Data, GraphData, LogData } from '/core/datas';
import { Array1DRenderer, Array2DRenderer, ChartRenderer, GraphRenderer, LogRenderer, Renderer } from '/core/renderers';
import { CompilerApi } from '/apis';
import { TracerApi } from '/apis';
class TracerManager {
constructor() {
......@@ -63,7 +63,7 @@ class TracerManager {
setFile(file) {
this.file = file;
this.runInitial();
if (extension(file.name) === 'js') this.runInitial();
}
reset(traces = []) {
......@@ -155,8 +155,12 @@ class TracerManager {
execute() {
const { name, content } = this.file;
const ext = extension(name);
if (ext in CompilerApi) {
return CompilerApi[ext](content).then(traces => this.reset(traces));
if (ext in TracerApi) {
return TracerApi[ext]({ code: content })
.then(traces => this.reset(traces))
.catch(e => {
throw e.err;
});
} else {
return Promise.reject(new Error('Language Not Supported'));
}
......
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册