Сервис валидации кода студентов для платформы PASV. Запускает пользовательский JavaScript-код в изолированной V8 среде (isolated-vm) и возвращает результаты тестов на backend.
Client (coding.pasv.us) → Backend (server-prod.pasv.us) → Validator (validator.pasv.us)
↑ │
└──────── callback ────────┘
- Студент отправляет решение через клиент
- Backend отправляет POST-запрос на валидатор с кодом решения и тестами
- Валидатор запускает код в V8 isolate, собирает результаты
- Валидатор сразу отвечает
200 OKбэкенду ("контейнер создан") - Параллельно отправляет callback с результатами на
POST /validation/receive/result
Health check. Возвращает { status: "ok", uptime: 123.45 }.
Запуск JS-кода с тестами в V8 isolate.
Request body:
{
"solution": "function sum(a, b) { return a + b; }",
"test": "describe('sum', () => { it('adds', () => { expect(sum(1,2)).to.equal(3); }); });",
"userId": "abc123",
"challengeId": "def456",
"programmingLang": "JavaScript"
}solution— код студентаtest— тесты в формате describe/it/expect (Chai-совместимый синтаксис)userId,challengeId— для callback на backendprogrammingLang— пока поддерживается только"JavaScript"
Response (мгновенный):
{
"success": true,
"message": "Container has been created and result has been sent.",
"payload": null
}Результаты тестов отправляются асинхронно на backend через callback (см. src/callback.js).
Простое сравнение строк (для заданий типа "напиши точно как в примере").
Request body:
{
"solution": "const x = 5;",
"completedSolution": "const x = 5;"
}Response:
{
"success": true,
"message": "Everything is good",
"payload": [{ "type": "passed" }]
}Возможные warnings в payload: notEqual, semicolon, checkSpaces, noVar, letConst, constLet.
- Память: 32 MB на isolate
- Таймаут: 10 секунд на выполнение
- Код запускается в полностью изолированном V8 контексте — нет доступа к Node.js API, файловой системе, сети
- Каждый запрос создаёт новый isolate, после выполнения он уничтожается (
isolate.dispose())
Если студент напишет while(true) {}, isolated-vm прервёт выполнение через 10 секунд по таймауту. Isolate будет уничтожен, сервер продолжит работу. Ответ с ошибкой уйдёт на backend через callback.
- Синтаксическая ошибка в
solution→ возвращаетсяterminalerror,isPassed: false - Синтаксическая ошибка в
test→ аналогично - Runtime error → перехватывается, возвращается как terminal error
Express ограничивает body до 1 MB (express.json({ limit: '1mb' })).
После выполнения тестов валидатор отправляет результаты на backend (src/callback.js):
- prod:
https://server-prod.pasv.us/validation/receive/result - local:
http://localhost:5000/validation/receive/result
Формат payload — массив JSON-строк (совместимость со старым валидатором):
[
"{\"event\":\"start\",\"payload\":{\"userId\":\"...\",\"challengeId\":\"...\",\"solution\":\"...\"}}",
"{\"event\":\"pass\",\"payload\":{\"title\":\"...\",\"fullTitle\":\"...\",\"duration\":5}}",
"{\"event\":\"end\",\"payload\":{\"tests\":1,\"passes\":1}}"
]Callback отправляется асинхронно — ответ клиенту не ждёт его завершения. Ошибки callback логируются, но не влияют на ответ.
Внутри isolate доступны describe(), it(), expect() с Chai-совместимым API:
.to.equal(),.to.deep.equal(),.to.eql().to.be.true,.to.be.false,.to.be.null,.to.be.undefined,.to.be.NaN.to.be.ok,.to.exist.to.be.a('string'),.to.be.an('array').to.include(),.to.contain().to.have.length(),.to.have.lengthOf().to.have.property().to.be.above(),.to.be.below(),.to.be.at.least(),.to.be.at.most().to.throw(),.to.match(),.to.satisfy().to.have.members(),.to.have.keys().to.be.oneOf().to.be.an.instanceOf().not— инверсия:.to.not.equal()
npm install
npm run dev # nodemon, hot-reload
npm start # productionПеременные окружения:
PORT— порт (по умолчанию 7000)NODE_ENV—localдля callback на localhost:5000,prodдля production
docker build -t pasv-validator .
docker run -p 7000:7000 -e NODE_ENV=prod pasv-validatornpm testТесты используют fixtures из pasv-validator-dockerized/test-data/ (соседний репозиторий).
Деплоится на Coolify (OVH сервер) автоматически из GitHub.
- URL: https://validator.pasv.us
- Coolify UUID:
egkw808sgkgs0gcc0ckk4gcc - Порт: 7000
Coolify автоматически перезапускает контейнер при падении (Docker restart policy). Health check доступен на GET /test.
src/
index.js — Express-сервер, роуты
isolate-runner.js — Запуск кода в V8 isolate + встроенный тест-фреймворк
equal-validator.js — Простое сравнение строк
callback.js — Отправка результатов на backend
test/
run.js — Тест-раннер (fixtures из pasv-validator-dockerized)
Dockerfile — Production образ (node:18-slim + build tools для isolated-vm)
| Старый (Docker) | Новый (isolated-vm) | |
|---|---|---|
| Изоляция | Docker-контейнер на каждый запрос | V8 isolate |
| Скорость | ~3700ms avg | ~84ms avg (в 50 раз быстрее) |
| Тест-фреймворк | Mocha + Chai (npm) | Встроенный мини-фреймворк |
| Инфра | AWS EC2 Auto Scaling + ELB | Один контейнер на Coolify |
| Масштабирование | Горизонтальное (EC2) | Вертикальное (один процесс) |