跳到主要内容

实例

note

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

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

并将其作为一个Maven项目导入。 查看标签 v5.4.3。 在运行这些例子之前,先建立项目。 这些例子位于 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 {} files total", fileUploadsCount);              return HttpResponse.ok200();            });      });}

为了将收到的文件上传到文件系统,我们将使用 ChannelFileWritter

private @NotNull 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