Новости » Критические изменения в библиотеке вводы\вывода dart:io
Критические изменения в библиотеке вводы\вывода dart:io
Классы и интерфейсы стандартной библиотеки для ввода\вывода dart:io подверглись значительной переработки, и теперь соответствует новым интерфейсам и концепциям используемым в основных библиотеках dart:core и dart:async. Таким образом, практически полностью убраны реализации классов, использующие в работе регистрацию и вызов обратных callback-функций, и все асинхронные операции проводятся с помощью интерфейсов Stream и Future.
На данный момент, изменения представлены только в ветки для разработчиков "bleeding_edge", но в течение недели они будут совмещены с основной веткой.
К сожалению, данные изменения требуют переписывания существующего кода, в тех местах, где присутствуют обращения к классам библиотеки dart:io. К счастью, команда разработчиков, снабдила пользователей краткой справкой, по новым способам работы с dart:io.
Потоки в dart:io
Классы InputStream и OutputStream замещаются классами, реализующими новые интерфейсы IOSink и Stream<List<int>>. И соответственно вся логика использования потоков ввода-вывода перестраивается с учетом особенностей новых интерфейсов.
Чтение данных из потока - Stream вместо InputStream
При чтении данных из потока Stream<List<int>> теперь достаточно использовать только один метод Stream.listen, с помощью которого можно единовременно установить все функции для обработки событий - получение данных, завершение чтения и возникновение ошибок. В следующем примере, представлен код для старого и нового способов чтения данных из потока:
dart:io v1
InputStream stream = ...
stream.onData = () {
var data = request.inputStream.read();
/* обработка данных */
};
stream.onClosed = () {
/* все данные получены */
};
stream.onError = (e) {
/* ошибки при чтение данных */
};
dart:io v2
Stream<List<int>> stream = ...
stream.listen(
(data) {
/* обработка данных */
},
onDone: () {
/* все данные получены */
},
onError: (e) {
/* ошибки при чтение данных */
});
InputStream stream = ...
stream.onData = () {
var data = request.inputStream.read();
/* обработка данных */
};
stream.onClosed = () {
/* все данные получены */
};
stream.onError = (e) {
/* ошибки при чтение данных */
};
Stream<List<int>> stream = ...
stream.listen(
(data) {
/* обработка данных */
},
onDone: () {
/* все данные получены */
},
onError: (e) {
/* ошибки при чтение данных */
});
Стоит отметить, что вместе с классом InputStream удален класс для обработки текстовых потоков StringInputStream. А для того, чтобы работать с текстовыми потокам представлены два новых класса для трансформации байтовых потоков - StringDecoder и LineTransformer. Класс StringDecoder преобразовывает поток типа List<int> в поток строк String, а класс LineTransformer преобразовывает поток строк String в новый поток строк, но такой, в котором, каждый элемент String - является отдельной текстовой-строкой, т.е. завершается силовом конца строки.
dart:io v1
InputStream stream = ...
StringInputStream stringStream = new StringInputStream(stream);
stringStream.onLine = () {
String line = stringStream.readLine();
/* обработка строки текста */
};
stringStream.onClosed = () {
/* все данные получены */
};
stream.onError = (e) {
/* ошибки при чтение данных */
};
dart:io v2
Stream<<int>> stream = ...
stream
.transform(new StringDecoder())
.transform(new LineTransformer())
.listen(
(String line) {
/* обработка строки текста */
},
onDone: () {
/* все данные получены */
},
onError: (e) {
/* ошибки при чтение данных */
});
Запись данных в поток - IOSink вместо OutputStream
InputStream stream = ...
StringInputStream stringStream = new StringInputStream(stream);
stringStream.onLine = () {
String line = stringStream.readLine();
/* обработка строки текста */
};
stringStream.onClosed = () {
/* все данные получены */
};
stream.onError = (e) {
/* ошибки при чтение данных */
};
Класс IOSink заменил OutputStream и следующий пример демонстрирует, как адаптировать старый код, для использования нового интерфейса:
dart:io v1
OutputStream stream = ...
// записываем массив байт - "Hello"
stream.write([72, 101, 108, 108, 111]);
// записываем строку
stream.writeString(", OutputStream!");
stream.close();
dart:io v2
IOSink sink = ...
// записываем массив байт - "Hello"
sink.add([72, 101, 108, 108, 111]);
// записываем строку
sink.addString(", IOSink!");
sink.close();
Кроме того, интерфейс IOSink позволяет напрямую получать данные из потока, с помощью метода IOSink.addStream.
HTTP
Основные изменения, касающиеся реализации HTTP-протокола для серверной и клиентской части библиотеки dart:io заключаются в следующем:
- Новый интерфейс
HttpServerожидающий и обрабатывающий http-запросы, создается с помощью статического методаHttpServer.bind. - Интерфейс
HttpServerобрабатывает поток объектов, типаHttpRequests. - Из класса
HttpServerудален сеттерdefaultRequestHandlerи методaddRequestHandler. - Классы
HttpRequestиHttpClientResponseреализуют интерфейсStream<List<int>>. - Классы
HttpClientRequestиHttpResponseреализуют интерфейсIOSink.
HTTP-сервер
Чтобы создать новый http-сервер для обработки запросов c определенного адреса и порта, теперь используется статический метод HttpServer.bind, который создает экземпляр HttpServer и асинхронно возвращает его с помощью объект типа Future. Следующий пример демонстрирует, как адаптировать старый код, для использования нового интерфейса:
dart:io v1
HttpServer server = new HttpServer();
server.defaultRequestHandler = …
server.addRequestHandler(…);
server.listen(“127.0.0.1”, 8080);
// Теперь HTTP-сервер подсоединен к адресу и порту, и ожидает входящие запросы.
dart:io v2
HttpServer.bind(“127.0.0.1”, 8080)
.then((HttpServer server) {
server.listen(
(HttpRequest request) {
// Обработка нового запроса.
});
}
Методы HttpServer.defaultRequestHandler и HttpServer.addRequestHandler удалены из класса HttpServer и теперь вся обработка запросов происходит в callback-функции, задаваемой с помощью метода HttpServer.listen. В качестве параметра этой callback-функции передается объект класса HttpRequest, а параметры для ответа сервера, следует задавать через свойство request.response.
HTTP-клиент
Что касается клиентских http-запросов, то основной класс HttpClient сохранен, а удален класс HttpClientConnection. И теперь в качестве результата на новый запрос асинхронно возвращает объект типа Future. Для удобства использования метод завершения запроса к сервер - HttpClientRequest.close асинхронно возвращает ответ от сервера Future. Следующий пример демонстрирует, как адаптировать старый код, для использования нового интерфейса:
dart:io v1
HttpClient client = new HttpClient();
HttpClientConnection connection = client.get(...);
connection.onRequest = (HttpClientRequest request) {
// Подготавливаем запрос
request.outputStream.close();
}
connection.onResponse = (HttpClientResponse response) {
// Обрабатываем ответ
}
dart:io v2
HttpClient client = new HttpClient();
client.get(...)
.then((HttpClientRequest request) {
// Подготавливаем запрос
return request.close();
})
.then((HttpClientResponse response) {
// Обрабатываем ответ
});
Веб-сокеты
Интерфейсы для работы с веб-сокетами значительно упрощены, и теперь используется всего один класс WebSocket - для реализации и клиента и сервера. Класс WebSocket представляет поток собыйти.
Веб-сокет сервер
Со стороны сервера веб-сокеты обрабатываются с использованием специального трансформатора WebSocketTransformer. Данный трансформатор позволяет преобразовать поток типа HttpRequests в поток WebSockets. Следующий пример демонстрирует, как адаптировать старый код, для использования нового интерфейса:
dart:io v1
HttpServer server = new HttpServer();
server.listen(...);
WebSocketHandler handler = new WebSocketHandler();
handler.onOpen = (WebSocketConnection connection) {
connection.onMessage = (Object message) {
/* обрабатываем сообщение */
};
connection.onClosed = (status, reason) {
/* закрытие соединения */
};
};
server.defaultRequestHandler = handler.onRequest;
dart:io v2
HttpServer.bind(...).then((server) {
server.transform(new WebSocketTransformer()).listen((WebSocket webSocket) {
webSocket.listen((event) {
if (event is MessageEvent) {
/* обрабатываем сообщение */
} else if (event is CloseEvent) {
/* закрытие соединения */
}
});
});
Веб-сокет клиент
С точке зрения клиентских соединений, использование класса WebSocket стало еще проще. Для соединения с сервером достаточно просто использовать статический метод WebSocket.connect, который асинхронно возвращает объект типа Future. В следующем примере, представлен код для старого и нового способов работы с клиентскими веб-сокетами:
dart:io v1
HttpClient client = new HttpClient();
HttpClientConnection conn = client.openUrl(“https://127.0.0.1:8080”);
WebSocketClientConnection wsconn = new WebSocketClientConnection(conn);
wsconn.onMessage = (message) {
/* обрабатываем сообщение */
}
wsconn.onClosed = (status, reason) {
/* закрытие соединения */
};
dart:io v2
WebSocket.connect("ws://127.0.0.1:8080")
.then((WebSocket webSocket) {
webSocket.listen((event) {
if (event is MessageEvent) {
/* обрабатываем сообщение */
} else if (event is CloseEvent) {
/* закрытие соединения */
}
});
});
Процессы
Класс Process использует интерфейс Stream<List<int>> для обработки потоков стандартного вывода, и вывода ошибок (stdout и stderr), а так же интерфейс IOSink для потока ввода (stdin), а код завершения процесса теперь доступен через свойство Process.exitCode.
dart:io v1
Process process = ...
process.stdout.onData = ...
process.stdout.onDone = ...
process.onExit = (exitCode) { /* обработка кода завершения процесса */ }
dart:io v2
Process process = ...
process.stdout.listen(...);
process.exitCode.then((exitCode) { /* обработка кода завершения процесса */ });
Также сменились типы для стандартных потоков ввода-вывод уровня приложения - stdin теперь является объектом класса Stream<List<int>> , а stdout и stderr - IOSink.
Файлы и директории
Для чтения и записи файлов теперь тоже используются интерфейсы Stream<List<int>> и IOSink. Для того, чтобы прочитать данные из файла следует заменить метод File.openInputStream на новый File.openRead, который будет возвращать поток типа Stream<List<int>>. А для записи в файл, следует использовать новый метод File.openWrite, который возвращает поток типа IOSink.
Изменился и способ листинга директорий - метод Directory.list теперь возвращает поток объектов класса FileSystemEntity, который является родительским классом как для директорий Directory, так и для файлов File. Класс DirectoryLister, используемый ранее для обработки списка вложенных файлов и поддиректорий для данной директории - удален. В следующем примере, представлен код для старого и нового способов работы с листингом директорий:
dart:io v1
Directory dir = ...
DirectoryLister lister = dir.list();
lister.onDir = (Directory directory) { /* обработка директорий */ };
lister.onFile = (File file) { /* обработка файлов */ };
lister.onDone = (bool complete) { /* завершение листинга директории */ };
lister.onError = (error) { /* обработка ошибок */ };
dart:io v2
Directory dir = ...
dir.list().listen(
(FileSystemEntity fse) {
if (fse is Directory) {
Directory directory = fse;
/* обработка директорий */
} else if (fse is File) {
File file = fse;
/* обработка файлов */
}
},
onDone: () { /* завершение листинга директории */ },
onError: (error) { /* обработка ошибок */ });
Сокеты
Поскольку классы для работы с сокетами Socket и SecureSocket поддерживают двухсторонний ввод-вывод, то теперь они оба реализуют одновременно интерфейсы Stream<List<int>> и IOSink. Поэтому полностью удалены все старые методы установки callback-функций для чтения и записи с помощью потоков InputStream и OutputStream.
Аналогично методам http-сервера, чтобы установить новый сокет для обработки запросов, используется статический метод Socket.bind, который создает экземпляр Socket и асинхронно возвращает его с помощью объект типа Future. В следующем примере, представлен код для старого и нового способов создания сокетов:
dart:io v1
Socket socket = new Socket(host, port);
socket.onConnect = () { /* соединение установлено. */ }
dart:io v2
Socket.connect(host, port).then((Socket socket) {
/* соединение установлено. */
};
Классы ServerSocket и SecureServerSocket теперь используют статический метод bind для асинхронного подсоединения к сетевому интерфейсу:
dart:io v1
ServerSocket socket = new ServerSocket(host, port);
socket.onConnection = (Socket clientSocket) {
/* Do something with the clientSocket. */
}
dart:io v2
ServerSocket.bind(host, port).then((ServerSocket socket) {
socket.listen((Socket clientSocket) {
/* Do something with the clientSocket. */
})
});
Raw-сокеты
Для предоставления низкоуровневого доступа к сокетам представлен ряд новых классов - raw-сокеты, на которых строятся более высокоуровневые классы Socket и ServerSocket. Подробнее с ними можно ознакомится в документации к API.