Перейти к основному содержанию

Примеры

note

Чтобы запустить примеры, необходимо клонировать ActiveJ с GitHub:

git clone https://github.com/activej/activej

И импортируйте его как проект Maven. Посмотрите тег v5.0-beta2. Перед запуском примеров выполните сборку проекта. Эти примеры расположены по адресу activej/examples/core/http.

Простой сервер "Hello World"#

HelloWorldExample использует AsyncHttpServer класс модуля HTTP. Это неблокирующий сервер, который работает в режиме eventloop:


public static void main(String[] args) throws IOException {  Eventloop eventloop = Eventloop.create();  AsyncHttpServer server = AsyncHttpServer.create(eventloop,      request -> HttpResponse.ok200()          .withPlainText("Hello world!"))      .withListenPort(8080);
  server.listen();
  System.out.println("Server is running");  System.out.println("You can connect from browser by visiting 'http://localhost:8080/'");
  eventloop.run();}

Этот сервер запускается в предоставленном eventloop и ожидает соединений на порту 8080. Когда сервер получает запрос, он отправляет обратно Promise ответ на приветствие.

note

Чтобы добавить поддержку HTTPS в AsyncHttpServer , необходимо вызвать метод withSslListenAddress или withSslListenAddresses и передать SSLContext, Executor, а также порт или адрес сервера, который будет прослушиваться.

Чтобы проверить, как работает пример, откройте ваш любимый браузер и перейдите по адресу localhost:8080. Полный текст примера смотрите на GitHub.

Сервер "Hello World" с предустановленным Launcher#

Launchers управляет жизненным циклом приложений и позволяет создавать приложения простым способом:

public final class HttpHelloWorldExample extends HttpServerLauncher {  @Provides  AsyncServlet servlet() {    return request -> HttpResponse.ok200().withPlainText("Hello World");  }
  public static void main(String[] args) throws Exception {    Launcher launcher = new HttpHelloWorldExample();    launcher.launch(args);  }}

Все, что вам нужно сделать, это предоставить сервлет, который обрабатывает запросы, и запустить приложение. HttpServerLauncher позаботится обо всем остальном. See full example on GitHub

Пользовательский сервер#

С помощью Launcher вы можете легко создавать HTTP-серверы с нуля. В этом примере мы создаем простой сервер, который посылает приветствие:

public final class CustomHttpServerExample extends Launcher {  private static final int PORT = 8080;
  @Provides  Eventloop eventloop() {    return Eventloop.create();  }
  @Provides  AsyncServlet servlet() {    return request -> HttpResponse.ok200()        .withPlainText("Hello from HTTP server");  }
  @Provides  @Eager  AsyncHttpServer server(Eventloop eventloop, AsyncServlet servlet) {    return AsyncHttpServer.create(eventloop, servlet).withListenPort(PORT);  }
  @Override  protected Module getModule() {    return ServiceGraphModule.create();  }
  @Override  protected void run() throws Exception {    logger.info("HTTP Server is now available at http://localhost:" + PORT);    awaitShutdown();  }
  public static void main(String[] args) throws Exception {    Launcher launcher = new CustomHttpServerExample();    launcher.launch(args);  }}

Во-первых, мы предоставляем eventloop, сервлет и сам асинхронный сервер. Затем мы переопределяем метод getModule для предоставления нашему серверу конфигураций и ServiceGraphModule для построения графа зависимостей сервисов. Наконец, мы переопределяем Launcher основной метод run() и затем определяем основной метод примера. Чтобы проверить, как работает пример, откройте ваш любимый браузер и перейдите по адресу localhost:8080. See full example on GitHub

Пример многопоточного сервера#

В этом примере мы используем предопределенный MultithreadedHttpServerLauncher для создания многопоточного HTTP-сервера. По умолчанию будет 4 рабочих сервлета с workerIds. Каждый из них отправляет обратно приветствие и номер рабочего , который обслуживал соединение:

public final class MultithreadedHttpServerExample extends MultithreadedHttpServerLauncher {  @Provides  @Worker  AsyncServlet servlet(@WorkerId int workerId) {    return request -> HttpResponse.ok200()        .withPlainText("Hello from worker server #" + workerId + "\n");  }
  public static void main(String[] args) throws Exception {    MultithreadedHttpServerExample example = new MultithreadedHttpServerExample();    example.launch(args);  }}

Чтобы проверить, как работает пример, откройте ваш любимый браузер и перейдите по адресу localhost:8080. See full example on GitHub

Пример параметров запроса#

В данном примере представлены запросы с параметрами, которые получены методами getPostParameters и getQueryParameter.

@ProvidesAsyncServlet servlet(Executor executor) {  return RoutingServlet.create()      .map(POST, "/hello", request -> request.loadBody()          .map($ -> {            String name = request.getPostParameters().get("name");            return HttpResponse.ok200()                .withHtml("<h1><center>Hello from POST, " + name + "!</center></h1>");          }))      .map(GET, "/hello", request -> {        String name = request.getQueryParameter("name");        return HttpResponse.ok200()            .withHtml("<h1><center>Hello from GET, " + name + "!</center></h1>");      })      .map("/*", StaticServlet.ofClassPath(executor, RESOURCE_DIR)          .withIndexHtml());}

Чтобы проверить, как работает пример, откройте ваш любимый браузер и перейдите по адресу localhost:8080. See full example on GitHub

Пример статического сервлета#

Показано, как настроить и использовать StaticServlet для создания сервлетов с некоторым статическим содержимым, в нашем случае он будет получать содержимое из каталога static/site .

@ProvidesAsyncServlet servlet(Executor executor) {  return StaticServlet.ofClassPath(executor, "static/site")      .withIndexHtml();}

Чтобы проверить, как работает пример, откройте ваш любимый браузер и перейдите по адресу localhost:8080. See full example on GitHub

Пример сервлета маршрутизации#

Представляет, как настроить дерево маршрутизации сервлетов. Этот процесс напоминает экспресс-подход. Чтобы добавить маршрут к RoutingServlet, следует использовать метод map:

.map(GET, "/", request ->    HttpResponse.ok200()        .withHtml("<h1>Go to some pages</h1>" +            "<a href=\"/path1\"> Path 1 </a><br>" +            "<a href=\"/path2\"> Path 2 </a><br>" +            "<a href=\"/user/0\"> Data for user with ID 0 </a><br>" +            "<br>" +            "<a href=\"/path3\"> Non existent </a>"))
  • метод (необязательно) - один из методов HTTP (GET, POSTи т.д.).
  • путь - путь на сервере.
  • servlet определяет логику обработки запроса. Все дерево сервлетов будет выглядеть следующим образом:
@ProvidesAsyncServlet servlet() {  return RoutingServlet.create()      //[START REGION_2]      .map(GET, "/", request ->          HttpResponse.ok200()              .withHtml("<h1>Go to some pages</h1>" +                  "<a href=\"/path1\"> Path 1 </a><br>" +                  "<a href=\"/path2\"> Path 2 </a><br>" +                  "<a href=\"/user/0\"> Data for user with ID 0 </a><br>" +                  "<br>" +                  "<a href=\"/path3\"> Non existent </a>"))      //[END REGION_2]      .map(GET, "/path1", request ->          HttpResponse.ok200()              .withHtml("<h1>Hello from the first path!</h1>" +                  "<a href=\"/\">Go home</a>"))      .map(GET, "/path2", request ->          HttpResponse.ok200()              .withHtml("<h1>Hello from the second path!</h1>" +                  "<a href=\"/\">Go home</a>"))
      //[START REGION_3]      .map(GET, "/user/:user_id", request -> {        String userId = request.getPathParameter("user_id");        return HttpResponse.ok200()            .withHtml("<h1>You have requested data for user with ID: " + userId + "</h1>" +                "<h3>Try changing URL after <i>'.../user/'</i> to get data for users with different IDs</h3>");      })      //[END REGION_3]
      //[START REGION_4]      .map("/*", request ->          HttpResponse.ofCode(404)              .withHtml("<h1>404</h1><p>Path '" + request.getRelativePath() + "' not found</p>" +                  "<a href=\"/\">Go home</a>"));      //[END REGION_4]}

Вы можете сопоставить параметры пути с помощью синтаксиса /:param :

.map(GET, "/user/:user_id", request -> {  String userId = request.getPathParameter("user_id");  return HttpResponse.ok200()      .withHtml("<h1>You have requested data for user with ID: " + userId + "</h1>" +          "<h3>Try changing URL after <i>'.../user/'</i> to get data for users with different IDs</h3>");})

Параметры пути можно получить, вызвав метод HttpRequest#getPathParameter и передав имя параметра пути. Вы также можете использовать подстановочный маршрут *:

.map("/*", request ->    HttpResponse.ofCode(404)        .withHtml("<h1>404</h1><p>Path '" + request.getRelativePath() + "' not found</p>" +            "<a href=\"/\">Go home</a>"));

* гласит, что какой бы сегмент следующего пути ни был получен, он будет обработан этим сервлетом. Чтобы проверить, как работает пример, откройте ваш любимый браузер и перейдите по адресу localhost:8080. See full example on GitHub

Примеры динамической маршрутизации#

Несколько примеров, демонстрирующих, как маршрутизировать HTTP-запросы по некоторым динамическим значениям, а не по заранее определенным путям:

Маршрутизирующий сервлет Multibinder#

Используя Dependency Injection , вы можете предоставить RoutingServletв различных модулях. Такое связывание приведет к тому, что DI выбросит исключение из-за конфликтующих ключей RoutingServlet.class. Однако вы можете поручить DI разрешить конфликты, объединив RoutingServlets в один RoutingServlet , который содержит все маршруты, указанные в других сервлетах маршрутизации. В примере мы объявляем несколько модулей, которые обеспечивают RoutingServlet различными маршрутами:

private static final class ModuleA extends AbstractModule {  @Provides  RoutingServlet servlet() {    return RoutingServlet.create()        .map(GET, "/a", request -> HttpResponse.ok200().withPlainText("Hello from '/a' path\n"))        .map(GET, "/b", request -> HttpResponse.ok200().withPlainText("Hello from '/b' path\n"))        .map(GET, "/", request -> HttpResponse.ok200().withPlainText("Hello from '/' path\n"));  }}
private static final class ModuleB extends AbstractModule {  @Provides  RoutingServlet servlet() {    return RoutingServlet.create()        .map(GET, "/a/b", request -> HttpResponse.ok200().withPlainText("Hello from '/a/b' path\n"))        .map(GET, "/b/a", request -> HttpResponse.ok200().withPlainText("Hello from '/b/a' path\n"))        .map(GET, "/d", request -> HttpResponse.ok200().withPlainText("Hello from '/d' path\n"));  }}
private static final class ModuleC extends AbstractModule {  @Provides  RoutingServlet servlet() {    return RoutingServlet.create()        .map(GET, "/a/c", request -> HttpResponse.ok200().withPlainText("Hello from '/a/c' path\n"))        .map(GET, "/b/c", request -> HttpResponse.ok200().withPlainText("Hello from '/b/c' path\n"))        .map(POST, "/d", request -> HttpResponse.ok200().withPlainText("Hello from POST '/d' path\n"));  }}

Далее мы определяем Multibinder , который объединяет конфликтующие сервлеты:

public static final Multibinder<RoutingServlet> SERVLET_MULTIBINDER = Multibinders.ofBinaryOperator((servlet1, servlet2) ->    servlet1.merge(servlet2));

Наконец, мы переопределяем метод HttpServerLauncher#getBusinesLogicModule для предоставления комбинированного DI модуля , который содержит ModuleA, ModuleB, ModuleC , а также установленный Multibinder для DI ключа RoutingServlet.class. Если мы запустим пример, то увидим, что конфликтов нет, так как конфликтующие сервлеты были успешно объединены вместе. Мы можем открыть веб-браузер и посетить любой из указанных маршрутов, чтобы убедиться, что маршрутизация работает правильно:

  • /
  • /a
  • /a/b
  • /a/c
  • /b
  • /б/а
  • /b/c
  • /d
    warning

    Если вы попытаетесь объединить RoutingServlets, которые имеют идентичные маршруты, то возникнет исключение

Пример блокирующего сервлета#

Показано, как создать новый поток для обработки некоторых сложных операций над BlockingServlet

@ProvidesAsyncServlet servlet(Executor executor) {  return RoutingServlet.create()      .map("/", request -> HttpResponse.ok200()          .withHtml("<a href='hardWork'>Do hard work</a>"))      .map("/hardWork", AsyncServlet.ofBlocking(executor, request -> {        Thread.sleep(2000); //Hard work        return HttpResponse.ok200()            .withHtml("Hard work is done");      }));}

Чтобы проверить, как работает пример, откройте ваш любимый браузер и перейдите по адресу localhost:8080. See full example on GitHub

Пример загрузки файлов#

В этом примере пользователь загружает файл из локального хранилища на сервер:

@ProvidesAsyncServlet servlet(Executor executor) {  return RoutingServlet.create()      .map("/", request -> HttpResponse.ok200()          .withHtml("<a href='hardWork'>Do hard work</a>"))      .map("/hardWork", AsyncServlet.ofBlocking(executor, request -> {        Thread.sleep(2000); //Hard work        return HttpResponse.ok200()            .withHtml("Hard work is done");      }));}

Чтобы проверить, как работает пример, откройте ваш любимый браузер и перейдите по адресу localhost:8080. See full example on GitHub

Пример клиента#

В данном примере показано, как создать HTTP-клиент, используя Launcher, предварительно заданные AsyncHttpClient, и AsyncDnsClient (сопоставляет заданные домены с соответствующими IP-адресами):

@ProvidesAsyncServlet servlet(Executor executor) {  return RoutingServlet.create()      .map("/", request -> HttpResponse.ok200()          .withHtml("<a href='hardWork'>Do hard work</a>"))      .map("/hardWork", AsyncServlet.ofBlocking(executor, request -> {        Thread.sleep(2000); //Hard work        return HttpResponse.ok200()            .withHtml("Hard work is done");      }));}
note

Чтобы добавить поддержку HTTPS в AsyncHttpClient , необходимо вызвать метод withSslEnabled и передать SSLContext и Executor.

Переопределите метод Launcher getModule для предоставления необходимых конфигураций и графа зависимостей ServiceGraph :

@Overrideprotected Module getModule() {  return combine(      ServiceGraphModule.create(),      ConfigModule.create()          .withEffectiveConfigLogger());}
@ProvidesConfig config() {  return Config.create()      .with("dns.address", "8.8.8.8")      .with("dns.timeout", "5 seconds")      .overrideWith(Config.ofSystemProperties("config"));}

Поскольку наш клиент расширяет Launcher, он переопределяет метод run , который определяет основную функциональность. В нашем случае он отправляет запрос, ждет ответа сервера (успешного или неудачного) и затем обрабатывает его:

@Overrideprotected void run() throws ExecutionException, InterruptedException {  String url = args.length != 0 ? args[0] : "http://127.0.0.1:8080/";  System.out.println("\nHTTP request: " + url);  CompletableFuture<String> future = eventloop.submit(() ->      httpClient.request(HttpRequest.get(url))          .then(response -> response.loadBody())          .map(body -> body.getString(UTF_8))  );  System.out.println("HTTP response: " + future.get());  System.out.println();}

eventloop.submit передает потоку eventloop отправку запроса и получение ответа. Итак, наш основной поток будет ждать, пока будущее в потоке eventloop не вернет результат, и только после этого ответ будет выведен на печать. Чтобы проверить работу клиента, запустите Simple "Hello World" Server или Custom HTTP server , а затем запустите ClientExample. See full example on GitHub

Пример обработки многокомпонентных данных#

POST-запросы иногда могут быть закодированы как Multipart/form-data. Такие запросы могут содержать множество полей и файлов. Для обработки запроса, содержащего многокомпонентные данные, можно использовать метод HttpRequest#handleMultipart . В этот метод необходимо передать экземпляр MultipartDataHandler . В классе MultipartDataHandler есть несколько общих обработчиков. Вы можете использовать их для сбора полей на карту, отправки файла на какой-либо канал ChannelConsumer<ByteBuf>, и т.д. Или вы можете написать свой собственный MultipartDataHandler , если этого недостаточно. В этом примере мы будем собирать поля в карту и загружать полученные файлы в некоторую директорию. После этого мы запишем в журнал собранные поля и количество загруженных файлов:

@ProvidesAsyncServlet servlet() {  return RoutingServlet.create()      .map(POST, "/handleMultipart", request -> {        Map<String, String> fields = new HashMap<>();
        return request.handleMultipart(MultipartDataHandler.fieldsToMap(fields, this::upload))            .map($ -> {              logger.info("Received fields: " + fields);              logger.info("Uploaded " + fileUploadsCount + " files total");              return HttpResponse.ok200();            });      });}

Для загрузки полученного файла в файловую систему мы будем использовать ChannelFileWritter:

@NotNullprivate Promise<ChannelConsumer<ByteBuf>> upload(String filename) {  logger.info("Uploading file '{}' to {}", filename, path);  return ChannelFileWriter.open(executor, path.resolve(filename))      .map(writer -> writer.withAcknowledgement(ack ->          ack.whenResult(() -> {            logger.info("Upload of file '{}' finished", filename);            fileUploadsCount++;          })));}

Запрос Multipart/form-data подделан вручную и содержит несколько полей и файлов. После выполнения примера вы должны увидеть аналогичный вывод журнала:

Uploading file 'data.txt' to /tmp/multipart-data-files4909047508332989372Upload of file 'data.txt' finishedUploading file 'key.txt' to /tmp/multipart-data-files4909047508332989372Upload of file 'key.txt' finishedReceived fields: {last name=Johnson, first name=Alice, id=12345}Uploaded 2 files total

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

See full example on GitHub

WebSocket Pong Server#

Давайте создадим WebSocket-сервер "Pong". Для этого нам нужно предоставить RoutingServlet и использовать метод mapWebSocket для отображения Consumer из WebSocket в качестве сервлета по пути /. Наш сервер будет просто принимать сообщения, распечатывать их и передавать обратно сообщение "Pong".

@ProvidesAsyncServlet servlet() {  return RoutingServlet.create()      .mapWebSocket("/", webSocket -> webSocket.readMessage()          .whenResult(message -> System.out.println("Received:" + message.getText()))          .then(() -> webSocket.writeMessage(Message.text("Pong")))          .whenComplete(webSocket::close));}

See full example on GitHub

Клиент WebSocket Ping#

Теперь создадим клиента, который будет посылать серверу сообщение "Ping" через WebSocket-соединение.

@Overrideprotected void run() throws ExecutionException, InterruptedException {  String url = args.length != 0 ? args[0] : "ws://127.0.0.1:8080/";  System.out.println("\nWeb Socket request: " + url);  CompletableFuture<?> future = eventloop.submit(() -> {    System.out.println("Sending: Ping");    return httpClient.webSocketRequest(HttpRequest.get(url))        .then(webSocket -> webSocket.writeMessage(Message.text("Ping"))            .then(webSocket::readMessage)            .whenResult(message -> System.out.println("Received: " + message.getText()))            .whenComplete(webSocket::close));  });  future.get();}

Сначала мы создаем поставщика и переопределяем его метод get через лямбду. Здесь мы вызываем AsyncHttpClient.webSocketRequest , который посылает запрос и возвращает Promise of a WebSocket. Затем мы создаем функцию `` , которая отправляет сообщение "Ping" и получает ответ от сервера.

See full example on GitHub