Эта страница переведена с английского. Читать страницу на английском
Приложение Zeyn можно поставлять как один исполняемый файл для рабочего стола. Крейт zeyn-desktop запускает собственный сервер приложения внутри процесса и показывает его в нативном окне через системный WebView:
| Платформа | WebView | Статус |
|---|---|---|
| Windows 10 и 11 | WebView2 (Edge) | собрано и проверено вручную |
| macOS | WKWebView | компилируется через wry, вручную не проверялось |
| Linux | WebKitGTK 4.1 | компилируется через wry, если установлены libwebkit2gtk-4.1-dev и libgtk-3-dev, вручную не проверялось |
Ничего не покидает машину. Приложение работает офлайн, пока само не обращается к удалённым сервисам.
Использование
Подключите крейт за feature, чтобы обычная серверная сборка не компилировала WebView:
[features]
desktop = ["dep:zeyn-desktop"]
[dependencies]
zeyn-desktop = { version = "0.0.2", optional = true }Запускайте окно вместо App::serve:
#[cfg(feature = "desktop")]
fn main() -> std::io::Result<()> {
zeyn_desktop::Desktop::new(app()?)
.id("my-app")
.title("My App")
.size(1280, 800)
.run()
}idзадаёт имя пользовательской папки данных и обеспечивает один экземпляр на каждый id. По умолчанию это имя исполняемого файла.titleпоказывается, пока страница не задастdocument.title; после этого заголовок окна следует за страницей.sizeэто начальный размер в логических пикселях. Последующие запуски используют последний размер и положение.
run блокируется до закрытия окна, останавливает сервер и завершает процесс. С ZEYN_DESCRIBE=1 он выводит описание приложения и возвращается, поэтому zeyn check и zeyn routes продолжают работать.
Чтобы скрыть окно консоли в релизной сборке на Windows, поместите это в начало main.rs:
#![cfg_attr(all(feature = "desktop", not(debug_assertions)), windows_subsystem = "windows")]examples/adisyon это полный пример, приложение кассы (point of sale):
cargo run -p zeyn-example-adisyon --features desktopСборка исполняемого файла
cd my-app
zeyn build-desktop # same as --features desktop
zeyn build-desktop --package my-app --features desktop,postgresКоманда запускает cargo build --release с оптимизацией на этапе компоновки, одним codegen unit и вырезанными символами, затем выводит путь и размер каждого исполняемого файла. Скорость остаётся на opt-level = 3.
Значок, сведения о версии и манифест (Windows)
Windows читает их из ресурсов внутри exe. Добавьте скрипт сборки, который встраивает их только для настольной сборки:
[features]
desktop = ["dep:zeyn-desktop", "dep:winresource"]
[build-dependencies]
winresource = { version = "0.1", optional = true, default-features = false }build.rs:
fn main() {
#[cfg(feature = "desktop")]
if std::env::var("CARGO_CFG_TARGET_OS").as_deref() == Ok("windows") {
let mut res = winresource::WindowsResource::new();
res.set_icon("assets/app.ico")
.set("ProductName", "My App")
.set("FileDescription", "My App")
.set_manifest_file("assets/app.exe.manifest");
res.compile().unwrap();
}
}У значка должен быть идентификатор ресурса 1 (значение по умолчанию у set_icon); его используют окно и панель задач. Версия берётся из package.version. Манифест с поддержкой DPI для каждого монитора, UTF-8 и common controls 6 смотрите в examples/adisyon/build.rs и examples/adisyon/assets. Набор инструментов MSVC находит rc.exe в Windows SDK; набор GNU использует windres.
Рантайм WebView2
В Windows 11 и актуальной Windows 10 рантайм WebView2 предустановлен и обновляется сам (evergreen runtime). Exe его не включает, что сохраняет малый размер.
Для старых или закрытых машин:
- Поставляйте вместе с установщиком evergreen bootstrapper от Microsoft (
MicrosoftEdgeWebview2Setup.exe, около 2 МБ) и запускайте его с/silent /install, если рантайма нет. Он загружает рантайм один раз. - Для машин без интернета используйте автономный установщик evergreen (большой, офлайн) или runtime фиксированной версии, размещённый рядом с exe. Фиксированная версия сама не обновляется, поэтому обновления безопасности остаются на вас.
Без рантайма окно не может открыться, и run возвращает ошибку.
Модель безопасности
Сервер доступен только собственному окну приложения.
- Только loopback. Слушатель привязывается к
127.0.0.1на порту, который выбирает система. Он никогда не привязывается к0.0.0.0или адресу локальной сети. - Токен запуска. Каждый запуск создаёт случайный 256-битный токен. Окно сначала открывает
/_desktop/start?t=<token>, который устанавливает токен как cookieHttpOnly,SameSite=Strictи перенаправляет на/. Каждый другой запрос должен нести токен в этом cookie или в заголовкеx-zeyn-desktop, иначе получает403. Статические ресурсы тоже защищены. Токен живёт только в памяти и сравнивается за постоянное время. - Host и Origin. Заголовок
Hostдолжен быть ровно127.0.0.1:<port>, что останавливает DNS rebinding (сайт, имя которого разрешается в 127.0.0.1, отправляет в качестве host собственное имя). ЗаголовокOrigin, если он есть, должен быть ровноhttp://127.0.0.1:<port>. Оба условия действуют для каждого пути, включая конечные точки start и focus. - Другие локальные программы могут подключиться к порту, но не знают токена. Другие учётные записи пользователей не могут прочитать файл экземпляра в пользовательской папке данных.
- Навигация. Окно показывает только собственный origin, свои URL
blob:иabout:blank. Ссылкиhttp,httpsиmailtoоткрываются обработчиком системы по умолчанию; любая другая схема (file:, пользовательские обработчики протоколов,javascript:) отбрасывается.window.openникогда не создаёт второй WebView. - Devtools включены в отладочных сборках и выключены в релизных.
- Загрузки разрешены только с origin приложения и попадают в папку «Загрузки» пользователя, никогда не перезаписывая файл (
orders.csv,orders (1).csv, ...). Несколько загрузок подряд разрешены без запроса. Прочие запросы разрешений (камера, микрофон, буфер обмена) получают обычный запрос WebView.
Что защита не покрывает:
- Программа, запущенная от того же пользователя, может читать память процесса или подключить отладчик; ни одно локальное приложение не может от этого защититься.
- WebView2 учитывает переменную окружения
WEBVIEW2_ADDITIONAL_BROWSER_ARGUMENTS, поэтому тот, кто может задавать переменные окружения пользователя, может включить удалённую отладку. Тот же пользователь, тот же предел, что выше. - Собственная аутентификация приложения по-прежнему действует внутри окна.
DevAuthв настольной сборке даёт полный доступ любому, кто сидит за машиной.
Поведение
- Один экземпляр. Первый запуск записывает
instance(порт и отдельный ключ фокуса) в папку данных. Второй запуск отправляет ключ на/_desktop/focus; первое окно выходит на передний план, а второй процесс завершается примерно за 150 мс. Если два запуска стартуют в один и тот же момент, могут открыться оба. - Состояние окна. Размер, положение и признак развёрнутости сохраняются в
window.jsonпри закрытии. Сохранённое положение, которого больше нет ни на одном мониторе, игнорируется. - Завершение. Закрытие окна освобождает WebView, корректно останавливает сервер (не более 2 секунд) и завершает процесс. Процессы WebView завершаются вместе с ним.
- Папка данных.
%LOCALAPPDATA%\<id>в Windows,~/Library/Application Support/<id>в macOS,~/.local/share/<id>в Linux. Профиль WebView (кэш, локальное хранилище) лежит вwebviewвнутри неё, поэтому exe работает и из папки только для чтения, например Program Files. - Без значка в трее. Не встроен.
Размер и память
Измерено в Windows 11 на examples/adisyon, релизная сборка, рантайм WebView2 156:
exe от zeyn build-desktop | 6.72 МБ (7.25 МБ с обычным релизным профилем) |
| Процесс приложения, простой | 4.3 МБ private, 25.6 МБ working set |
| Процесс приложения и процессы WebView2, простой | 7 процессов, около 180 МБ private, 370 МБ working set |
То же с --disable-gpu | около 114 МБ private, 330 МБ working set |
| Второй запуск до завершения | около 130 мс |
Доминирует WebView. Один только его GPU-процесс занимает около 88 МБ. Для простых экранов на машинах с малой памятью запускайте exe с WEBVIEW2_ADDITIONAL_BROWSER_ARGUMENTS=--disable-gpu (например, из ярлыка или скрипта запуска), чтобы сэкономить около 65 МБ ценой программной отрисовки.