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