Введение
Форме обращения в службу поддержки нужен API, который отличает корректный запрос от некорректного JSON, отсутствующего маршрута и недоступного сервиса заявок. Вы создадите этот HTTP-уровень на JavaScript, протестируете его локально, а затем развернёте вместе с временным вышестоящим Worker.
Используйте собственную учебную учётную запись Cloudflare и знания об авторизации устройства из лабораторной работы по подключению. Эта лабораторная работа начинается в новой виртуальной машине с Node.js 22.22.0 и локально установленным для проекта Wrangler 4.131.1 в /home/labex/project/support-api. Базовые знания функций, объектов и модулей JavaScript являются предварительным условием; поведение HTTP и асинхронные запросы объясняются здесь. Два публичных Worker используют только синтетические данные. Предоставленный вышестоящий сервис подтверждает получение запросов, но ничего не сохраняет: это не постоянная система заявок. Для этого небольшого упражнения достаточно Workers Free и поддомена workers.dev; база данных, приобретённый домен и платное обновление не нужны. Запросы учитываются в лимитах использования Workers вашей учётной записи.
Перед завершением лабораторной работы вы удалите оба Worker и выйдете из учётной записи. Не закрывайте этот терминал: в нём сохраняются переменные оболочки с именами ресурсов и URL-адресами.
Маршрутизация запросов по пути и методу
На этом шаге вы зададите для каждого поддерживаемого URL явный метод и ответ. Путь определяет операцию, а метод описывает действие. GET /health проверяет доступность, а POST /requests будет принимать запрос в службу поддержки.
Перейдите в подготовленный проект и проверьте инструменты:
cd /home/labex/project/support-api
node --version
npx wrangler --version
Ожидайте Node v22.22.0 и Wrangler 4.131.1. Установка уже выполнена; на собственном компьютере установите Node и выполните в проекте npm install --save-dev wrangler@4.131.1. Если нужно воспроизвести проект по его lock-файлу, используйте npm ci.
Сгенерируйте уникальное временное имя. Команда openssl rand -hex 6 выводит 12 случайных шестнадцатеричных символов; $(...) подставляет этот результат, а присваивание оболочки сохраняет его для следующих команд.
WORKER_NAME="labex-support-$(openssl rand -hex 6)"
Создайте стандартную конфигурацию Wrangler. Команда cat > file <<MARKER записывает следующие строки до закрывающего маркера; маркер без кавычек позволяет оболочке подставить $WORKER_NAME.
cat > wrangler.jsonc <<CONFIG
{
"name": "$WORKER_NAME",
"main": "src/index.js",
"compatibility_date": "2026-09-14",
"workers_dev": true,
"preview_urls": false,
"vars": {"UPSTREAM_URL": "http://127.0.0.1:8081"}
}
CONFIG
Параметр main указывает обработчик, compatibility_date выбирает поведение среды выполнения, а vars передаёт несекретный адрес вышестоящего сервиса через env. Пока он указывает на локальный тестовый сервис, который будет запущен позже. Публичные URL для предварительного просмотра отключены, чтобы упростить список ресурсов.
Запишите обработчик. Кавычки вокруг маркера JS сохраняют текст JavaScript без изменений. new URL(...).pathname извлекает маршрут. Тернарное выражение выбирает разрешённый метод; ответ HTTP 405 также сообщает этот метод в заголовке Allow. Response.json сериализует объект и задаёт для ответа тип содержимого. Асинхронный обработчик async сможет ожидать асинхронные операции на следующих шагах.
cat > src/index.js <<'JS'
export default {
async fetch(request, env) {
const path = new URL(request.url).pathname;
if (path !== '/health' && path !== '/requests') {
return Response.json({error: 'not_found'}, {status: 404});
}
const allowed = path === '/health' ? 'GET' : 'POST';
if (request.method !== allowed) {
return Response.json({error: 'method_not_allowed'}, {
status: 405, headers: {Allow: allowed}
});
}
if (path === '/health') return Response.json({status: 'ok'});
return Response.json({error: 'not_implemented'}, {status: 501});
}
};
JS
Запустите локальный Wrangler в фоновом режиме: > перенаправляет обычный вывод, 2>&1 добавляет к нему сообщения об ошибках, а & возвращает приглашение терминала, пока сервер продолжает работать.
npx wrangler dev --port 8080 > api.log 2>&1 &
cat api.log
Дождитесь, пока в журнале появится сообщение о готовности на порту 8080. Если сервер ещё запускается, снова выполните cat api.log. Команда curl -i включает в вывод статус HTTP и заголовки:
curl -i http://127.0.0.1:8080/health
curl -i http://127.0.0.1:8080/missing
curl -i http://127.0.0.1:8080/requests
Ожидайте соответственно ответ 200 с {"status":"ok"}, ответ 404 с {"error":"not_found"} и ответ 405 с {"error":"method_not_allowed"} вместе с Allow: POST. Эти ответы с ошибками предусмотрены намеренно. Нажмите кнопку проверки, пока сервер ещё работает.
Разбор и проверка входных данных JSON
На этом шаге вы будете отклонять некорректные данные до вызова любого вышестоящего сервиса. HTTP 415 означает неподдерживаемый тип содержимого, 400 — невозможность разобрать JSON, а 422 — несоответствие разобранных данных контракту. Поле subject должно быть строкой длиной от 1 до 80 символов после удаления пробелов в начале и конце.
Замените обработчик этой полной версией. headers.get читает объявленный тип содержимого; разделение по ; позволяет указать параметр кодировки. await request.json() ожидает разбор и считывает тело запроса один раз. Блок try/catch преобразует исключение разбора в предсказуемый ответ. JSON также может представлять null, массивы и числа, поэтому перед использованием строковых методов проверяется структура данных. trim() нормализует принятое значение subject.
cat > src/index.js <<'JS'
export default {
async fetch(request, env) {
const path = new URL(request.url).pathname;
if (path !== '/health' && path !== '/requests') {
return Response.json({error: 'not_found'}, {status: 404});
}
const allowed = path === '/health' ? 'GET' : 'POST';
if (request.method !== allowed) {
return Response.json({error: 'method_not_allowed'}, {
status: 405, headers: {Allow: allowed}
});
}
if (path === '/health') return Response.json({status: 'ok'});
const mediaType = (request.headers.get('content-type') || '').split(';')[0].trim().toLowerCase();
if (mediaType !== 'application/json') {
return Response.json({error: 'unsupported_media_type'}, {status: 415});
}
let body;
try {
body = await request.json();
} catch {
return Response.json({error: 'invalid_json'}, {status: 400});
}
if (!body || Array.isArray(body) || typeof body.subject !== 'string' ||
body.subject.trim().length < 1 || body.subject.trim().length > 80) {
return Response.json({error: 'invalid_subject'}, {status: 422});
}
const subject = body.subject.trim();
return Response.json({subject}, {status: 201});
}
};
JS
Wrangler перезагружает Worker после изменения исходного кода. Проверьте cat api.log на наличие ошибок компиляции. Отправьте корректный запрос: -H задаёт заголовок, а --data передаёт тело и выбирает метод POST. Одинарные кавычки сохраняют двойные кавычки JSON в оболочке.
curl -i http://127.0.0.1:8080/requests -H "Content-Type: application/json" --data '{"subject":" Printer offline "}'
Ожидайте ответ 201 и {"subject":"Printer offline"}. Это подтверждение в памяти, а не сохранённая заявка. Проверьте три разных пути отклонения:
curl -i http://127.0.0.1:8080/requests -H "Content-Type: application/json" --data '{'
curl -i http://127.0.0.1:8080/requests -H "Content-Type: application/json" --data '{"subject":" "}'
curl -i http://127.0.0.1:8080/requests -H "Content-Type: text/plain" --data 'hello'
Ожидайте ответы 400 invalid_json, 422 invalid_subject и 415 unsupported_media_type. Также попробуйте JSON null, [] и {"subject":5} — каждый из них должен вернуть 422, а не вызвать исключение. Нажмите кнопку проверки: она проверяет эти граничные случаи и сохраняет проверку доступности и маршрутизации.
Вызов вышестоящего сервиса и обработка его ошибок
На этом шаге вы подключите API к предоставленному симулятору сервиса заявок. Вышестоящий сервис — это зависимость, которую вызывает ваше приложение. Симулятор возвращает одну синтетическую заявку для обычных значений subject и HTTP 503 для специального значения simulate-outage; запросы он никогда не сохраняет.
Изучите предоставленный исходный код, чтобы понять работу тестового сервиса, затем задайте для него уникальное имя Worker:
cat upstream/index.js
cat > upstream/wrangler.jsonc <<CONFIG
{
"name": "${WORKER_NAME}-upstream",
"main": "index.js",
"compatibility_date": "2026-09-14",
"workers_dev": true,
"preview_urls": false
}
CONFIG
Параметр --config выбирает эту вторую конфигурацию. Используйте порт 8081 и отдельный порт инспектора, чтобы оба локальных Worker могли работать одновременно:
npx wrangler dev --config upstream/wrangler.jsonc --port 8081 --inspector-port 9230 > upstream.log 2>&1 &
cat upstream.log
curl -i http://127.0.0.1:8081/health
Дождитесь готовности и ожидайте ответ 200 с {"service":"support-upstream","status":"ok"}. Теперь замените основной обработчик полной интеграцией. Глобальная функция fetch выполняет исходящий запрос, а JSON.stringify кодирует проверенное значение subject. await ожидает ответ. Ошибки HTTP не вызывают исключение, поэтому upstream.ok явно проверяет статус; блок catch отдельно обрабатывает сбой подключения или невозможность прочитать ответ JSON. HTTP 502 сообщает клиенту о сбое зависимости, не раскрывая её внутреннее тело ответа.
cat > src/index.js <<'JS'
export default {
async fetch(request, env) {
const path = new URL(request.url).pathname;
if (path !== '/health' && path !== '/requests') {
return Response.json({error: 'not_found'}, {status: 404});
}
const allowed = path === '/health' ? 'GET' : 'POST';
if (request.method !== allowed) {
return Response.json({error: 'method_not_allowed'}, {
status: 405, headers: {Allow: allowed}
});
}
if (path === '/health') return Response.json({status: 'ok'});
const mediaType = (request.headers.get('content-type') || '').split(';')[0].trim().toLowerCase();
if (mediaType !== 'application/json') {
return Response.json({error: 'unsupported_media_type'}, {status: 415});
}
let body;
try {
body = await request.json();
} catch {
return Response.json({error: 'invalid_json'}, {status: 400});
}
if (!body || Array.isArray(body) || typeof body.subject !== 'string' ||
body.subject.trim().length < 1 || body.subject.trim().length > 80) {
return Response.json({error: 'invalid_subject'}, {status: 422});
}
const subject = body.subject.trim();
try {
const upstream = await fetch(`${env.UPSTREAM_URL}/tickets`, {
method: 'POST',
headers: {'Content-Type': 'application/json'},
body: JSON.stringify({subject})
});
if (!upstream.ok) {
return Response.json({error: 'upstream_unavailable'}, {status: 502});
}
const ticket = await upstream.json();
return Response.json({ticket: ticket.ticket, subject}, {status: 201});
} catch {
return Response.json({error: 'upstream_unavailable'}, {status: 502});
}
}
};
JS
curl -i http://127.0.0.1:8080/requests -H "Content-Type: application/json" --data '{"subject":"Printer offline"}'
curl -i http://127.0.0.1:8080/requests -H "Content-Type: application/json" --data '{"subject":"simulate-outage"}'
Ожидайте сначала ответ 201 с {"ticket":"demo-1001","subject":"Printer offline"}, затем ответ 502 с {"error":"upstream_unavailable"}. Внутренняя диагностическая информация симулятора не должна появиться. Значение subject является синтетическим, а этот endpoint не имеет постоянных побочных эффектов. Нажмите кнопку проверки, пока оба локальных сервера работают.
В этой лабораторной работе используется обычный HTTP для изучения границы взаимодействия с внешним сервисом. В следующей лабораторной работе рассматриваются service bindings для внутренних вызовов Worker-to-Worker. Ограничение времени ожидания и расширенная диагностика разбираются в лабораторной работе «Диагностика сбоев Worker». Тестовый сервис возвращает небольшие ограниченные ответы; в производственном API также необходимо ограничивать размеры недоверенных запросов и ответов.
Развёртывание и проверка публичного API
На этом шаге вы развернёте оба Worker в одной учебной учётной записи и замените локальный адрес вышестоящего сервиса его публичным URL. Сначала остановите оба локальных процесса. Просмотрите jobs и используйте фактические номера процессов; в примерах предполагается, что API имеет номер 1, а вышестоящий сервис — номер 2.
jobs
kill %1 %2
Авторизуйте эту новую виртуальную машину. Предоставленное разрешение идентифицирует вашу учётную запись и позволяет развёртывать и удалять Worker. Разрешение для последней команды соответствует разрешению из урока по развёртыванию, хотя этой лабораторной работе поток журналов не нужен.
npx wrangler login --device --browser=false --scopes account:read user:read workers_scripts:write workers_tail:read
Откройте показанную ссылку в браузере, введите текущий код устройства, просмотрите разрешения Wrangler, включая обязательный Background Access, выберите только свою учебную учётную запись и подтвердите авторизацию. Вернитесь в терминал и дождитесь завершения операции.
npx wrangler whoami --json
Убедитесь, что loggedIn: true, отображается имя учётной записи и её фактический идентификатор в accounts. Замените YOUR_ACCOUNT_ID ниже этим идентификатором. Сохраните имена, созданные на шаге 1; если переменная потерялась, прочитайте сохранённую конфигурацию и восстановите её вместо генерации нового имени ресурса.
cat > upstream/wrangler.jsonc <<CONFIG
{
"name": "${WORKER_NAME}-upstream",
"main": "index.js",
"compatibility_date": "2026-09-14",
"workers_dev": true,
"preview_urls": false,
"account_id": "YOUR_ACCOUNT_ID"
}
CONFIG
npx wrangler deploy --config upstream/wrangler.jsonc
Скопируйте точный URL workers.dev из вывода развёртывания. Используйте уже существующий поддомен учётной записи. Если Wrangler предложит зарегистрировать поддомен впервые, выберите доступное имя и выполните подтверждение; не изменяйте существующий поддомен учётной записи.
Теперь перепишите основную конфигурацию, заменив оба заполнителя идентификатором учётной записи и URL вышестоящего сервиса без завершающего слеша. global_fetch_strictly_public заставляет исходящие вызовы fetch() использовать маршрутизацию через публичный Интернет, включая другой Worker на поддомене workers.dev этой учётной записи. Без этого флага HTTP-вызов между Worker в одной зоне может завершиться ошибкой, хотя оба Worker по отдельности работают. Этот флаг должен находиться в конфигурации развёрнутого API; локальному тестовому сервису с loopback-адресом он не нужен. См. рекомендации по Fetch API.
cat > wrangler.jsonc <<CONFIG
{
"name": "$WORKER_NAME",
"main": "src/index.js",
"compatibility_date": "2026-09-14",
"compatibility_flags": ["global_fetch_strictly_public"],
"workers_dev": true,
"preview_urls": false,
"account_id": "YOUR_ACCOUNT_ID",
"vars": {"UPSTREAM_URL": "YOUR_UPSTREAM_URL"}
}
CONFIG
cat wrangler.jsonc
npx wrangler deploy
Скопируйте URL основного API из вывода развёртывания в переменную ниже:
API_URL="https://YOUR_API.YOUR_SUBDOMAIN.workers.dev"
curl -i "$API_URL/health"
curl -i "$API_URL/requests" -H "Content-Type: application/json" --data '{"subject":"Printer offline"}'
curl -i "$API_URL/requests" -H "Content-Type: application/json" --data '{"subject":"simulate-outage"}'
curl -i "$API_URL/requests" -H "Content-Type: application/json" --data '{'
Ожидайте те же контракты, что и локально: 200 для проверки доступности, 201 для синтетической заявки, 502 для ошибки вышестоящего сервиса и 400 для некорректного JSON. Если возникли ошибки подключения, подождите распространения имени хоста и повторите запрос. В Dashboard выберите ту же учебную учётную запись и откройте Compute → Workers & Pages. Найдите оба точных имени и сравните их адреса с выводом развёртывания. Это контрольная точка только для чтения; не создавайте там дубликаты приложений.
В примере ниже показаны основной API и соответствующий ему сервис -upstream. На левой боковой панели раскройте Compute и выберите Workers & Pages. Если в учётной записи есть другие проекты, используйте Search applications. Сравните полные сгенерированные имена и указанные под ними адреса с двумя результатами развёртывания; ваш случайный суффикс и поддомен учётной записи будут отличаться от примера.

Оба ресурса должны отображаться в выбранной учётной записи. Их наличие подтверждает место развёртывания; приведённые выше HTTP-ответы показывают, работает ли API. Если какого-либо имени нет, проверьте выбор учётной записи и вывод развёртывания, прежде чем повторять операцию. Не используйте Create application, чтобы дублировать развёртывание из CLI.
Нажмите кнопку проверки. Она независимо проверяет принадлежность обоих Worker, развёрнутое подключение к вышестоящему сервису, а также положительные и отрицательные публичные ответы. Для симулятора этой лабораторной работы отправляются только синтетические запросы без состояния.
Удаление обоих временных Worker
На этом шаге вы удалите API и его вышестоящий сервис, пока авторизация ещё доступна для проверки результата. Это единственные облачные ресурсы, созданные данной лабораторной работой. Перед удалением просмотрите обе конфигурации:
cat wrangler.jsonc
cat upstream/wrangler.jsonc
Убедитесь, что основное имя имеет вид labex-support-..., а имя вышестоящего сервиса содержит соответствующий суффикс -upstream; идентификатор учебной учётной записи должен быть одинаковым. Сначала удалите основной API, затем вышестоящий сервис. При каждом запросе проверьте точное имя и нажмите единственную клавишу y.
npx wrangler delete
npx wrangler delete --config upstream/wrangler.jsonc
Wrangler 4.131.1 может удалить Worker, а затем вывести ошибку аутентификации при проверке данных KV для устаревшего Workers Sites, поскольку это разрешение не предоставляет доступ к KV. Такая конкретная диагностика сама по себе не подтверждает ни успех, ни неудачу удаления. Не выдавайте дополнительные разрешения только для того, чтобы скрыть это сообщение. Обновите раздел Workers & Pages и нажмите кнопку проверки: успешная авторизованная инвентаризация должна подтвердить отсутствие обоих имён. Ошибки сети или авторизации не дают однозначного результата; устраните их, прежде чем продолжать. Не изменяйте другие приложения, учебную учётную запись и её поддомен.
Отключение виртуальной машины
На этом шаге вы удалите авторизацию Wrangler для этой виртуальной машины после успешной проверки удаления обоих ресурсов. Выход из учётной записи не удаляет Worker, поэтому очистка выполняется заранее.
npx wrangler logout
npx wrangler whoami --json
Ожидайте явное значение "loggedIn": false. Команда проверки статуса без аутентификации может завершиться с ненулевым кодом; это ожидаемо, если её структурированный результат явно сообщает о выходе из учётной записи. Ошибка сети не является эквивалентом этого результата. Нажмите кнопку проверки, затем завершите работу среды LabEx. Вход в браузере и учебная учётная запись останутся доступными для следующих лабораторных работ; каждая новая виртуальная машина потребует собственной авторизации.
Итоги
Вы создали HTTP API с учётом метода, разобрали и проверили JSON, нормализовали принятые данные и преобразовали ошибку вышестоящего сервиса в предсказуемую публичную ошибку. Вы протестировали корректные и отклонённые запросы локально и в Cloudflare, проверили принадлежность обоих развёртываний, удалили временные ресурсы и отключили виртуальную машину.
Дополнительные сведения см. в официальной документации: Request API, Response API и Fetch API.

