Как стать автором
Поиск
Написать публикацию
Обновить

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

Уровень сложностиСредний
Время на прочтение3 мин
Количество просмотров1.2K

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

Мы рассматривали два подхода: 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 позволил нам реализовать простой и надёжный канал обновлений без лишней сложности.

Теги:
Хабы:
Всего голосов 3: ↑3 и ↓0+3
Комментарии0

Публикации

Ближайшие события