
Vert.x – это экосистема для создания реактивных приложений на JVM, которые могут масштабироваться и обрабатывать огромные объемы данных в реальном времени. Это полиглотная платформа, поддерживающая не только Java, но и Kotlin, Groovy, Scala, и еще js. В контексте статьи работать будем на java
Одна из фич vert.x — это набор абстракций и API для асинхронной работы с сетью, файловой системой и другими ресурсами. С его помощью можно легко создавать масштабируемые веб-приложения, микросервисы, сетевые утилиты и многое другое.
Файловая система
В Vert.x вся работа с файловой системой осуществляется через класс FileSystem.
Для создания файла можно использовать метод createFile, который асинхронно создает новый файл:
FileSystem fs = vertx.fileSystem(); fs.createFile("myfile.txt", result -> { if (result.succeeded()) { System.out.println("Файл успешно создан."); } else { System.err.println("Ошибка при создании файла: " + result.cause()); } });
Аналогично, для удаления файла используется метод delete:
fs.delete("myfile.txt", result -> { if (result.succeeded()) { System.out.println("Файл успешно удален."); } else { System.err.println("Ошибка при удалении файла: " + result.cause()); } });
Для чтения содержимого файла Vert.x есть метод readFile, который возвращает содержимое файла в виде объекта Buffer, который может быть легко преобразован в строку или массив байтов:
fs.readFile("myfile.txt", result -> { if (result.succeeded()) { Buffer buffer = result.result(); System.out.println("Содержимое файла: " + buffer.toString()); } else { System.err.println("Ошибка при чтении файла: " + result.cause()); } });
Для записи в файл используйте метод writeFile, который принимает путь к файлу и данные в виде Buffer:
Buffer data = Buffer.buffer("Hello, Vert.x!"); fs.writeFile("myfile.txt", data, result -> { if (result.succeeded()) { System.out.println("Данные успешно записаны в файл."); } else { System.err.println("Ошибка при записи в файл: " + result.cause()); } });
AsyncFile
AsyncFile в Vert.x — это интерфейс для асинхронной работы с файлами. Он позволяет выполнять различные операции с файлами: чтение и запись, асинхронно, т.е. без блокирования потока, в котором выполняется операция.
Основные методы:
-
read(Buffer buffer, int offset, long position, int length, Handler<AsyncResult<Buffer>> handler): асинхронно читает данные из файла вBuffer.offsetуказывает на позицию в буфере, куда будут записаны данные,position— позиция в файле, с которой начнется чтение, аlength— количество байтов для чтения. -
write(Buffer buffer, long position, Handler<AsyncResult<Void>> handler): асинхронно записывает данные изBufferв файл, начиная с указаннойposition. -
flush(Handler<AsyncResult<Void>> handler): асинхронно сбрасывает данные из буфера в файл -
close(Handler<AsyncResult<Void>> handler): асинхронно закрывает файл. После закрытия файла любые попытки его чтения или записи приведут к ошибке.
Чтения файла асинхронно может выглядеть так:
FileSystem fileSystem = vertx.fileSystem(); String filePath = "path/to/your/file.txt"; fileSystem.open(filePath, new OpenOptions(), openRes -> { if (openRes.succeeded()) { AsyncFile file = openRes.result(); Buffer buffer = Buffer.buffer(1024); // размер буфера для чтения file.read(buffer, 0, 0, 1024, readRes -> { if (readRes.succeeded()) { System.out.println("File content: " + buffer.toString()); file.close(voidAsyncResult -> {}); } else { System.err.println("Error reading file: " + readRes.cause().getMessage()); } }); } else { System.err.println("Could not open file: " + openRes.cause().getMessage()); } });
Асинхронная запись файла:
Buffer data = Buffer.buffer("This is some data to be written to the file."); fileSystem.open(filePath, new OpenOptions().setWrite(true), openRes -> { if (openRes.succeeded()) { AsyncFile file = openRes.result(); file.write(data, 0, writeRes -> { if (writeRes.succeeded()) { System.out.println("Data written successfully!"); file.flush(flushRes -> { if (flushRes.succeeded()) { System.out.println("Data flushed to disk."); } file.close(voidAsyncResult -> {}); }); } else { System.err.println("Failed to write data: " + writeRes.cause().getMessage()); } }); } else { System.err.println("Could not open file: " + openRes.cause().getMessage()); } });
Для открытия файла асинхронно используется метод open класса FileSystem. Метод возвращает Future<AsyncFile>, который позволяет работать с файлом асинхронно:
FileSystem fs = vertx.fileSystem(); String path = "path/to/your/file.txt"; fs.open(path, new OpenOptions(), result -> { if (result.succeeded()) { AsyncFile file = result.result(); // можно работать с AsyncFile } else { // обработка ошибки System.err.println("Error opening file: " + result.cause().getMessage()); } });
Обработка результатов через Future
В Vert.x Future<T> используется для представления результата, который будет доступен позже. Он может успешно завершиться с результатом типа T или завершиться с ошибкой.
Cоздадим простой Future:
Future<String> future = Future.future(promise -> { // асинхронная операция vertx.setTimer(1000, id -> promise.complete("Operation completed")); });
Для обработки результатов Future имеет методы onSuccess, onFailure, и compose.
onSuccess вызывается, если Future успешно завершен, а onFailure — в случае ошибки:
future.onSuccess(result -> { System.out.println("Result: " + result); }).onFailure(error -> { System.err.println("Failed: " + error.getMessage()); });
compose позволяет создать цепочку асинхронных операций, где каждая следующая операция начинается после успешного завершения предыдущей:
Future<String> future1 = Future.future(promise -> { vertx.setTimer(1000, id -> promise.complete("First operation")); }); future1.compose(result -> { // код выполнится после future1 return Future.future(promise -> vertx.setTimer(1000, id -> promise.complete(result + ", second operation"))); }).onSuccess(result -> System.out.println("Result: " + result));
Пример с чтением файла:
FileSystem fs = vertx.fileSystem(); String path = "path/to/your/file.txt"; Future<Buffer> readFileFuture = fs.readFile(path); readFileFuture.onSuccess(buffer -> { System.out.println("File content: " + buffer.toString()); }).onFailure(Throwable::printStackTrace);
Предположим, есть две асинхронные операции: первая читает файл, а вторая записывает содержимое в другой файл:
String readPath = "path/to/read.txt"; String writePath = "path/to/write.txt"; fs.readFile(readPath).compose(buffer -> { // после успешного чтения файла записываем содержимое в другой файл return fs.writeFile(writePath, buffer); }).onSuccess(v -> System.out.println("File was successfully copied")) .onFailure(Throwable::printStackTrace);
Future можно использовать для композиции результатов нескольких асинхронных операций:
Future<String> future1 = Future.succeededFuture("Hello"); Future<String> future2 = Future.succeededFuture("World"); CompositeFuture.all(future1, future2).onSuccess(composite -> { String result = composite.resultAt(0) + " " + composite.resultAt(1); System.out.println(result); // "Hello World" });
Pump и CompositeFuture
Pump в Vert.x — это утилита, которая помогает передавать данные из ReadStream в WriteStream, автоматически управляя обратным давлением:
FileSystem fs = vertx.fileSystem(); String sourcePath = "path/to/source/file"; String destPath = "path/to/dest/file"; fs.open(sourcePath, new OpenOptions().setRead(true), readResult -> { if (readResult.succeeded()) { AsyncFile readFile = readResult.result(); fs.open(destPath, new OpenOptions().setWrite(true), writeResult -> { if (writeResult.succeeded()) { AsyncFile writeFile = writeResult.result(); Pump pump = Pump.pump(readFile, writeFile); readFile.endHandler(v -> writeFile.close()); writeFile.endHandler(v -> readFile.close()); pump.start(); } else { System.err.println("Failed to open destination file: " + writeResult.cause()); } }); } else { System.err.println("Failed to open source file: " + readResult.cause()); } });
Pump.pump(readFile, writeFile) создает экземпляр Pump, который автоматически читает из readFile и записывает в writeFile, управляя обратным давлением между потоками
CompositeFuture позволяет группировать несколько асинхронных операций и обрабатывать их результаты как единое целое:
FileSystem fs = vertx.fileSystem(); List<Future> futures = new ArrayList<>(); // путь к файлам, которые нужно прочитать List<String> filePaths = Arrays.asList("path/to/file1", "path/to/file2", "path/to/file3"); for (String path : filePaths) { Future<Buffer> future = fs.readFile(path).future(); futures.add(future); } CompositeFuture.all(futures).onComplete(ar -> { if (ar.succeeded()) { for (int i = 0; i < filePaths.size(); i++) { System.out.println("Content of " + filePaths.get(i) + ": " + ar.result().resultAt(i)); } } else { System.err.println("Failed to read one or more files: " + ar.cause()); } });
CompositeFuture.all(futures) используется для объединения нескольких асинхронных операций чтения файлов. Результаты каждой операции доступны через ar.result().resultAt(i), где i — индекс операции в исходном списке.
Небольшой пример
Реализуем сервер, который будет поддерживать загрузку и скачивание файлов через HTTP:
Создаем новый Maven-проект и добавляем зависимость Vert.x Web в pom.xml:
<dependencies> <dependency> <groupId>io.vertx</groupId> <artifactId>vertx-web</artifactId> <version>4.2.1</version> <!-- Используйте актуальную версию --> </dependency> </dependencies>
Создаем класс FileServerVerticle, который расширяет AbstractVerticle. В методе start определяем маршруты для загрузки и скачивания файлов:
import io.vertx.core.AbstractVerticle; import io.vertx.core.Promise; import io.vertx.ext.web.Router; import io.vertx.ext.web.RoutingContext; import io.vertx.ext.web.handler.BodyHandler; import io.vertx.ext.web.file.FileUpload; public class FileServerVerticle extends AbstractVerticle { @Override public void start(Promise<Void> startPromise) throws Exception { Router router = Router.router(vertx); // директория для сохранения загруженных файлов String uploadDir = "uploads"; // обработчик для загрузки файлов router.route("/upload").handler(BodyHandler.create().setUploadsDirectory(uploadDir)); router.post("/upload").handler(this::handleFileUpload); // обработчик для скачивания файлов router.get("/download/:fileName").handler(this::handleFileDownload); vertx.createHttpServer().requestHandler(router).listen(8888, http -> { if (http.succeeded()) { startPromise.complete(); System.out.println("HTTP server started on port 8888"); } else { startPromise.fail(http.cause()); } }); } private void handleFileUpload(RoutingContext context) { for (FileUpload fileUpload : context.fileUploads()) { System.out.println("Received file: " + fileUpload.fileName()); // логика обработки файла } context.response().setStatusCode(200).end("File uploaded"); } private void handleFileDownload(RoutingContext context) { String fileName = context.pathParam("fileName"); String fileLocation = "uploads/" + fileName; context.response().sendFile(fileLocation, result -> { if (result.failed()) { context.response().setStatusCode(404).end("File not found"); } }); } }
Создаем главный класс для запуска вертикала:
import io.vertx.core.Vertx; public class Main { public static void main(String[] args) { Vertx vertx = Vertx.vertx(); vertx.deployVerticle(new FileServerVerticle(), res -> { if (res.succeeded()) { System.out.println("FileServerVerticle deployed successfully."); } else { System.err.println("Failed to deploy FileServerVerticle: " + res.cause()); } }); } }
Запускаем сервер с помощью curl:
curl -F "file=@path/to/your/file.txt" http://localhost:8888/upload
В преддверии старта специализации Java-разработчик хочу порекомендовать вам несколько бесплатных вебинаров по следующим темам:
ссылка на оригинал статьи https://habr.com/ru/articles/794484/
Добавить комментарий