Реализация простого SSE клиента на Dart

от автора

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

Мы рассматривали два подхода: WebSocket и Server-Sent Events (SSE). Оба варианта соответствовали нашим требованиям, но в итоге мы остановились на SSE — из-за его простоты реализации и использования стандартного HTTP-протокола.

Стоит отметить, что SSE имеет определённые ограничения по сравнению с WebSocket:

1. Поддерживаются только текстовые сообщения;

2. Передача данных возможна только в одну сторону — от сервера к клиенту.

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

Что потребуется для установления соединения?

1. Нам нужна модель данных которую мы будем отдавать в выходящий стрим. В нашем случае она будет содержать ID сообщения, и данные — JSON.

2. Создадим класс нашего клиента.  Подключение к SSE стриму и отключение от него мы обернем  в статические методы.  Так же для работы класса нам понадобятся следующие приватные поля:

class SSEClient {   static http.Client _client = http.Client();   static StreamController<SSEModel>? _streamController;   static StreamSubscription? _subscription;   static StreamSubscription? _dataSubscription;    static Stream<SSEModel> subscribeToSSE()    static Future<void> unsubscribeFromSSE() async {} }

3. Теперь будем реализовывать метод подключения. Внутри метода первым делом инициализируем HTTP-клент и стримконтроллер, который будет отдавать вовне данные полученные из канала.

dart _streamController = StreamController();  _client = http.Client();  final request = http.Request(       method == 'GET'        Uri.parse(url),  );

Заголовок авторизации и URL можно передать в аргументы метода.

4. Создаём переменную модельки данных (пустую):

var currentSSEModel = SSEModel(data: '', id: '', event: '');

5. Делаем GET запрос, при этом в запрос кладём следующие заголовки:

dart final headers = <String, String>{         'Authorization': 'Bearer ${token.accessToken}',         'Accept': 'text/event-stream',         'Cache-Control': 'no-cache',       }; header.forEach((key, value) {     request.headers[key] = value;   });

6. В ответе получаем StreamedResponse, на который можем подписаться и получать события:

dart Future<http.StreamedResponse> response = _client.send(request);  _subscription = response.asStream().listen((data) {}

7. Внутри  этой подписки, нам необходимо отделить  одно сообщение от другого, для этого необходимо трансформировать содержимое этого стрима в другой с отсечением одного сообщения от другого переводом строки:

dart _dataSubscription = data.stream.transform(const Utf8Decoder()).transform(const LineSplitter()).listen((dataLine) {})

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

if (dataLine.isEmpty) {      _streamController!.add(currentSSEModel);      currentSSEModel = SSEModel(data: '', id: '', event: '');       return; }

Нам понадобится регулярное выражение по которому мы будем идентифицировать строки с данными:

dart final lineRegex = RegExp(r'^([^:]*)(?::)?(?: )?(.*)?$'); final match = lineRegex.firstMatch(dataLine)!; final field = match.group(1);  if (field!.isEmpty) {     return; } var value = ''; if (field == 'data') {   value = dataLine.substring(       5,    );  } else {      value = match.group(2)  ?? '';  }  switch (field) {      case 'event':         currentSSEModel.event = value;      case 'data':         currentSSEModel.data = '${currentSSEModel.data ?? ''}$value\n';       case 'id':          currentSSEModel.id = value;       case 'retry':         break; }

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

return _streamController!.stream;

Вывод

Использование Server-Sent Events (SSE) стало для нас оптимальным решением задачи по передаче данных в реальном времени от сервера к клиенту. Несмотря на определённые ограничения SSE по сравнению с WebSocket — одностороннюю передачу и поддержку только текстовых сообщений — в нашем кейсе этого оказалось более чем достаточно. SSE позволил нам реализовать простой и надёжный канал обновлений без лишней сложности.


ссылка на оригинал статьи https://habr.com/ru/articles/896442/


Комментарии

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *