LINUX.ORG.RU

Сообщения KivApple

 

SwarmChatBot

Форум — Talks

Вдохновился Посоветуйте идею для TG-бота, которая минимум полезна и максимум можно продать как успеховую. и запилил за сегодняшний вечер небольшого Telegram бота.

Задумка проста. Добавляете бота в групповой чат. Пишете сообщение тегая бота (он не имеет доступа к сообщениям). Сообщение улетает в другой случайно выбранный чат, куда кто-то другой добавил этого бота.

Если кто-то в этом чате реплайнет на это сообщение, то ответ будет переслан назад в чат отправителя. Если зареплаить реплай, то реплай уйдёт в чат получателя и так далее. То есть поддерживается полноценная нить диалога между двумя чатами, но скрывается собственно какие это два чата и имена собеседников (и так как это чаты групповые, то отвечать могут разные люди знакомые между собой с одного конца).

В принципе бот работает и в личке, но моя больная фантазия рисует что-то типа, мол в групповых чатах должно быть веселее.

На случай если взлетит сделал систему хештегов - вместо приёма совсем рандома можно принимать сообщения содержащие некие хештеги, заданные в настройках чата.

Бот написан на Rust с Teloxide и запущен на сервере под убравлением Ubuntu.

UPD: Бот переименован

 

KivApple
()

Использование UUIDv7 в качестве токена авторизации

Форум — Development

UUID v4 не рекомендуется использовать в качестве токенов авторизации: https://security.stackexchange.com/questions/157270/using-v4-uuid-for-authent...

Do not assume that UUIDs are hard to guess; they should not be used as security capabilities (identifiers whose mere possession grants access), for example. A predictable random number source will exacerbate the situation.

Как я понимаю, идея в том, что обычно для их генерации используется не криптостойкие источники рандома и поэтому их может оказаться легко угадать.

Но тем не менее я даже UUID v4 не хочу использовать, не то что криптостойкий рандом в качестве токена авторизации - я буду хранить токены в Postgres, а Postgres, поговаривают, плохо работает с UNIQUE колонками со случайными данными - B-дерево распухает.

Хочу использовать в качестве токенов авторизации UUID v7 - они должны быть по заверению авторов дружелюбны к индексам баз данных. Но беда в том, что их ещё проще угадать, ибо там половина бит даже не рандом неизвестного качества, а вообще метка времени.

Но что если в БД хранить UUID, а юзеру отдавать и от юзера принимать JWT, где UUID хранить в JTI (ну и срок жизни токена, если надо, в exp зашить, stateless фичами JWT мы пользоваться всё равно не будем, нам от него по сути только цифровая подпись токена нужна).

Теперь злоумышленнику мало угадать UUID, ему ещё надо его подписать секретом приложения, который он не знает (в качестве небольшого бонуса - если утечёт дамп БД - без конфига приложения он всё равно не позволит угнать сессии). И наоборот, если утёк секрет JWT, чтобы увести сессию надо ещё угадать UUID (что не невозможно, но всё равно требует усилий). С другой стороны, приложение выборку из БД делает по jti, который UUID v7, который хорошо дружит с индексами БД.

Что думаете о такой схеме?

 ,

KivApple
()

Rust + WGPU + dyn trait

Форум — Development

Есть небольшой рабочий набросок кода, который комплируются и запускается, создавая окно winit и instance + surface wgpu:

use std::sync::Arc;
use winit::event_loop::EventLoop;
use winit::application::ApplicationHandler;
use winit::event::WindowEvent;
use winit::event_loop::{ActiveEventLoop, ControlFlow};
use winit::window::{Window, WindowId};

trait Renderer<'window> {
}

struct WGPURenderer<'window> {
	instance: wgpu::Instance,
	surface: wgpu::Surface<'window>
}

impl<'window> WGPURenderer<'window> {
	async fn new(window: Arc<Window>) -> anyhow::Result<Self> {
		let instance = wgpu::Instance::default();
		let surface = instance.create_surface(Arc::clone(&window))?;
		Ok(Self {
			instance,
			surface
		})
	}
}

impl<'window> Renderer<'window> for WGPURenderer<'window> {
}


#[derive(Default)]
struct App<'window> {
	window: Option<Arc<Window>>,
	renderer: Option<WGPURenderer<'window>>
}

impl ApplicationHandler for App<'_> {
	fn resumed(&mut self, event_loop: &ActiveEventLoop) {
		if self.window.is_none() {
			let window_attrs = Window::default_attributes();
			let window = Arc::new(
				event_loop.create_window(window_attrs).
					expect("Failed to create window")
			);
			self.window = Some(window.clone());
			let renderer = pollster::block_on(WGPURenderer::new(window))
				.expect("Failed to create renderer");
			self.renderer = Some(renderer);
		}
		event_loop.set_control_flow(ControlFlow::Wait);
	}
	
	fn window_event(
		&mut self,
		event_loop: &ActiveEventLoop,
		_window_id: WindowId,
		event: WindowEvent
	) {
		match event {
			WindowEvent::CloseRequested => {
				event_loop.exit();
			}
			_ => {}
		}
	}
}

fn main() {
	let event_loop = EventLoop::new()
		.expect("Failed to create event loop");
	let mut app = App::default();
	event_loop.run_app(&mut app)
		.expect("Failed to start event loop");
}

Проблема в том, что я хочу добавить косвенность - я не хочу прибивать гвоздями App к WGPURenderer, я хочу сделать возможными в будущем написать разные реализации трейта Renderer - например, VulkanRenderer, OpenGLRenderer и т. д.

Но стоит мне сделать:

#[derive(Default)]
struct App<'window> {
	window: Option<Arc<Window>>,
	renderer: Option<Arc<dyn Renderer<'window>>> // Тут может быть и Box
}

...

let renderer = pollster::block_on(WGPURenderer::new(window))
	.expect("Failed to create renderer");
self.renderer = Some(Arc::new(renderer));

Я получаю:

error: lifetime may not live long enough
  --> src/main.rs:29:25
   |
17 |     fn resumed(&mut self, event_loop: &ActiveEventLoop) {
   |                --------- has type `&mut App<'1>`
...
29 |             self.renderer = Some(Arc::new(renderer));
   |                                  ^^^^^^^^^^^^^^^^^^ coercion requires that `'1` must outlive `'static`

Как правильно организовать архитектуру приложения, чтобы можно было иметь общий код App (где расположен код обработки событий ввода и т. п.) на все бекэнды рендера с учётом того, что WGPU требует лайфтайм окна?

 ,

KivApple
()

Counter в Grafana

Форум — Admin

Есть приложение, в нём есть метрики-счётчики (counter), которые собираются в Prometheus, которые потом выводятся на графиках в Grafana.

Проблема в том, что рестарт приложения обнуляет счётчики.

Интересно, можно ли как-то с этим бороться, чтобы при выводе графиков учитывалось то что счётчики не могут уменьшаться, а могут только увеличиваться и, соответственно, их уменьшение игнорировалось и они продолжали расти дальше.

 ,

KivApple
()

Lorify RIP?

Форум — Talks

https://chromewebstore.google.com/detail/lorify/lcbahplohbljaoccfaionnkdgakdfomn

This extension is no longer available because it doesn't follow best practices for Chrome extensions.

Собственно, мой хром самовольно отключил расширение после обновления и не даёт включить обратно.

 

KivApple
()

Проверка доступности сайта из России

Форум — Talks

Есть ли какой-то сервис проверки, что сайт не подпадает под блокировки РКН ПО ФАКТУ?

Допустим, я администратор сайта, сам по себе я в список блокировок не попадаю, ничего осуждаемого с точки зрения РФ не делаю, но всегда есть риски, что IP раньше принадлежал кому-то, кто не нравился РКН или что кто-то, кто не нравился РКН направил одну из своих DNS записей на этот IP.

В этом случае, соответственно, надо попросить хостера IP поменять, если важна доступность сайта из РФ.

Но для этого об этом надо узнать. А узнать это проблематично, если сам в РФ не находишься. VPN в Россию, как я понимаю, совсем не обязательно применяют блокировки, потому что в датацентрах своя атмосфера.

Проверка списков ничего не гарантирует, так как если в список не включён явно судом именно мой домен или мой IP, то результат будет отрицательный.

Нужен сервис, который делает curl с домашнего провайдера в РФ на заданный домен и говорит вернулась ли заглушка или нет. В идеале, если он работает с набором провайдеров.

 ,

KivApple
()

VPN в Docker сеть

Форум — Admin

Есть хост с кучей Docker контейнеров.

Хочу запустить ещё один контейнер, в котором будет VPN сервер, дающий доступ к одной из сетей Docker. Причём таким образом, чтобы клиенты VPN могли подключаться к контейнерам подключенным к этой сети, а контейнеры в этой сети могли подключаться к клиентам VPN.

В Интернет же ходить клиентам не надо через VPN.

Как это лучше организовать? Ни сервер, ни клиенты не в России, так что устойчивость к РКН не нужна. Зато нужна беспроблемная работа VPN на Mac OS.

 ,

KivApple
()

Целесообразность перехода на более жирный сервер со старым CPU

Форум — Admin

Сейчас мои петы крутятся на VPS 2 ядра 2 гига озу 20 гб ssd. Дёшево, но постоянно надо думать о свободной памяти и диске, всё оптимизировать и иногда случаются инциденты а-ля Docker сожрал диск.

Есть вариант доплатить некоторую приемлемую сумму и перейти на bare metal server с 4 ядрами 32 гб ОЗУ 2 х 1 тб ssd.

Но есть нюанс. На сервере будет стоять Intel Xeon E3 1220, который вышел в 2011 году (сейчас VPS работает на AMD EPYC 7282).

Собственно, вопрос, не огребу ли я из-за этого проблем?

Из нагрузки nginx со статикой, postgres, бекэнды на разных языках устроенные по принципу «сходить в базу и отдать клиенту, упаковав результат в JSON, проверив пару условий», prometheus и grafana для мониторинга всего этого хозяйства. Всё крутится в докер контейнерах.

В данный момент производительность по CPU меня удовлетворяет, старый CPU в бенчмарках всего на 20% слабее на ядро, зато у меня будет в два раза больше ядер. С другой стороны, он может не умеет какие-то важные новые инструкции, а ещё сильнее просаживаться на заплатках от spectre и meltdown (хотя, возможно, их можно отключить, так как сервер исполняет только тот код, который я сам на него принёс, а user generated у меня только неисполняемые данные).

Ещё надо учитывать, что dedicated bare metal он полностью мой, а vps я с кем-то делю.

 ,

KivApple
()

Docker сожрал диск

Форум — Admin

Собственно, сабж

root@maumyrtille:~# du -shc /var/lib/docker/*
116K	/var/lib/docker/buildkit
5.4G	/var/lib/docker/containers
4.0K	/var/lib/docker/engine-id
27M	/var/lib/docker/image
236K	/var/lib/docker/network
6.9G	/var/lib/docker/overlay2
469M	/var/lib/docker/plugins
4.0K	/var/lib/docker/runtimes
4.0K	/var/lib/docker/swarm
8.0K	/var/lib/docker/tmp
554M	/var/lib/docker/volumes
14G	total
root@maumyrtille:~# docker system df
TYPE            TOTAL     ACTIVE    SIZE      RECLAIMABLE
Images          22        21        2.842GB   236.8MB (8%)
Containers      25        24        1.212MB   0B (0%)
Local Volumes   19        16        564.3MB   0B (0%)
Build Cache     0         0         0B        0B
root@maumyrtille:~# docker system prune
WARNING! This will remove:
  - all stopped containers
  - all networks not used by at least one container
  - all dangling images
  - unused build cache

Are you sure you want to continue? [y/N] y
Total reclaimed space: 0B

Чем занято более 10 ГБ?

 ,

KivApple
()

Что с Redis?

Форум — Development

Я тут узнал, что Redis с версии 7.4 сменил лицензию, вроде как нарушил обратную совместимость формата БД и по этому поводу случилась драма и наплодились его форки. Например, KeyDB, Valkey и т. д.

Если присмотреться к форкам, то у них есть всякие интересные фичи. Например, обещают лучшую утилизацию многоядерных CPU. Или предоставляют возможность ставить EXPIRE на поля хешей или отдельных членов сетов, а не только ключи целиком.

Собственно, вопрос к лоравцам: кто чем пользуется и на что стоит посмотреть?

Перемещено leave из talks

 ,

KivApple
()

Один Docker контейнер не может связаться с другим Docker контейнером

Форум — Admin

Имеется два docker compose файла.

infra/docker-compose.yaml:

services:
  watchtower:
    container_name: watchtower
    image: containrrr/watchtower
    restart: unless-stopped
    volumes:
      - /var/run/docker.sock:/var/run/docker.sock
      - $HOME/.docker/config.json:/config.json
    command: --label-enable
  prometheus:
    container_name: prometheus
    image: prom/prometheus
    user: root
    restart: unless-stopped
    volumes:
      - ./prometheus.yml:/etc/prometheus/prometheus.yml
      - ./data/prometheus:/prometheus
    networks:
      - prometheus-network
networks:
  prometheus-network:
    driver: bridge

app/docker-compose.yaml:

services:
  backend:
    container_name: app-backend
    image: ...
    restart: unless-stopped
    environment:
      - DATABASE_URL=postgres://${POSTGRES_USER}:${POSTGRES_PASSWORD}@${POSTGRES_HOST}:${POSTGRES_PORT}/${POSTGRES_DB}
    networks:
      - postgres-network
      - prometheus-network
    depends_on:
      - postgres
    labels:
      - "com.centurylinklabs.watchtower.enable=true"
  frontend:
    container_name: app-frontend
    ...
  postgres:
    container_name: app-postgres
    image: postgres:17-alpine
    ...
networks:
  postgres-network:
    driver: bridge
  prometheus-network:
    name: infra_prometheus-network
    external: true

Соответственно, запускается сначала infra, потом app.

Если зайти в контейнер app-backend и попробовать ping prometheus, то всё работает. Также можно попинговать самих себя - ping app-backend.

Если зайти в контейнер app-postgres и попробовать ping app-backend, то всё тоже работает.

Если зайти в контейнер prometheus и попробовать ping app-backend, то будет bad address 'app-backend'. Более того, если попробовать сделать ping prometheus, то эта команда тоже потерпит неудачу - то есть контейнер не видит сам себя.

Делаем docker inspect infra_prometheus-network:

[
    {
        "Name": "infra_prometheus-network",
        "Id": "ffec05671b873e60def53db831d4eb3966ba39bab74c01e8c7175362ae7da347",
        "Created": "2024-10-11T22:59:25.742311072Z",
        "Scope": "local",
        "Driver": "bridge",
        "EnableIPv6": false,
        "IPAM": {
            "Driver": "default",
            "Options": null,
            "Config": [
                {
                    "Subnet": "172.24.0.0/16",
                    "Gateway": "172.24.0.1"
                }
            ]
        },
        "Internal": false,
        "Attachable": true,
        "Ingress": false,
        "ConfigFrom": {
            "Network": ""
        },
        "ConfigOnly": false,
        "Containers": {
            "09bf47aa0f971ba2c9b686cb7bef947bd32eb7351ad41ec318118399345785d4": {
                "Name": "prometheus",
                "EndpointID": "dea3bb0a3e470cbcc9561ad8ef481c363f24a8c98628c4efd9d0193060092961",
                "MacAddress": "02:42:ac:18:00:02",
                "IPv4Address": "172.24.0.2/16",
                "IPv6Address": ""
            },
            "fa4dc2216105c90ccb1d165b6ce2b99dd43263a1bb459b43d0ce44a89330a9f8": {
                "Name": "app-backend",
                "EndpointID": "caa1b1371b290e7afd0273dd9a4e02781e2c895ed21d2913585739e233bc4784",
                "MacAddress": "02:42:ac:18:00:03",
                "IPv4Address": "172.24.0.3/16",
                "IPv6Address": ""
            }
        },
        "Options": {},
        "Labels": {
            "com.docker.compose.network": "prometheus-network",
            "com.docker.compose.project": "infra",
            "com.docker.compose.version": "2.29.7"
        }
    }
]

Видим, что IP контейнера app-backend - 172.24.0.3. Заходим в контейнер prometheus и делаем ping 172.24.0.3 - всё работает. То есть связь между контейнерами есть, но DNS не работает, причём только в одну сторону.

Пинговать внешние ресурсы типа Google из контейнера prometheus успешно получается, так что такой хотя бы DNS работает.

В чём может быть дело?

UPD: Проблема в официальном образе прометиуса - Один Docker контейнер не может связаться с другим Docker контейнером (комментарий), точнее скорее даже в базовом образе из которого он сделан. На неофициальном образе на базе alpine всё работает.

 ,

KivApple
()

Хранение в IndexedDB диапазона значений и выборка по одному значению

Форум — Development

Мне нужно сохранить в IndexedDB в Веб-приложении в браузере набор событий. Каждое событие характеризуется датой начала и датой конца.

Затем мне нужно выбирать события актуальные в заданный день (например, сегодня).

В SQL это выглядело бы как-то так:

SELECT * FROM events WHERE startDate >= $1 AND endDate <= $1

Но в IndexedDB нельзя делать запросы в такой свободной форме.

Есть две гипотетические возможности:

Создать композитный индекс и использовать его, но они очень плохо документированны и непонятно можно ли как-то сделать так, чтобы для startDate использовался IDBKeyRange.lowerBound, а для endDate использовался IDBKeyRange.upperBound в одном и том же запросе.

Создать какое-то синтетическое поле в документе, включающее в себя обе даты, и по которому можно было бы обходясь ограниченными возможностями индексов, запрашивать события для заданной даты.

 indexeddb, ,

KivApple
()

Токены авторизации в веб-приложениях

Форум — Development

Вот чисто интуитивно есть два варианта реализации авторизации в веб-приложении:

1) Храним сессию в БД, юзеру выдаём после успешного ввода логина-пароля ключ (разумеется, ключ должен быть длинным и случайным, чтобы исключить угадывание). При каждом запросе вытаскиваем из БД сессию по ключу и проверяем, что сессия валидна.

2) Шифруем важные для нас данные об учётке и отдаем юзеру в качестве токена. При каждом запросе дешифруем токен и если получается осмысленный результат, то считаем юзера авторизованным, в БД не ходим. Так как ключ шифрования недоступен юзеру, то это тоже надёжно.

Второй вариант с точки зрения производительности выгоднее первого, так как мы уменьшаем I/O (плюс общую БД тяжелее масштабировать, чем инстансы приложения, так что жрать CPU лучше, чем жрать I/O).

Но возникает одно НО. На многих сайтах есть функция «выйти со всех устройств» или даже список сессий и возможность кикнуть любую сессию. Это полезно в случае, если нечаянно залогинился на чужом компьютере или если тебя поломали, но ты успел сменить пароль.

Как это реализовано в первом варианте понятно - простое удаление сессии из БД. Как это реализовывать во втором варианте - непонятно.

Возникает вопрос, как устроены крупные сервисы типа всяких гуглов. Если посмотреть запросы, которые шлёт сайт гугла, там будут JWT токены, которые предполагают второй вариант (но, конечно, не гарантируют). Но у гугла есть опция, например, при смене пароля выбить все сессии во всех браузерах. Значит ли это, что гугл на каждый запрос к своему API дёргает БД сессий и JWT там только для красоты или каких-нибудь неважных сервисов.

В голову пришёл ещё компромиссный вариант, что можно гонять короткоживущие токены второго типа, но с расчётом, что они будут постоянно протухать и будет происходить переавторизация по токену первого типа. В этом случае получается, что функция «выйти со всех устройств» работать будет, но с лагом несколько минут/часов.

Интересно как это сделано в крупных сервисах. Используется ли вариант один, два или гибридный. Как во втором варианте работает функция выхода со всех устройств. Какой срок жизни токенов используется в третьем варианте обычно.

 

KivApple
()

SDL3 + Vulkan на Mac OS X

Форум — Development

Имеется Mac OS X, на которую установлен MoltenVk с помощью команды

brew install vulkan-tools

Этот пакет по зависимостям подтягивает и molten-vk, и vulkan-headers. Команда vulkaninfo успешно отрабатывает и показывает всякую информацию о Vulkan.

Затем, имеется простой код создающий окно в SDL (для простоты примера опущен код цикла событий, освобождения ресурсов и т. д.):

#include <stdio.h>
#include <SDL3/SDL.h>

int main(int argc, char *argv[]) {
    if (SDL_Init(SDL_INIT_VIDEO) < 0) {
        printf("Unable to initialize SDL: %s", SDL_GetError());
        return 1;
    }
    SDL_Window *window = SDL_CreateWindow("My app", 800, 600, SDL_WINDOW_RESIZABLE | SDL_WINDOW_HIGH_PIXEL_DENSITY | SDL_WINDOW_VULKAN);
    if (!window) {
        printf("Unable to create a window: %s", SDL_GetError());
        return 1;
    }
    return 0;
}

Код собирается следующим скриптом CMake:

cmake_minimum_required(VERSION 3.28)
project(MyApp)

set(BUILD_SHARED_LIBS OFF)

find_package(Vulkan REQUIRED)
add_subdirectory(lib/volk)
add_subdirectory(lib/SDL)

add_executable(MyApp main.c)
target_link_libraries(MyApp SDL3::SDL3-static volk::volk)

В данном случае lib/SDL это клонированный master https://github.com/libsdl-org/SDL, а lib/volk это клонированный master https://github.com/zeux/volk.

При запуске код выдаёт ошибку:

Unable to create a window: Failed to load Vulkan Portability library

В чём может быть проблема?

 ,

KivApple
()

Тип-обёртка для ошибок в Rust

Форум — Development

Допустим, я хочу сделать тип MyError, в который бы я мог упихать произвольную ошибку - хочу возвращать свой тип ошибки в Result методов моего трейта, чтобы абстрагироваться от деталей реализации. При этом я хочу, чтобы при написании реализации этих методов я мог использовать оператор вопроса с автоматическим преобразованием типа ошибки, а не вызывать вручную map_err.

use derive_more::Display;
use thiserror::Error;

#[derive(Error, Debug, Display)]
pub struct MyError(#[source] Box<dyn std::error::Error>);

impl<T: std::error::Error> From<T> for MyError {
	fn from(value: T) -> Self {
		Self(Box::new(value))
	}
}

Получаю ошибку компиляции:

error[E0119]: conflicting implementations of trait `From<MyError>` for type `MyError`
 --> src\mod.rs:9:1
  |
9 | impl<T: std::error::Error> From<T> for MyError {
  | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  |
  = note: conflicting implementation in crate `core`:
          - impl<T> From<T> for T;

Как я понимаю, проблема в том, что MyError сам по себе реализует трейт Error и получается что-то вроде рекурсии.

Что с этим делать?

 

KivApple
()

Нарисовать граф

Форум — Development

Требуется нарисовать граф (набор кружочков с подписями и связи между ними). При этом требуется, чтобы связи были более кастомизируемы, чем простой выбор стиля штриховки и направления стрелочки. Например, чтобы можно было задавать цвет и толщину обоих концов связи между кружочками и связь рисовалась плавным переходом от одного цвета и толщины к другому. Кружочки тоже неплохо кастомизировать. Например, чтобы помимо подписей в некоторые кружочки можно было вставить картинки.

Исходный датасет обрабатываю на Python, но в целом готов рассмотреть другие популярные языки.

Есть ли какие-то готовые библиотеки с таким функционалом? Graphwiz, как я понимаю, не умеет в тонкую кастомизацию внешнего вида связей.

Если же велосипедить свою отрисовку, то нужна хотя бы готовая библиотека/алгоритм оптимального позиционирования кружочков с учётом связей, чтобы результат был читаем.

Бонусом была бы возможность экспортировать граф не только в статичную картинку, но и в какую-нибудь HTML-страничку с возможностью зума, скроллинга и вывода дополнительной информации при наведении мыши на кружочки и связи.

 , ,

KivApple
()

Смешивание порошков

Форум — Science & Engineering

Допустим, есть два порошка двух разных веществ. Назовём их порошок А и порошок Б.

Требуется их смешать таким образом, чтобы в 10мл смеси было 1.5 г порошка А (а остальную массу составлял порошок Б).

С помощью каких технологий такое реализуется в домашних условиях? Допустим, есть точные весы, мерные баночки и керамическая кухонная ступка с пестиком.

Как я понимаю, главная засада в том, что смесь двух веществ будет иметь другую плотность нежели каждое вещество по отдельности (и при этом не как среднее плотностей ингридиентов с учётом пропорции смешивания). Так что, условно, 10 мл порошка А + 10 мл порошка Б дадут в сумме объём меньше 20 мл, поэтому нельзя решить задачу в лоб (вычислить плотности двух чистых веществ и найти пропорцию смешивания).

В голову приходит бинпоиск нужной пропорции, но это звучит замороченно (а ещё мы тратим порошки впустую) и интересно нет ли более аналитических решений.

 ,

KivApple
()

Запихнуть Axum Multipart в S3

Форум — Development

Пишу бекэнд на расте и Axum. Делаю загрузку файла multipart/form-data.

Грузить файл предполагается как-то так:

// Обработчик запроса принимает параметр mut multipart: Multipart
while let Some(mut field) = multipart.next_field().await? {
    if let Some("file") = field.name() { // Ищем нужное имя поля
        while let Some(chunk) = field.chunk().await? {
            // Делаем что-то с очередным куском файла (Bytes)
        }
        // Файл закончился
    }
}

Как я понял по документации, это единственный асинхронный способ. У Field есть ещё методы bytes() и text(), но они грузят содержимое файла полностью в ОЗУ и возвращают целиком Bytes или String.

А библиотека rust-s3 имеет вот такой метод у Bucket:

pub async fn put_object_stream<R: AsyncRead + Unpin>(
    &self,
    reader: &mut R,
    s3_path: impl AsRef<str>,
) -> Result<u16, S3Error>

То есть мне надо как-то превратить field в AsyncRead. При этом я категорически не хочу грузить файл целиком в ОЗУ или сохранять во временный каталог на диск. Хочу напрямую от клиента в S3 пересылать файл (но при этом S3 хранилище напрямую клиенту недоступно, потому что там ещё определённая бизнес-логика навёрнута на загрузке файлов, плюс контроль доступа).

 axum, ,

KivApple
()

Аналог beforeunload для History API

Форум — Web-development

JS позволяет потребовать у юзера подтверждение при закрытии страницы. Например, если на веб-странице содержится какой-нибудь редактор или важная форма и разработчик хочет напомнить юзеру сохранить несохранённое перед выходом. Для этого есть специальное событие beforeunload, в котором можно установить returnValue = true, если мы знаем, что не все данные сохранены.

Однако, beforeunload не работает при навигации с использованием History API. Это не беда для навигации с помощью pushState/replaceState, потому что мы сами вызываем эти функции и можем спросить подтверждение и в случае отказа просто не вызывать эти функции.

Но есть ещё событие popstate, которое происходит при нажатии кнопок Вперёд и Назад в браузере. И вот когда оно пришло, кажется, уже слишком поздно - preventDefault у него не работает, элемент истории в любом случае удаляется, так что подтверждающий диалог показывать нет смысла (мы не можем отменить навигацию).

Есть ли возможность реализовать аналог beforeunload для этой ситуации? Спросить у юзера подтверждение навигации с возможностью отмены, потому что у него есть несохранённые данные.

 ,

KivApple
()

HTML: Две кнопки

Форум — Web-development

Есть две кнопки. В одной одинокий эмоджи, в другой обычный текст:

<div style="position: fixed; right: 0; top: 0">
    <button class="top-button">
        🌎
    </button>
    <button class="top-button">
        Text
    </button> 
</div>

Они отформатированны с помощью CSS:

.top-button {
    display: inline-block;
    cursor: pointer;
    padding: 0.25rem;
    min-width: 2rem;
    min-height: 2rem;
    background-color: rgba(180, 180, 180, 50%);
    border: none;
    box-shadow: 2px 2px 2px darkgray;
    border-radius: 5px;
    margin: 0.1rem;
}

Живой пример: https://playcode.io/1629892

Скриншот: https://i.stack.imgur.com/hCmfL.png

Возникает две проблемы:

1) Кнопка без эмоджи в моём браузере отображается на пару пикселей НИЖЕ, чем кнопка с эмоджи. Очевидно, я хочу, чтобы кнопки были выровнены по горизонтали.

2) Кнопка без эмоджи, располагаясь ближе к правому краю окна, почему-то прижимается вплотную к нему, хотя у неё задан не-нулевой margin. Хочется, чтобы она так не делала.

 , ,

KivApple
()

RSS подписка на новые темы