up
This commit is contained in:
parent
e62dfd86d4
commit
0e1f7eaaa9
11
online_compiler/Dockerfile.api
Normal file
11
online_compiler/Dockerfile.api
Normal file
@ -0,0 +1,11 @@
|
|||||||
|
FROM rust:latest
|
||||||
|
|
||||||
|
WORKDIR /app
|
||||||
|
|
||||||
|
COPY api .
|
||||||
|
|
||||||
|
RUN cargo build --release
|
||||||
|
|
||||||
|
EXPOSE 8000
|
||||||
|
|
||||||
|
CMD ["cargo", "run", "--release"]
|
11
online_compiler/Dockerfile.web
Normal file
11
online_compiler/Dockerfile.web
Normal file
@ -0,0 +1,11 @@
|
|||||||
|
FROM rust:latest
|
||||||
|
|
||||||
|
WORKDIR /app
|
||||||
|
|
||||||
|
COPY web .
|
||||||
|
|
||||||
|
RUN cargo build --release
|
||||||
|
|
||||||
|
EXPOSE 80
|
||||||
|
|
||||||
|
CMD ["cargo", "run", "--release"]
|
122
online_compiler/REDME.md
Normal file
122
online_compiler/REDME.md
Normal file
@ -0,0 +1,122 @@
|
|||||||
|
README.md
|
||||||
|
|
||||||
|
markdown
|
||||||
|
深色版本
|
||||||
|
|
||||||
|
# 在线编译器项目
|
||||||
|
|
||||||
|
这是一个简单的在线编译器项目,用户可以通过Web界面输入代码并选择编程语言,然后执行代码并查看结果。该项目由两部分组成:
|
||||||
|
|
||||||
|
- **API服务**:负责接收代码执行请求,使用Docker容器来执行代码,并返回结果。
|
||||||
|
- **Web服务**:提供一个用户界面,允许用户输入代码并查看执行结果。
|
||||||
|
|
||||||
|
## 项目结构
|
||||||
|
|
||||||
|
online-compiler
|
||||||
|
├── api
|
||||||
|
│ ├── Cargo.toml
|
||||||
|
│ ├── src
|
||||||
|
│ │ └── main.rs
|
||||||
|
├── web
|
||||||
|
│ ├── Cargo.toml
|
||||||
|
│ ├── src
|
||||||
|
│ │ └── main.rs
|
||||||
|
│ ├── static
|
||||||
|
│ │ ├── styles.css
|
||||||
|
│ │ ├── scripts.js
|
||||||
|
│ │ └── index.html
|
||||||
|
├── Dockerfile.api
|
||||||
|
├── Dockerfile.web
|
||||||
|
└── docker-compose.yml
|
||||||
|
|
||||||
|
深色版本
|
||||||
|
|
||||||
|
|
||||||
|
## 快速开始
|
||||||
|
|
||||||
|
1. 确保你已经安装了[Docker](https://docs.docker.com/get-docker/)和[Docker Compose](https://docs.docker.com/compose/install/)。
|
||||||
|
2. 克隆此仓库到你的本地机器。
|
||||||
|
3. 进入项目根目录。
|
||||||
|
4. 使用以下命令启动项目:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
docker-compose up --build
|
||||||
|
|
||||||
|
打开浏览器访问 http://localhost 来使用在线编译器。
|
||||||
|
|
||||||
|
API文档
|
||||||
|
执行代码
|
||||||
|
|
||||||
|
URL: /api/v1/execute
|
||||||
|
|
||||||
|
方法: POST
|
||||||
|
|
||||||
|
请求体 (JSON):
|
||||||
|
|
||||||
|
json
|
||||||
|
深色版本
|
||||||
|
|
||||||
|
{
|
||||||
|
"code": "print('Hello, world!')",
|
||||||
|
"language": "python"
|
||||||
|
}
|
||||||
|
|
||||||
|
响应 (JSON):
|
||||||
|
|
||||||
|
json
|
||||||
|
深色版本
|
||||||
|
|
||||||
|
{
|
||||||
|
"id": "b7c5f6a7-8d9a-4e00-9f5b-5a7c9f6b5d8e",
|
||||||
|
"status": "queued",
|
||||||
|
"response": null
|
||||||
|
}
|
||||||
|
|
||||||
|
id: 执行任务的唯一标识符。
|
||||||
|
status: 当前状态("queued", "compiling", "running", "completed")。
|
||||||
|
response: 如果代码执行完成,则包含输出结果;否则为null。
|
||||||
|
|
||||||
|
查询执行状态
|
||||||
|
|
||||||
|
URL: /api/v1/status/{id}
|
||||||
|
|
||||||
|
方法: GET
|
||||||
|
|
||||||
|
路径参数:
|
||||||
|
id: 执行任务的唯一标识符。
|
||||||
|
|
||||||
|
响应 (JSON):
|
||||||
|
|
||||||
|
json
|
||||||
|
深色版本
|
||||||
|
|
||||||
|
{
|
||||||
|
"id": "b7c5f6a7-8d9a-4e00-9f5b-5a7c9f6b5d8e",
|
||||||
|
"status": "completed",
|
||||||
|
"response": {
|
||||||
|
"stdout": "Hello, world!\n",
|
||||||
|
"stderr": "",
|
||||||
|
"returncode": 0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
id: 执行任务的唯一标识符。
|
||||||
|
status: 当前状态("queued", "compiling", "running", "completed")。
|
||||||
|
response: 如果代码执行完成,则包含输出结果;否则为null。
|
||||||
|
|
||||||
|
示例
|
||||||
|
执行代码示例
|
||||||
|
|
||||||
|
bash
|
||||||
|
深色版本
|
||||||
|
|
||||||
|
curl -X POST http://localhost:8000/api/v1/execute \
|
||||||
|
-H "Content-Type: application/json" \
|
||||||
|
-d '{"code":"print(\\\"Hello, world!\\\")","language":"python"}'
|
||||||
|
|
||||||
|
查询状态示例
|
||||||
|
|
||||||
|
bash
|
||||||
|
深色版本
|
||||||
|
|
||||||
|
curl -X GET http://localhost:8000/api/v1/status/b7c5f6a7-8d9a-4e00-9f5b-5a7c9f6b5d8e
|
1
online_compiler/api/.gitignore
vendored
Normal file
1
online_compiler/api/.gitignore
vendored
Normal file
@ -0,0 +1 @@
|
|||||||
|
/target
|
7
online_compiler/api/Cargo.lock
generated
Normal file
7
online_compiler/api/Cargo.lock
generated
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
# This file is automatically @generated by Cargo.
|
||||||
|
# It is not intended for manual editing.
|
||||||
|
version = 3
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "api"
|
||||||
|
version = "0.1.0"
|
21
online_compiler/api/Cargo.toml
Normal file
21
online_compiler/api/Cargo.toml
Normal file
@ -0,0 +1,21 @@
|
|||||||
|
[package]
|
||||||
|
name = "online_compiler_api"
|
||||||
|
version = "0.1.0"
|
||||||
|
edition = "2021"
|
||||||
|
|
||||||
|
[dependencies]
|
||||||
|
|
||||||
|
|
||||||
|
hyper-tls = "0.6" # 确保这是最新的稳定版本
|
||||||
|
native-tls = "0.2" # 明确指定 0.2 版本
|
||||||
|
|
||||||
|
rocket = { version = "0.5", features = ["json"] }
|
||||||
|
serde = { version = "1.0", features = ["derive"] }
|
||||||
|
serde_json = "1.0"
|
||||||
|
uuid = "0.8"
|
||||||
|
tempfile = "3.1"
|
||||||
|
docker = "0.0.40"
|
||||||
|
tokio = { version = "1.0", features = ["full"] }
|
||||||
|
lazy_static = "1.4"
|
||||||
|
reqwest = { version = "0.12", features = ["json"] }
|
||||||
|
|
283
online_compiler/api/src/main.rs
Normal file
283
online_compiler/api/src/main.rs
Normal file
@ -0,0 +1,283 @@
|
|||||||
|
#[macro_use] extern crate rocket;
|
||||||
|
extern crate serde;
|
||||||
|
#[macro_use] extern crate serde_derive;
|
||||||
|
extern crate tokio;
|
||||||
|
extern crate uuid;
|
||||||
|
extern crate tempfile;
|
||||||
|
extern crate docker;
|
||||||
|
extern crate lazy_static;
|
||||||
|
extern crate reqwest;
|
||||||
|
|
||||||
|
use rocket::response::content::Json;
|
||||||
|
use rocket::serde::{json::Json, Serialize, Deserialize};
|
||||||
|
use std::process::Command;
|
||||||
|
use std::fs;
|
||||||
|
use std::io::Write;
|
||||||
|
use tempfile::NamedTempFile;
|
||||||
|
use uuid::Uuid;
|
||||||
|
use docker::Docker;
|
||||||
|
use docker::opts::{HostConfig, ResourceConstraints};
|
||||||
|
use std::time::{Duration, Instant};
|
||||||
|
use tokio::time::timeout;
|
||||||
|
use std::collections::HashMap;
|
||||||
|
use std::sync::{Arc, Mutex};
|
||||||
|
|
||||||
|
#[derive(Serialize, Deserialize, Debug)]
|
||||||
|
#[serde(crate = "rocket::serde")]
|
||||||
|
struct CodeRequest {
|
||||||
|
code: String,
|
||||||
|
language: String,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Serialize, Deserialize, Debug, Clone)]
|
||||||
|
#[serde(crate = "rocket::serde")]
|
||||||
|
struct CodeResponse {
|
||||||
|
stdout: String,
|
||||||
|
stderr: String,
|
||||||
|
returncode: i32,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Serialize, Deserialize, Debug, Clone)]
|
||||||
|
#[serde(crate = "rocket::serde")]
|
||||||
|
struct ExecutionStatus {
|
||||||
|
id: Uuid,
|
||||||
|
status: String,
|
||||||
|
response: Option<CodeResponse>,
|
||||||
|
}
|
||||||
|
|
||||||
|
// 定义一个结构体来存储容器信息
|
||||||
|
struct ContainerInfo {
|
||||||
|
container: docker::Container,
|
||||||
|
last_used: Instant,
|
||||||
|
}
|
||||||
|
|
||||||
|
// 定义一个全局的容器缓存
|
||||||
|
lazy_static! {
|
||||||
|
static ref CONTAINER_CACHE: Arc<Mutex<HashMap<String, ContainerInfo>>> = Arc::new(Mutex::new(HashMap::new()));
|
||||||
|
static ref CONTAINER_LIMIT: Arc<Mutex<usize>> = Arc::new(Mutex::new(0));
|
||||||
|
static ref EXECUTION_STATUS: Arc<Mutex<HashMap<Uuid, ExecutionStatus>>> = Arc::new(Mutex::new(HashMap::new()));
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn execute_code(code: &str, language: &str, execution_id: Uuid) -> Result<CodeResponse, String> {
|
||||||
|
// 获取 Docker 客户端
|
||||||
|
let docker = Docker::connect_with_defaults().unwrap();
|
||||||
|
|
||||||
|
// 根据语言选择 Docker 镜像
|
||||||
|
let image_name = match language {
|
||||||
|
"python" => "python-runner",
|
||||||
|
"rust" => "rust-runner",
|
||||||
|
"nim" => "nim-runner",
|
||||||
|
"lua" => "lua-runner",
|
||||||
|
_ => return Err("Unsupported language".to_string()),
|
||||||
|
};
|
||||||
|
|
||||||
|
// 创建临时文件
|
||||||
|
let mut temp_file = NamedTempFile::new().unwrap();
|
||||||
|
write!(temp_file, "{}", code).unwrap();
|
||||||
|
let temp_path = temp_file.path().to_str().unwrap();
|
||||||
|
|
||||||
|
// 检查缓存中是否有可用的容器
|
||||||
|
let mut cache = CONTAINER_CACHE.lock().unwrap();
|
||||||
|
if let Some(container_info) = cache.get_mut(image_name) {
|
||||||
|
// 如果容器最近被使用过,则复用它
|
||||||
|
if container_info.last_used.elapsed() < Duration::from_secs(3600) { // 1小时
|
||||||
|
container_info.last_used = Instant::now();
|
||||||
|
let (status, output) = timeout(Duration::from_secs(600), async { // 10分钟超时
|
||||||
|
let status = container_info.container.wait(None).await.map_err(|e| e.to_string())?;
|
||||||
|
let logs = container_info.container.logs(true, true, None, None, None, None).await.map_err(|e| e.to_string())?;
|
||||||
|
(status, String::from_utf8(logs).unwrap())
|
||||||
|
}).await.map_or_else(
|
||||||
|
|_| {
|
||||||
|
Err("Execution timed out".to_string())
|
||||||
|
},
|
||||||
|
|result| result
|
||||||
|
)?;
|
||||||
|
|
||||||
|
// 解析输出
|
||||||
|
let (stdout, stderr) = if status.success {
|
||||||
|
(output, String::new())
|
||||||
|
} else {
|
||||||
|
(String::new(), output)
|
||||||
|
};
|
||||||
|
|
||||||
|
return Ok(CodeResponse {
|
||||||
|
stdout,
|
||||||
|
stderr,
|
||||||
|
returncode: status.exit_code.unwrap_or(-1) as i32,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 创建新的容器
|
||||||
|
let host_config = HostConfig {
|
||||||
|
resources: Some(ResourceConstraints {
|
||||||
|
memory: Some(128 * 1024 * 1024), // 128MB 内存限制
|
||||||
|
cpu_shares: Some(1024), // CPU 资源限制
|
||||||
|
..Default::default()
|
||||||
|
}),
|
||||||
|
network_mode: Some("none".to_string()), // 禁用网络
|
||||||
|
..Default::default()
|
||||||
|
};
|
||||||
|
|
||||||
|
let container = docker.create_container(
|
||||||
|
Some(image_name),
|
||||||
|
vec!["/bin/sh", "-c", &format!("cat {} | {} -", temp_path, language)],
|
||||||
|
None,
|
||||||
|
Some(host_config),
|
||||||
|
None,
|
||||||
|
None,
|
||||||
|
None,
|
||||||
|
None,
|
||||||
|
None,
|
||||||
|
None,
|
||||||
|
None,
|
||||||
|
None,
|
||||||
|
).await.map_err(|e| {
|
||||||
|
error!("Failed to create container: {}", e);
|
||||||
|
"Failed to create container".to_string()
|
||||||
|
})?;
|
||||||
|
|
||||||
|
// 启动容器
|
||||||
|
container.start(None).await.map_err(|e| {
|
||||||
|
error!("Failed to start container: {}", e);
|
||||||
|
"Failed to start container".to_string()
|
||||||
|
})?;
|
||||||
|
|
||||||
|
// 设置超时时间为10分钟
|
||||||
|
let timeout_duration = Duration::from_secs(600);
|
||||||
|
|
||||||
|
let (status, output) = timeout(timeout_duration, async {
|
||||||
|
let status = container.wait(None).await.map_err(|e| e.to_string())?;
|
||||||
|
let logs = container.logs(true, true, None, None, None, None).await.map_err(|e| e.to_string())?;
|
||||||
|
(status, String::from_utf8(logs).unwrap())
|
||||||
|
}).await.map_or_else(
|
||||||
|
|_| {
|
||||||
|
Err("Execution timed out".to_string())
|
||||||
|
},
|
||||||
|
|result| result
|
||||||
|
)?;
|
||||||
|
|
||||||
|
// 将新的容器添加到缓存中
|
||||||
|
cache.insert(image_name.to_string(), ContainerInfo {
|
||||||
|
container: container.clone(),
|
||||||
|
last_used: Instant::now(),
|
||||||
|
});
|
||||||
|
|
||||||
|
// 增加创建容器的计数
|
||||||
|
let mut limit = CONTAINER_LIMIT.lock().unwrap();
|
||||||
|
*limit += 1;
|
||||||
|
|
||||||
|
// 检查是否超过每小时的最大创建数量
|
||||||
|
if *limit > 3 { // 每小时最多创建3个新容器
|
||||||
|
// 清理旧的容器
|
||||||
|
for (key, value) in cache.iter_mut() {
|
||||||
|
if value.last_used.elapsed() > Duration::from_secs(3600) {
|
||||||
|
value.container.remove(None).await.ok();
|
||||||
|
cache.remove(key);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
*limit = 0; // 重置计数
|
||||||
|
}
|
||||||
|
|
||||||
|
// 解析输出
|
||||||
|
let (stdout, stderr) = if status.success {
|
||||||
|
(output, String::new())
|
||||||
|
} else {
|
||||||
|
(String::new(), output)
|
||||||
|
};
|
||||||
|
|
||||||
|
Ok(CodeResponse {
|
||||||
|
stdout,
|
||||||
|
stderr,
|
||||||
|
returncode: status.exit_code.unwrap_or(-1) as i32,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
#[post("/api/v1/execute", format = "json", data = "<request>")]
|
||||||
|
async fn handle_execute_code(request: Json<CodeRequest>) -> Result<Json<ExecutionStatus>, rocket::response::status::Custom<String>> {
|
||||||
|
let code_request = request.into_inner();
|
||||||
|
let code = &code_request.code;
|
||||||
|
let language = &code_request.language;
|
||||||
|
let execution_id = Uuid::new_v4();
|
||||||
|
|
||||||
|
// 初始化执行状态
|
||||||
|
let mut status_map = EXECUTION_STATUS.lock().unwrap();
|
||||||
|
status_map.insert(execution_id, ExecutionStatus {
|
||||||
|
id: execution_id,
|
||||||
|
status: "queued".to_string(),
|
||||||
|
response: None,
|
||||||
|
});
|
||||||
|
|
||||||
|
// 异步执行代码
|
||||||
|
tokio::spawn(async move {
|
||||||
|
// 更新状态为“正在编译”
|
||||||
|
let mut status_map = EXECUTION_STATUS.lock().unwrap();
|
||||||
|
if let Some(status) = status_map.get_mut(&execution_id) {
|
||||||
|
status.status = "compiling".to_string();
|
||||||
|
}
|
||||||
|
|
||||||
|
let response = match execute_code(code, language, execution_id).await {
|
||||||
|
Ok(response) => Some(response),
|
||||||
|
Err(e) => {
|
||||||
|
error!("Execution failed: {}", e);
|
||||||
|
None
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// 更新执行状态
|
||||||
|
let mut status_map = EXECUTION_STATUS.lock().unwrap();
|
||||||
|
if let Some(status) = status_map.get_mut(&execution_id) {
|
||||||
|
status.status = "completed".to_string();
|
||||||
|
status.response = response;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// 返回初始状态
|
||||||
|
Ok(Json(ExecutionStatus {
|
||||||
|
id: execution_id,
|
||||||
|
status: "queued".to_string(),
|
||||||
|
response: None,
|
||||||
|
}))
|
||||||
|
}
|
||||||
|
|
||||||
|
#[get("/api/v1/status/<id>")]
|
||||||
|
async fn get_execution_status(id: Uuid) -> Json<ExecutionStatus> {
|
||||||
|
let status_map = EXECUTION_STATUS.lock().unwrap();
|
||||||
|
if let Some(status) = status_map.get(&id) {
|
||||||
|
Json(status.clone())
|
||||||
|
} else {
|
||||||
|
Json(ExecutionStatus {
|
||||||
|
id,
|
||||||
|
status: "not found".to_string(),
|
||||||
|
response: None,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[get("/api/v1/result/<id>")]
|
||||||
|
async fn get_execution_result(id: Uuid) -> Json<CodeResponse> {
|
||||||
|
let status_map = EXECUTION_STATUS.lock().unwrap();
|
||||||
|
if let Some(status) = status_map.get(&id) {
|
||||||
|
if let Some(response) = &status.response {
|
||||||
|
Json(response.clone())
|
||||||
|
} else {
|
||||||
|
Json(CodeResponse {
|
||||||
|
stdout: "".to_string(),
|
||||||
|
stderr: "Result not available".to_string(),
|
||||||
|
returncode: -1,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
Json(CodeResponse {
|
||||||
|
stdout: "".to_string(),
|
||||||
|
stderr: "Execution ID not found".to_string(),
|
||||||
|
returncode: -1,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[launch]
|
||||||
|
fn rocket() -> _ {
|
||||||
|
rocket::build()
|
||||||
|
.mount("/", routes![handle_execute_code, get_execution_status, get_execution_result])
|
||||||
|
}
|
24
online_compiler/docker-compose.yml
Normal file
24
online_compiler/docker-compose.yml
Normal file
@ -0,0 +1,24 @@
|
|||||||
|
version: '3'
|
||||||
|
services:
|
||||||
|
api:
|
||||||
|
build:
|
||||||
|
context: .
|
||||||
|
dockerfile: Dockerfile.api
|
||||||
|
ports:
|
||||||
|
- "8000:8000"
|
||||||
|
networks:
|
||||||
|
- app-network
|
||||||
|
|
||||||
|
web:
|
||||||
|
build:
|
||||||
|
context: .
|
||||||
|
dockerfile: Dockerfile.web
|
||||||
|
ports:
|
||||||
|
- "8001:8001"
|
||||||
|
networks:
|
||||||
|
- app-network
|
||||||
|
depends_on:
|
||||||
|
- api
|
||||||
|
|
||||||
|
networks:
|
||||||
|
app-network:
|
1
online_compiler/web/.gitignore
vendored
Normal file
1
online_compiler/web/.gitignore
vendored
Normal file
@ -0,0 +1 @@
|
|||||||
|
/target
|
12
online_compiler/web/Cargo.toml
Normal file
12
online_compiler/web/Cargo.toml
Normal file
@ -0,0 +1,12 @@
|
|||||||
|
[package]
|
||||||
|
name = "online_compiler_web"
|
||||||
|
version = "0.1.0"
|
||||||
|
edition = "2021"
|
||||||
|
|
||||||
|
[dependencies]
|
||||||
|
rocket = { version = "0.5", features = ["json"] }
|
||||||
|
rocket_contrib = { version = "0.4.10", features = ["serve"] }
|
||||||
|
rocket_cors = "0.5"
|
||||||
|
serde = { version = "1.0", features = ["derive"] }
|
||||||
|
serde_json = "1.0"
|
||||||
|
reqwest = { version = "0.12", features = ["json"] }
|
145
online_compiler/web/src/main.rs
Normal file
145
online_compiler/web/src/main.rs
Normal file
@ -0,0 +1,145 @@
|
|||||||
|
#[macro_use] extern crate rocket;
|
||||||
|
#[macro_use] extern crate rocket_contrib;
|
||||||
|
|
||||||
|
use rocket::response::content::Html;
|
||||||
|
use rocket_contrib::serve::StaticFiles;
|
||||||
|
use rocket_cors::{AllowedOrigins, CorsOptions};
|
||||||
|
use rocket::State;
|
||||||
|
use rocket::request::Form;
|
||||||
|
use rocket::http::Status;
|
||||||
|
use serde_json::{json, Value};
|
||||||
|
use reqwest::Client;
|
||||||
|
use std::sync::Mutex;
|
||||||
|
|
||||||
|
// 用于存储API客户端
|
||||||
|
struct ApiClient {
|
||||||
|
client: Client,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl ApiClient {
|
||||||
|
fn new() -> Self {
|
||||||
|
ApiClient {
|
||||||
|
client: Client::new(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn execute_code(&self, code: &str, language: &str) -> Result<Value, String> {
|
||||||
|
let url = "http://api:8000/api/v1/execute"; // 使用Docker网络中的服务名
|
||||||
|
let body = json!({
|
||||||
|
"code": code,
|
||||||
|
"language": language
|
||||||
|
});
|
||||||
|
|
||||||
|
let response = self.client.post(url)
|
||||||
|
.json(&body)
|
||||||
|
.send()
|
||||||
|
.await
|
||||||
|
.map_err(|e| e.to_string())?;
|
||||||
|
|
||||||
|
if response.status().is_success() {
|
||||||
|
response.json::<Value>().await.map_err(|e| e.to_string())
|
||||||
|
} else {
|
||||||
|
Err("API request failed".to_string())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn get_status(&self, id: &str) -> Result<Value, String> {
|
||||||
|
let url = format!("http://api:8000/api/v1/status/{}", id);
|
||||||
|
let response = self.client.get(&url)
|
||||||
|
.send()
|
||||||
|
.await
|
||||||
|
.map_err(|e| e.to_string())?;
|
||||||
|
|
||||||
|
if response.status().is_success() {
|
||||||
|
response.json::<Value>().await.map_err(|e| e.to_string())
|
||||||
|
} else {
|
||||||
|
Err("API request failed".to_string())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[get("/")]
|
||||||
|
fn index() -> Html<&'static str> {
|
||||||
|
Html(r#"
|
||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="zh-CN">
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8">
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||||
|
<title>在线编译器</title>
|
||||||
|
<link rel="stylesheet" href="/static/styles.css">
|
||||||
|
<script src="https://code.jquery.com/jquery-3.6.0.min.js"></script>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<div class="container">
|
||||||
|
<h1>在线编译器</h1>
|
||||||
|
<div class="editor-container">
|
||||||
|
<textarea id="code" placeholder="在这里输入你的代码" required></textarea>
|
||||||
|
<select id="language" required>
|
||||||
|
<option value="python">Python</option>
|
||||||
|
<option value="rust">Rust</option>
|
||||||
|
<option value="nim">Nim</option>
|
||||||
|
<option value="lua">Lua</option>
|
||||||
|
</select>
|
||||||
|
<button type="button" id="run-button">运行代码</button>
|
||||||
|
</div>
|
||||||
|
<div class="output-container">
|
||||||
|
<div id="status"></div>
|
||||||
|
<pre id="output"></pre>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<script src="/static/scripts.js"></script>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
|
"#)
|
||||||
|
}
|
||||||
|
|
||||||
|
#[post("/run", data = "<form>")]
|
||||||
|
async fn run_code(form: Form<CodeForm>, api_client: State<Mutex<ApiClient>>) -> Result<rocket::response::Json<Value>, Status> {
|
||||||
|
let form_data = form.into_inner();
|
||||||
|
let code = &form_data.code;
|
||||||
|
let language = &form_data.language;
|
||||||
|
|
||||||
|
let api_client = api_client.lock().unwrap();
|
||||||
|
match api_client.execute_code(code, language).await {
|
||||||
|
Ok(response) => Ok(rocket::response::Json(response)),
|
||||||
|
Err(e) => {
|
||||||
|
error!("API call failed: {}", e);
|
||||||
|
Err(Status::InternalServerError)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[get("/api/v1/status/<id>")]
|
||||||
|
async fn get_execution_status(id: String, api_client: State<Mutex<ApiClient>>) -> Result<rocket::response::Json<Value>, Status> {
|
||||||
|
let api_client = api_client.lock().unwrap();
|
||||||
|
match api_client.get_status(&id).await {
|
||||||
|
Ok(status) => Ok(rocket::response::Json(status)),
|
||||||
|
Err(e) => {
|
||||||
|
error!("API call failed: {}", e);
|
||||||
|
Err(Status::InternalServerError)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(FromForm)]
|
||||||
|
struct CodeForm {
|
||||||
|
code: String,
|
||||||
|
language: String,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[launch]
|
||||||
|
fn rocket() -> _ {
|
||||||
|
let cors = CorsOptions::default()
|
||||||
|
.allowed_origins(AllowedOrigins::all())
|
||||||
|
.to_cors().unwrap();
|
||||||
|
|
||||||
|
rocket::build()
|
||||||
|
.mount("/", routes![index])
|
||||||
|
.mount("/static", StaticFiles::from("static/"))
|
||||||
|
.manage(Mutex::new(ApiClient::new()))
|
||||||
|
.mount("/run", routes![run_code])
|
||||||
|
.mount("/api/v1/status", routes![get_execution_status])
|
||||||
|
.attach(cors)
|
||||||
|
.port(8001) // 在这里指定监听 8001 端口
|
||||||
|
}
|
33
online_compiler/web/static/index.html
Normal file
33
online_compiler/web/static/index.html
Normal file
@ -0,0 +1,33 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="zh-CN">
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8">
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||||
|
<title>在线编译器</title>
|
||||||
|
<link rel="stylesheet" href="styles.css">
|
||||||
|
<script src="https://code.jquery.com/jquery-3.6.0.min.js"></script>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<div class="container">
|
||||||
|
<h1>在线编译器</h1>
|
||||||
|
<div class="editor-container">
|
||||||
|
<div class="language-selector">
|
||||||
|
<label for="language">选择语言:</label>
|
||||||
|
<select id="language" required>
|
||||||
|
<option value="python">Python</option>
|
||||||
|
<option value="rust">Rust</option>
|
||||||
|
<option value="nim">Nim</option>
|
||||||
|
<option value="lua">Lua</option>
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
<textarea id="code" placeholder="在这里输入你的代码" required></textarea>
|
||||||
|
<button type="button" id="run-button">运行代码</button>
|
||||||
|
</div>
|
||||||
|
<div class="output-container">
|
||||||
|
<div id="status"></div>
|
||||||
|
<pre id="output"></pre>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<script src="scripts.js"></script>
|
||||||
|
</body>
|
||||||
|
</html>
|
53
online_compiler/web/static/scripts.js
Normal file
53
online_compiler/web/static/scripts.js
Normal file
@ -0,0 +1,53 @@
|
|||||||
|
$(document).ready(function() {
|
||||||
|
$('#run-button').on('click', function() {
|
||||||
|
const code = $('#code').val();
|
||||||
|
const language = $('#language').val();
|
||||||
|
|
||||||
|
// 初始化状态
|
||||||
|
$('#status').text('排队中...');
|
||||||
|
$('#output').text('');
|
||||||
|
|
||||||
|
$.ajax({
|
||||||
|
url: '/run',
|
||||||
|
method: 'POST',
|
||||||
|
contentType: 'application/x-www-form-urlencoded; charset=UTF-8',
|
||||||
|
data: { code, language },
|
||||||
|
success: function(response) {
|
||||||
|
const executionId = response.id;
|
||||||
|
pollForStatus(executionId);
|
||||||
|
},
|
||||||
|
error: function(error) {
|
||||||
|
console.error('Error:', error);
|
||||||
|
$('#status').text('请求失败');
|
||||||
|
$('#output').text('无法连接到服务器,请稍后再试。');
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
function pollForStatus(executionId) {
|
||||||
|
const interval = setInterval(() => {
|
||||||
|
$.ajax({
|
||||||
|
url: `/api/v1/status/${executionId}`,
|
||||||
|
method: 'GET',
|
||||||
|
success: function(response) {
|
||||||
|
$('#status').text(`状态: ${response.status}`);
|
||||||
|
if (response.status === 'completed') {
|
||||||
|
clearInterval(interval);
|
||||||
|
if (response.response) {
|
||||||
|
$('#output').text(`输出:\n${response.response.stdout}\n\n错误:\n${response.response.stderr}`);
|
||||||
|
} else {
|
||||||
|
$('#output').text('结果不可用。');
|
||||||
|
}
|
||||||
|
} else if (response.status === 'compiling' || response.status === 'running') {
|
||||||
|
$('#status').text(`状态: ${response.status}`);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
error: function(error) {
|
||||||
|
console.error('Error:', error);
|
||||||
|
$('#status').text('请求失败');
|
||||||
|
$('#output').text('无法连接到服务器,请稍后再试。');
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}, 1000); // 每秒轮询一次
|
||||||
|
}
|
||||||
|
});
|
55
online_compiler/web/static/styles.css
Normal file
55
online_compiler/web/static/styles.css
Normal file
@ -0,0 +1,55 @@
|
|||||||
|
body {
|
||||||
|
font-family: Arial, sans-serif;
|
||||||
|
background-color: #f4f4f4;
|
||||||
|
margin: 0;
|
||||||
|
padding: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.container {
|
||||||
|
max-width: 800px;
|
||||||
|
margin: 50px auto;
|
||||||
|
padding: 20px;
|
||||||
|
background-color: #fff;
|
||||||
|
box-shadow: 0 0 10px rgba(0, 0, 0, 0.1);
|
||||||
|
border-radius: 5px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.editor-container, .output-container {
|
||||||
|
margin-bottom: 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.language-selector {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
margin-bottom: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.language-selector label {
|
||||||
|
margin-right: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
textarea {
|
||||||
|
width: 100%;
|
||||||
|
height: 200px;
|
||||||
|
padding: 10px;
|
||||||
|
font-size: 14px;
|
||||||
|
border: 1px solid #ddd;
|
||||||
|
border-radius: 5px;
|
||||||
|
}
|
||||||
|
|
||||||
|
select, button {
|
||||||
|
padding: 10px;
|
||||||
|
font-size: 14px;
|
||||||
|
border: 1px solid #ddd;
|
||||||
|
border-radius: 5px;
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
|
||||||
|
.output-container pre {
|
||||||
|
white-space: pre-wrap;
|
||||||
|
word-wrap: break-word;
|
||||||
|
background-color: #f9f9f9;
|
||||||
|
padding: 10px;
|
||||||
|
border: 1px solid #ddd;
|
||||||
|
border-radius: 5px;
|
||||||
|
}
|
145
online_runer/REDME. md
Normal file
145
online_runer/REDME. md
Normal file
@ -0,0 +1,145 @@
|
|||||||
|
|
||||||
|
## 安装与运行
|
||||||
|
|
||||||
|
### 前提条件
|
||||||
|
|
||||||
|
- 安装 [Rust](https://www.rust-lang.org/tools/install)
|
||||||
|
- 安装 [Docker](https://docs.docker.com/get-docker/)
|
||||||
|
- 安装 [Docker Compose](https://docs.docker.com/compose/install/)
|
||||||
|
|
||||||
|
### 构建Docker镜像
|
||||||
|
|
||||||
|
你说得对,为了使在线编译器API能够正常工作,我们需要确保依赖的Docker镜像环境已经构建好并推送到Docker Hub。这些镜像将用于执行不同语言的代码。
|
||||||
|
1. 构建和推送Docker镜像
|
||||||
|
|
||||||
|
首先,我们需要为每种编程语言创建一个Docker镜像,并将其推送到Docker Hub。假设你已经有了Docker Hub账号,以下是每种语言的Dockerfile示例:
|
||||||
|
Python Runner Dockerfile (python-runner/Dockerfile)
|
||||||
|
|
||||||
|
dockerfile
|
||||||
|
深色版本
|
||||||
|
|
||||||
|
FROM python:3.9-slim
|
||||||
|
|
||||||
|
# 设置工作目录
|
||||||
|
WORKDIR /code
|
||||||
|
|
||||||
|
# 安装必要的包
|
||||||
|
RUN pip install --no-cache-dir -q flake8
|
||||||
|
|
||||||
|
# 默认命令
|
||||||
|
CMD ["python", "-"]
|
||||||
|
|
||||||
|
Rust Runner Dockerfile (rust-runner/Dockerfile)
|
||||||
|
|
||||||
|
dockerfile
|
||||||
|
深色版本
|
||||||
|
|
||||||
|
FROM rust:latest
|
||||||
|
|
||||||
|
# 设置工作目录
|
||||||
|
WORKDIR /code
|
||||||
|
|
||||||
|
# 默认命令
|
||||||
|
CMD ["rustc", "-", "-o", "/code/a.out" && "./a.out"]
|
||||||
|
|
||||||
|
Nim Runner Dockerfile (nim-runner/Dockerfile)
|
||||||
|
|
||||||
|
dockerfile
|
||||||
|
深色版本
|
||||||
|
|
||||||
|
FROM nimlang/nim:latest
|
||||||
|
|
||||||
|
# 设置工作目录
|
||||||
|
WORKDIR /code
|
||||||
|
|
||||||
|
# 默认命令
|
||||||
|
CMD ["nim", "compile", "-", "&&", "./a.out"]
|
||||||
|
|
||||||
|
Lua Runner Dockerfile (lua-runner/Dockerfile)
|
||||||
|
|
||||||
|
dockerfile
|
||||||
|
深色版本
|
||||||
|
|
||||||
|
FROM lua:5.4
|
||||||
|
|
||||||
|
# 设置工作目录
|
||||||
|
WORKDIR /code
|
||||||
|
|
||||||
|
# 默认命令
|
||||||
|
CMD ["lua", "-"]
|
||||||
|
|
||||||
|
2. 构建并推送Docker镜像
|
||||||
|
|
||||||
|
对于每个Dockerfile,你需要在相应的目录中运行以下命令来构建和推送镜像到Docker Hub。请确保你已经登录到Docker Hub。
|
||||||
|
构建并推送Python Runner镜像
|
||||||
|
|
||||||
|
sh
|
||||||
|
深色版本
|
||||||
|
|
||||||
|
cd python-runner
|
||||||
|
docker build -t your_dockerhub_username/python-runner .
|
||||||
|
docker push your_dockerhub_username/python-runner
|
||||||
|
|
||||||
|
构建并推送Rust Runner镜像
|
||||||
|
|
||||||
|
sh
|
||||||
|
深色版本
|
||||||
|
|
||||||
|
cd rust-runner
|
||||||
|
docker build -t your_dockerhub_username/rust-runner .
|
||||||
|
docker push your_dockerhub_username/rust-runner
|
||||||
|
|
||||||
|
构建并推送Nim Runner镜像
|
||||||
|
|
||||||
|
sh
|
||||||
|
深色版本
|
||||||
|
|
||||||
|
cd nim-runner
|
||||||
|
docker build -t your_dockerhub_username/nim-runner .
|
||||||
|
docker push your_dockerhub_username/nim-runner
|
||||||
|
|
||||||
|
构建并推送Lua Runner镜像
|
||||||
|
|
||||||
|
sh
|
||||||
|
深色版本
|
||||||
|
|
||||||
|
cd lua-runner
|
||||||
|
docker build -t your_dockerhub_username/lua-runner .
|
||||||
|
docker push your_dockerhub_username/lua-runner
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
1. 构建并推送每种语言的执行环境到Docker Hub:
|
||||||
|
- Python Runner:
|
||||||
|
```sh
|
||||||
|
cd python-runner
|
||||||
|
docker build -t your_dockerhub_username/python-runner .
|
||||||
|
docker push your_dockerhub_username/python-runner
|
||||||
|
```
|
||||||
|
- Rust Runner:
|
||||||
|
```sh
|
||||||
|
cd rust-runner
|
||||||
|
docker build -t your_dockerhub_username/rust-runner .
|
||||||
|
docker push your_dockerhub_username/rust-runner
|
||||||
|
```
|
||||||
|
- Nim Runner:
|
||||||
|
```sh
|
||||||
|
cd nim-runner
|
||||||
|
docker build -t your_dockerhub_username/nim-runner .
|
||||||
|
docker push your_dockerhub_username/nim-runner
|
||||||
|
```
|
||||||
|
- Lua Runner:
|
||||||
|
```sh
|
||||||
|
cd lua-runner
|
||||||
|
docker build -t your_dockerhub_username/lua-runner .
|
||||||
|
docker push your_dockerhub_username/lua-runner
|
||||||
|
```
|
||||||
|
|
||||||
|
2. 构建主项目Docker镜像:
|
||||||
|
```sh
|
||||||
|
docker-compose build
|
7
online_runer/lua-runner/Dockerfile
Normal file
7
online_runer/lua-runner/Dockerfile
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
FROM lua:5.4
|
||||||
|
|
||||||
|
# 设置工作目录
|
||||||
|
WORKDIR /code
|
||||||
|
|
||||||
|
# 默认命令
|
||||||
|
CMD ["lua", "-"]
|
7
online_runer/nim-runner/Dockerfile
Normal file
7
online_runer/nim-runner/Dockerfile
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
FROM nimlang/nim:latest
|
||||||
|
|
||||||
|
# 设置工作目录
|
||||||
|
WORKDIR /code
|
||||||
|
|
||||||
|
# 默认命令
|
||||||
|
CMD ["nim", "compile", "-", "&&", "./a.out"]
|
10
online_runer/python-runner/Dockerfile
Normal file
10
online_runer/python-runner/Dockerfile
Normal file
@ -0,0 +1,10 @@
|
|||||||
|
FROM python:3.9-slim
|
||||||
|
|
||||||
|
# 设置工作目录
|
||||||
|
WORKDIR /code
|
||||||
|
|
||||||
|
# 安装必要的包
|
||||||
|
RUN pip install --no-cache-dir -q flake8
|
||||||
|
|
||||||
|
# 默认命令
|
||||||
|
CMD ["python", "-"]
|
7
online_runer/rust-runner/Dockerfile
Normal file
7
online_runer/rust-runner/Dockerfile
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
FROM rust:latest
|
||||||
|
|
||||||
|
# 设置工作目录
|
||||||
|
WORKDIR /code
|
||||||
|
|
||||||
|
# 默认命令
|
||||||
|
CMD ["rustc", "-", "-o", "/code/a.out" && "./a.out"]
|
Loading…
Reference in New Issue
Block a user