Новости » Критические изменения в библиотеке вводы\вывода 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.