Если вы разрабатываете iOS-приложение с встроенным веб-сервером (например, Mongoose), который работает временно в фоне, рано или поздно столкнётесь с проблемой Safari: при попытке открыть локальную страницу через HTTP браузер выдаст ошибку «Cannot open the page… HTTPS-only mode enabled».
Это связано с HSTS – механизмом принудительного использования HTTPS. Но как решить задачу, если сервер и клиент находятся внутри одного приложения и используют loopback-адрес (127.0.0.x), а публичный IP или DNS недоступны? Давайте разбираться, не переживайте – всё решаемо.
Почему Safari упорно требует HTTPS
Современные браузеры, включая Safari, активно борются с незашифрованным трафиком. HSTS (HTTP Strict Transport Security) – это политика, которая «запоминает» домены, обязанные использовать HTTPS. Если ваш локальный сервер отдаёт контент по HTTP, Safari просто заблокирует запрос после первого успешного подключения по HTTPS (даже если это было в другом контексте).
Кстати, ошибка «Cannot navigate to HTTP URL…» возникает именно из-за этой политики. Но выход есть: достаточно настроить сервер на работу с HTTPS, даже если он доступен только внутри приложения. И для этого не нужен публичный IP или DNS-запись – достаточно самоподписанного сертификата и пары трюков с локальным разрешением имён.
Пошаговая настройка HTTPS для Mongoose без публичных ресурсов
Основная идея: создать самоподписанный SSL-сертификат, привязать его к домену (например, local.dev), а затем «объяснить» устройству, что этот домен соответствует 127.0.0.1. Вот как это сделать:
1. Генерация сертификата
Используйте OpenSSL (установите через Homebrew, если нет):
openssl req -x509 -newkey rsa:4096 -nodes -keyout key.pem -out cert.pem -days 365 -subj "/CN=local.dev"
Параметр – subj задаёт Common Name – имя домена. Можно выбрать любое, но позже оно должно совпадать с адресом в приложении.
2. Настройка Mongoose
Загрузите сертификат и приватный ключ в код. Пример для Mongoose:
struct mg_mgr mgr;
mg_mgr_init(&mgr);
struct mg_connection *c = mg_http_listen(&mgr, "https://0.0.0.0:8000", eh, NULL);
mg_tls_opts opts = { .cert = "cert.pem", .certkey = "key.pem" };
mg_tls_init(c, &opts);
Убедитесь, что файлы cert.pem и key.pem добавлены в проект и доступны в бандле.
3. Локальное разрешение домена
Добавьте в файл /etc/hosts на устройстве строку:
127.0.0.1 local.dev
Если редактирование hosts недоступно (например, на реальных iOS-устройствах), используйте кастомный URL-схему в приложении для перенаправления на https://local.dev:8000.
4. Доверие сертификату
Самоподписанные сертификаты по умолчанию не доверяются. Чтобы Safari не ругался:
- Экспортируйте cert.pem в формате .der (например, через терминал: openssl x509 -in cert.pem -outform der -out cert.der).
- Установите его на устройство через профиль конфигурации или програмно, используя SecTrustEvaluate.
Типичные ошибки и как их избежать
Даже после настройки HTTPS могут возникнуть проблемы:
- «Invalid certificate» в Safari: Убедитесь, что Common Name в сертификате совпадает с доменом в URL (если вы используете local.dev, а в коде прописан localhost, ошибка неизбежна).
- Сервер не запускается: Проверьте, что порт не занят (на iOS иногда требуются кастомные разрешения в Info.plist для фоновых сокетов).
- HSTS всё равно блокирует запрос: Очистите кеш HSTS в Safari (Настройки → Safari → Очистить историю и данные).
Кстати, если вам не хочется возиться с hosts-файлами, можно использовать Bonjour для локального разрешения имён (mDNS). Например, зарегистрируйте службу с типом _https._tcp и доменом yourapp.local – тогда Safari автоматически обнаружит сервер.
И последнее: Mongoose поддерживает HTTP/2, что может ускорить загрузку файлов. Попробуйте включить его в настройках, добавив #define MG_ENABLE_HTTP2 1 перед компиляцией библиотеки.
Теперь ваш локальный сервер будет работать по HTTPS, обходя ограничения Safari. Да, придётся повозиться с сертификатами и доменами, зато пользователи не увидят раздражающих ошибок. А если что-то пойдёт не так – проверьте, нет ли опечаток в конфигах (вот где чаще всего кроются проблемы!).