iT邦幫忙

2025 iThome 鐵人賽

DAY 12
0

前言

歡迎來到第十二天!過去幾天,我們一直專注在 RAG 和向量資料庫上,教會了 AI 如何「理解」使用者在說什麼。我們的 AI 面試官現在有了不錯的「文科」素養,能針對概念題進行有深度的語意分析。

但是,一個優秀的前端面試官,光有文科素養是不夠的,還必須具備紮實的「理科」能力——也就是判斷程式碼對錯的能力。如果一段程式碼連最基本的 console.log('Hello World') 都印不出來,那即便它在語意上看起來寫得再怎麼「漂亮」,也是不及格的。

我們需要為 AI 補上這塊短板:在它進行主觀的 Code Review 之前,先提供給它客觀的執行結果

然而,直接在我們的伺服器上執行使用者提交的程式碼,是極度危險的行為。想像一下,如果有人提交了惡意程式碼,比如 fs.unlinkSync('./.env.local'),那我們的金鑰和整個專案就都毀了。這絕對不行!

因此,我們需要一個專業、安全、隔離的「程式碼沙箱」服務。今天,我們就來認識並整合這樣一個強大的工具:Judge0

今日目標

  • 理解什麼是 Judge0,以及為什麼它是執行不受信任程式碼的安全選擇。
  • 透過 RapidAPI 平台,取得免費的 Judge0 API 金鑰。
  • 建立一個安全的 Next.js 後端 API Route,作為 Judge0 的代理,徹底保護我們的 API 金鑰不外洩。
  • 測試代理 API,確保我們的後端能與 Judge0 成功通訊。

Step 1: 什麼是 Judge0?為什麼我們需要它?

Judge0 是一個開源的、強大的「程式碼執行即服務 (Code Execution as a Service)」平台。你可以把它想像成一個專門用來執行程式碼的遠端實驗室。

它的核心工作流程非常簡單:

  1. 你把一段程式碼(例如 JavaScript)、標準輸入 (stdin) 等資訊,透過 API 傳送給它。
  2. Judge0 會在一個完全隔離、安全的沙箱環境 (通常是 Docker 容器) 中執行這段程式碼。
  3. 執行完畢後,它會回傳一個詳細的「實驗報告」,包含:
    • 標準輸出 (stdout)
    • 標準錯誤 (stderr)
    • 執行時間 (Time)
    • 消耗記憶體 (Memory)
    • 編譯輸出 (Compile Output)
    • 執行結果狀態 (例如:Accepted, Wrong Answer, Time Limit Exceeded)

為什麼不自己做,而要用 Judge0?

痛點 Judge0 如何解決 前端類比
安全性 使用者程式碼在與你伺服器完全隔離的沙箱中執行,無法存取你的檔案、環境變數或網路。 就像在 <iframe> 中使用 sandbox 屬性,限制了子頁面的權限,但 Judge0 的隔離更徹底。
複雜性 你不需要自己管理 Docker、設定資源限制 (CPU/Memory)、處理各種語言的執行環境。 就像使用 Next.js 或 Vite,你不需要從零開始設定 Webpack 和 Babel,工具已經幫你處理好了。
資源消耗 執行程式碼是個消耗資源的任務,外包給 Judge0 可以讓你的伺服器專注於處理 API 請求。 就像將圖片、影片等靜態資源放在 CDN 上,減輕主伺服器的負擔。
多語言支援 開箱即用支援數十種程式語言,未來擴充題庫到 Python 或其他語言也沒問題。 像是 PrettierESLint,透過插件就能支援不同的程式碼風格與語言。

總之,使用 Judge0 讓我們能專注在應用程式的邏輯上,而把最棘手、最危險的程式碼執行任務,交給最專業的工具來處理。

Step 2: 透過 RapidAPI 取得你的 Judge0 金鑰

Judge0 官方推薦透過 RapidAPI 這個平台來使用他們的服務,因為它提供了一個非常慷慨的免費方案,對我們的專案來說綽綽有餘。

  1. 前往 RapidAPI 上的 Judge0 頁面
    請打開 Judge0 CE on RapidAPI

  2. 註冊並訂閱免費方案
    用你的 GitHub 或 Google 帳號註冊/登入。接著,點擊「Pricing」分頁,找到「BASIC」方案(通常是免費的),點擊「Subscribe」訂閱它。

  3. 找到你的 API 金鑰
    回到「Endpoints」分頁。在頁面的右側,你會看到程式碼範例區塊。在這裡,你可以找到兩個我們需要的關鍵資訊:

    • X-RapidAPI-Key: 這是你的個人金鑰,像密碼一樣重要。
    • X-RapidAPI-Host: judge0-ce.p.rapidapi.com,這是服務的主機位置。
圖1
圖1 : 在 RapidAPI 找到你的金鑰與主機
  1. 儲存到環境變數
    和 Gemini 的金鑰一樣,我們必須把這些敏感資訊儲存在 .env.local 檔案中,從後端讀取而不直接在前端顯示出來,再次強調,包在前端的資訊永遠不安全的,別再傻傻的以為環境可以在前端藏住了。

    打開 .env.local,加入以下兩行:

    # .env.local
    
    # ... 其他金鑰 ...
    JUDGE0_API_HOST=judge0-ce.p.rapidapi.com
    JUDGE0_API_KEY=貼上你剛剛複製的X-RapidAPI-Key
    

Step 3: 建立安全的後端代理 API

現在我們有了金鑰,但千萬記得我們的安全第一原則金鑰永不下發到瀏覽器

我們必須建立一個自己的後端 API Route 來當作「中間人」或「代理」。前端只會跟我們自己的 API 溝通,再由我們的後端,帶著金鑰去跟真正的 Judge0 API 溝通。

這樣做的好處是:

  1. 安全JUDGE0_API_KEY 永遠只存在於我們的伺服器端,前端完全接觸不到。
  2. 控制:未來我們可以在這個代理層加入快取、速率限制或日誌記錄等額外邏輯。
  3. 彈性:如果有一天我們想換掉 Judge0,改用另一個服務,我們只需要修改這個代理 API 的邏輯,前端的程式碼完全不需要變動。

讓我們來建立第一個測試用的代理,功能是獲取 Judge0 支援的所有程式語言。

app/api/ 資料夾下,建立新資料夾 judge0,並在其中新增 languages/route.ts

// app/api/judge0/languages/route.ts
import { NextResponse } from 'next/server';

const JUDGE0_API_HOST = process.env.JUDGE0_API_HOST;
const JUDGE0_API_KEY = process.env.JUDGE0_API_KEY;

export async function GET() {
  // 再次檢查環境變數是否存在,確保伺服器配置正確
  if (!JUDGE0_API_HOST || !JUDGE0_API_KEY) {
    return NextResponse.json(
      { error: 'Judge0 API 環境變數未設定' },
      { status: 500 }
    );
  }

  const url = `https://${JUDGE0_API_HOST}/languages`;
  const options = {
    method: 'GET',
    headers: {
      'X-RapidAPI-Key': JUDGE0_API_KEY,
      'X-RapidAPI-Host': JUDGE0_API_HOST,
    },
  };

  try {
    const response = await fetch(url, options);
    
    // 如果 Judge0 回傳非 200 的狀態碼,我們將錯誤資訊透傳給前端
    if (!response.ok) {
      const errorData = await response.text(); // 使用 .text() 以防回傳的不是 JSON
      return NextResponse.json(
        { error: '無法從 Judge0 獲取資料', details: errorData },
        { status: response.status }
      );
    }
    
    const data = await response.json();
    // 成功取得資料後,將其回傳給前端
    return NextResponse.json(data);

  } catch (error) {
    console.error('代理請求至 Judge0 時發生網路或解析錯誤:', error);
    return NextResponse.json(
      { error: '代理伺服器內部錯誤' },
      { status: 500 }
    );
  }
}

程式碼摘要說明:

  • 我們只在 route.ts 這個伺服器端檔案中讀取 process.env,金鑰完全被封裝在後端。
  • 我們使用標準的 fetch API,並在 headers 中附上 RapidAPI 需要的 X-RapidAPI-KeyX-RapidAPI-Host
  • 加入基本的錯誤處理,檢查環境變數以及 Judge0 回傳得狀態。

Step 4: 測試我們的代理 API

萬事俱備,只欠測試!打開我們的本地專案:

npm run dev

打開瀏覽器,訪問我們剛剛建立的代理 API 端點:http://localhost:3000/api/judge0/languages

如果一切順利,你應該會看到一個 JSON 陣列,裡面列出了 Judge0 支援的所有語言,類似這樣:

[
  {
    "id": 63,
    "name": "JavaScript (Node.js 12.14.0)"
  },
  {
    "id": 71,
    "name": "Python (3.8.1)"
  },
  {
    "id": 74,
    "name": "TypeScript (3.7.4)"
  },
  // ... and many more
]

這就證明了我們的後端代理已經成功運作!我們的 Next.js 應用程式現在具備了與 Judge0 安全通訊的能力。

補充說明:常見坑、降級策略與成本提示
成本提示:RapidAPI 的免費方案是每天 200 次提交 (Submission),速率限制為每分鐘 5 次。對於我們的開發和測試來說非常足夠。GET /languages 這種查詢型 API 通常不計入提交次數,但還是要注意不要頻繁呼叫。

降級策略 (Degradation Strategy):

我們的代理 API 已經做了基本的錯誤處理。但更進一步的降級策略是什麼?
如果 Judge0 服務暫時不可用(例如 API 掛了,或我們超出了免費額度),我們的 /api/judge0/execute(明天會實作)應該要能優雅地失敗。它不應該讓整個應用程式崩潰,而是回傳一個特定的狀態,例如 { "status": "degraded", "message": "Code execution service unavailable" }。
當 AI 收到這個狀態時,我們會在 Prompt 裡指示它:「注意:無法取得客觀執行結果。請僅根據程式碼的邏輯、風格和可讀性進行評論,並在回饋中註明此程式碼未經實際運行。」
這就是一個穩健的降級路徑,確保即使部分外部服務失效,我們的核心功能依然能以一種降級但可用的方式運作。

今日回顧

今天我們為專案的「理科」能力打下了堅實且安全的基礎。雖然看起來只是建立了一個簡單的代理,但背後的安全原則至關重要。

✅ 我們理解了直接執行使用者程式碼的巨大風險,以及 Judge0 如何解決這個問題。
✅ 我們成功在 RapidAPI 申請了免費的 Judge0 服務並取得了 API 金鑰。
✅ 我們掌握了建立安全後端代理的核心原則,並實作了一個 Next.js API Route 來保護金鑰。
✅ 我們成功地透過代理 API 從 Judge0 獲取了資料,驗證了通訊管道的暢通。

明日預告

安全通道已經建立,明天(Day 13),我們就要開始傳送真正的「貨物」了。我們將實作最核心的 /api/judge0/execute 代理,學會如何將使用者的 JavaScript 程式碼發送到 Judge0 執行,並取回關鍵的執行結果,例如 stdout、stderr、執行時間等等。
這些客觀的「呈堂證供」,將在 Day 14 成為我們餵給 AI 的最有力證據!我們明天見!


上一篇
喚醒長期記憶:用資料庫函式實現高效語意搜尋
系列文
前端工程師的AI應用開發實戰:30天從Prompt到Production - 以打造AI前端面試官為例12
圖片
  熱門推薦
圖片
{{ item.channelVendor }} | {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言