跳到主要内容

实例

note

要运行这些例子,你需要从GitHub上克隆ActiveJ。

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

并将其作为一个Maven项目导入。 查看标签 v5.0-beta2。 在运行这些例子之前,先建立项目。 这些例子位于 activej/examples/core/http

简单的 "Hello World "服务器#

HelloWorldExample 使用 AsyncHttpServer HTTP模块的类。 它是一个非阻塞式的服务器,在一个事件循环中工作。


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();}

该服务器在提供的事件环中运行,并等待端口 8080的连接。 当服务器收到一个请求时,它会发回一个 Promise 的问候回应。

note

要在 AsyncHttpServer 中添加对HTTPS的支持,你需要调用 withSslListenAddresswithSslListenAddresses 方法,并传递 SSLContext, Executor, 和一个用于监听服务器的端口或地址。

要检查这个例子是如何工作的,打开你喜欢的浏览器,进入 localhost:8080在GitHub上看到完整的例子

带有预定义启动器的 "Hello World "服务器#

Launcher,管理应用程序的生命周期,并允许以一种简单的方式创建应用程序。

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);  }}

你所需要做的就是提供一个处理请求的Servlet,并启动应用程序。 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);  }}

首先,我们提供了一个事件循环,一个Servlet,以及一个异步服务器本身。 然后,我们覆盖 getModule 方法,为我们的服务器提供 配置和 ServiceGraphModule ,以构建服务的依赖图。 最后,我们覆盖 Launcher 主方法 run() ,然后定义 示例的主 方法。 要检查这个例子是如何工作的,打开你喜欢的浏览器,进入 localhost:8080 See full example on GitHub

多线程服务器实例#

在这个例子中,我们使用预定义的 MultithreadedHttpServerLauncher 来创建一个多线程的HTTP服务器。 根据 ,默认情况下,将有4个worker servlets, 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

请求参数示例#

这个例子代表了带有参数的请求,这些参数是通过方法 getPostParametersgetQueryParameter 收到的。

@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

静态Servlet实例#

展示了如何设置和利用 StaticServlet ,以创建具有一些静态内容的servlets,在我们的例子中,它将从 static/site 目录中获得 内容。

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

要检查这个例子是如何工作的,打开你喜欢的浏览器,进入 localhost:8080 See full example on GitHub

Routing Servlet示例#

代表了如何设置servlet路由树。 这个过程类似于快递方式。 要添加一个路由到一个 RoutingServlet,你应该使用方法 映射

.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方法之一(GETPOST,等等)。
  • path 是服务器上的路径。
  • servlet 定义了请求处理的逻辑。 整个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>"));

* 说明无论收到哪一个下一个路径段,都将由这个Servlet来处理。 要检查这个例子是如何工作的,打开你喜欢的浏览器,进入 localhost:8080 See full example on GitHub

动态路由的例子#

有几个例子演示了如何通过一些动态值而不是预定义路径来路由HTTP请求。

Routing Servlet Multibinder#

使用 依赖性注入 ,你可以在不同的模块中提供 RoutingServlets。 这样的绑定会导致 DI 抛出一个冲突键的异常 RoutingServlet.class。 然而,你可以指示 DI ,通过将 RoutingServlets合并成一个单一的 RoutingServlet ,其中包含其他路由servlet中指定的所有路由来解决冲突。 在这个例子中,我们声明了几个模块,为 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 ,它可以合并相互冲突的Servlet。

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

最后,我们覆盖 HttpServerLauncher#getBusinesLogicModule 方法,以提供一个组合的DI 模块 ,其中包含 ModuleAModuleBModuleC ,以及一个安装了 Multibinder 的DI Key RoutingServlet.class。 如果我们启动这个例子,我们可以看到没有冲突,因为冲突的Servlet被成功地合并在一起。 我们可能会打开一个网络浏览器,访问任何一个指定的路线,以确保路由的正常运行。

  • /
  • /a
  • /a/b
  • /a/c
  • /b
  • /b/a
  • /b/c
  • /d
    warning

    如果你试图合并映射有相同路由的RoutingServlets,就会产生一个异常

Routing Servlet示例#

展示了如何创建一个新的线程来处理一些复杂的操作。 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

客户实例#

这个例子显示了如何使用 Launcher、预定义的 AsyncHttpClient、 和 AsyncDnsClient (将给定的域名映射到相应的IP地址)创建一个HTTP客户端。

@ProvidesAsyncHttpClient client(Eventloop eventloop, AsyncDnsClient dnsClient) {  return AsyncHttpClient.create(eventloop)      .withDnsClient(dnsClient);}
@ProvidesAsyncDnsClient dnsClient(Eventloop eventloop, Config config) {  return RemoteAsyncDnsClient.create(eventloop)      .withDnsServerAddress(config.get(ofInetAddress(), "dns.address"))      .withTimeout(config.get(ofDuration(), "dns.timeout"));}
note

为了给 AsyncHttpClient ,添加对HTTPS的支持,你需要调用 withSslEnabled 方法,并传递 SSLContextExecutor

覆盖Launcher getModule 方法,以提供所需的 configsServiceGraph dependency graph。

@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线程。 因此,我们的主线程将等待,直到 future 在事件循环线程中返回一个结果,然后才将响应打印出来。 要检查客户端是如何工作的,启动 简单的 "Hello World "服务器自定义HTTP服务器 ,然后运行 ClientExample See full example on GitHub

Multipart Data Handling Example#

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 请求是手动伪造的,包含几个字段和文件。 运行该例子后,你 ,应该看到类似的日志输出。

上传文件'data.txt'到/tmp/multipart-data-files4909047508332989372上传文件'data.txt'完成上传文件'key.txt'到/tmp/multipart-data-files4909047508332989372上传文件'key.txt'完成接收到字段。{last name=Johnson, first name=Alice, id=12345}共上传了2个文件

你可以从日志输出中检查该目录,以确保文件被上传。

See full example on GitHub

WebSocket Pong服务器#

让我们创建一个 "Pong "WebSocket服务器。 为此,我们需要 ,提供一个 RoutingServlet ,并使用 mapWebSocket 方法,将 WebSocket 的一个 Consumer 映射为 /路径上的一个Servlet。 我们的服务器将简单地接受信息,将其打印出来,并流回一个 "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客户端#

现在让我们创建一个客户端,它将通过WebSocket连接向服务器发送 "Ping "消息。

@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();}

首先,我们创建一个供应商,并通过lambda重写其 get 方法。 这里我们调用 AsyncHttpClient.webSocketRequest ,它发送一个请求并返回一个Promise的 WebSocket. 然后我们创建一个 函数 ,发送一个 "Ping "消息,并从服务器接收一个响应。

See full example on GitHub