CORS для фронтенда
Часто фронтенд работает на одном адресе, например http://localhost:5173 (сервер разработки Vite), а API — на другом, http://localhost:8080. Браузер по умолчанию не даёт JavaScript со страницы одного источника (origin: схема, домен и порт) читать ответы другого. Механизм, который позволяет серверу разрешить такие запросы, называется CORS (Cross-Origin Resource Sharing). Без него код fetch("http://localhost:8080/posts") на странице фронтенда упадёт с ошибкой в консоли браузера.
Как работает CORS
- Браузер добавляет к запросу заголовок
Originс адресом страницы. - Для «непростых» запросов, например
POSTс JSON или с заголовкомAuthorization, браузер сначала отправляет предварительный запрос (preflight) методомOPTIONS. - Сервер отвечает заголовком
Access-Control-Allow-Origin. Если источника там нет, браузер не отдаст ответ странице.
curl и другие программы правила CORS не проверяют — это защита именно браузера.
Плагин CORS
В Javalin есть встроенный плагин. Включите его в настройках:
// src/main/java/com/example/blog/Main.java
config.bundledPlugins.enableCors(cors -> {
cors.addRule(rule -> rule.allowHost("http://localhost:5173"));
});addRule добавляет правило, allowHost перечисляет разрешённые источники. Плагин сам отвечает на запросы OPTIONS и добавляет нужные заголовки.
Проверка
Изобразим браузер: передадим заголовок Origin.
curl -i -H "Origin: http://localhost:5173" http://localhost:8080/postsHTTP/1.1 200 OK
Content-Type: application/json
Access-Control-Allow-Origin: http://localhost:5173
Vary: Origin
[{"id":1,"title":"Привет, H2","body":"Посты теперь хранятся в базе.","author":"Олег"}]
Теперь предварительный запрос, который браузер отправит перед POST с паролем:
curl -i -X OPTIONS http://localhost:8080/posts \
-H "Origin: http://localhost:5173" \
-H "Access-Control-Request-Method: POST" \
-H "Access-Control-Request-Headers: content-type, authorization"HTTP/1.1 200 OK
Content-Type: text/plain
Access-Control-Allow-Headers: content-type, authorization
Access-Control-Allow-Methods: POST
Access-Control-Allow-Origin: http://localhost:5173
Vary: Origin
Сервер разрешил метод и заголовки, и браузер отправит настоящий запрос.
Другие настройки правила
| Настройка | Что делает |
|---|---|
rule.allowHost("https://blog.example.com") | Разрешить конкретные источники |
rule.allowHost("*.example.com") | Разрешить все поддомены |
rule.anyHost() | Разрешить всех (заголовок *) |
rule.allowCredentials = true | Разрешить куки и заголовок Authorization в запросах браузера |
rule.exposeHeader("X-Total") | Показать заголовок ответа скрипту |
rule.path = "posts*" | Применять правило только к части адресов |
Частая ошибка
Если источник не входит в список, плагин отвечает кодом 400, хотя тело ответа на месте:
curl -i -H "Origin: http://evil.example" http://localhost:8080/postsHTTP/1.1 400 Bad Request
Content-Type: application/json
[{"id":1,"title":"Привет, H2","body":"Посты теперь хранятся в базе.","author":"Олег"}]
Заголовка Access-Control-Allow-Origin нет, поэтому браузер не отдаст ответ странице. Увидев в консоли браузера ошибку CORS, сначала сравните адрес страницы с allowHost вплоть до порта и схемы: http://localhost:5173 и http://127.0.0.1:5173 — разные источники.