异步响应

tio-boot 的响应本身就是异步的,但是如果你想要在发送 http 响应之后在做一些事情,可以参考本节的内容

tio-boot 异步地发送 HTTP 响应

  1. 获取 ChannelContext:
ChannelContext channelContext = request.getChannelContext();

HttpRequest获取ChannelContextChannelContext包含了当前连接的状态和配置信息。

  1. 准备发送的数据:
Map<String, Object> data = new HashMap<>();
data.put("code", 1);

创建一个包含响应数据的Map,这里只放入了一个键值对。

  1. 封装为 HttpResponse:
HttpResponse httpResponse = TioControllerContext.getResponse();
httpResponse = Resps.json(httpResponse, data);

使用TioControllerContext.getResponse()获取HttpResponse对象,然后使用Resps.json方法将data封装为 JSON 格式的响应。

  1. 调用 Handler 进行解码:
TioConfig tioConfig = channelContext.getTioConfig();
AioHandler aioHandler = tioConfig.getAioHandler();
ByteBuffer writeBuffer = aioHandler.encode(httpResponse, tioConfig, channelContext);

获取AioHandler,这是一个用于处理编码和解码的处理器。调用encode方法将httpResponse编码为可以通过网络发送的字节数据。

  1. 异步写入数据到客户端:
channelContext.asynchronousSocketChannel.write(writeBuffer, request, new CompletionHandler<Integer, HttpRequest>() {

使用channelContext.asynchronousSocketChannel.write异步地将编码后的数据写入到客户端。同时传入request作为attachment,并定义了一个匿名的CompletionHandler来处理写操作的结果。

  1. 处理写操作的结果:
  • completed 方法:
public void completed(Integer result, HttpRequest attachment) {
  log.info("result:{},attachment:{}", result, attachment);
  try {
    channelContext.asynchronousSocketChannel.close();
  } catch (IOException e) {
    e.printStackTrace();
  }
}

写操作成功完成时,会调用此方法。它记录了操作结果,并尝试关闭AsynchronousSocketChannel

  • failed 方法:
public void failed(Throwable exc, HttpRequest attachment) {
  try {
    channelContext.asynchronousSocketChannel.close();
  } catch (IOException e) {
    e.printStackTrace();
  }
}

如果写操作失败,这个方法会被调用。它同样尝试关闭AsynchronousSocketChannel

这个方法展示了如何异步地发送 HTTP 响应,并通过CompletionHandler处理操作的结果。通过这种方式,服务器可以在不阻塞当前线程的情况下处理多个连接和请求,从而提高性能。

AsynchronousSocketChannel 的 write 方法

AsynchronousSocketChannelwrite方法是一个用于异步写操作的方法。它接收三个参数,这些参数的含义如下:

  1. ByteBuffer src: 这是一个ByteBuffer对象,它包含了要写入通道的数据。当调用write方法时,数据会从这个缓冲区写入到通道中。ByteBuffer 提供了一个接口,通过这个接口可以以高效的方式读写多个字节,常用于 NIO 的数据输入输出。

  2. A attachment: attachment 参数是传递到 CompletionHandler 中的。当你创建一个异步操作时,你可以提供一个 attachment 对象。这个对象可以是任何类型的数据,它会被传递给 CompletionHandler 的 completed 或者 failed 方法。

  3. CompletionHandler<Integer,? super A> handler: 这是一个CompletionHandler接口的实现。当异步操作完成时,无论是成功还是失败,都会调用这个处理器的方法。处理器有两个方法:completedfailed。如果操作成功完成,会调用completed方法,并且会将写入的字节数作为参数传递给它。如果操作失败,会调用failed方法,并且会将导致失败的异常作为参数传递给它。这个处理器的泛型参数<Integer,? super A>表示它接受一个Integer类型的结果(这里是写入的字节数),以及一个A类型或其父类型的附件对象。

示例

示例代码
package com.litongjava.tio.web.hello.controller;

import java.io.IOException;
import java.nio.ByteBuffer;
import java.nio.channels.CompletionHandler;
import java.util.HashMap;
import java.util.Map;

import com.litongjava.tio.boot.http.TioControllerContext;
import com.litongjava.tio.core.ChannelContext;
import com.litongjava.tio.core.TioConfig;
import com.litongjava.tio.core.intf.AioHandler;
import com.litongjava.tio.http.common.HttpRequest;
import com.litongjava.tio.http.common.HttpResponse;
import com.litongjava.tio.http.server.annotation.RequestPath;
import com.litongjava.tio.http.server.util.Resps;

import lombok.extern.slf4j.Slf4j;

@RequestPath("/async")
@Slf4j
public class AsyncController {

  public Map<String, Object> index(HttpRequest request) {
    ChannelContext channelContext = request.getChannelContext();
    TioConfig tioConfig = channelContext.getTioConfig();
    AioHandler aioHandler = tioConfig.getAioHandler();
    String token = channelContext.getToken();

    Map<String, Object> data = new HashMap<>();
    data.put("channelContext", channelContext.toString());
    data.put("tioConfig", tioConfig.toString());
    data.put("userId", channelContext.userid);
    data.put("token", token);
    data.put("asynchronousSocketChannel", channelContext.asynchronousSocketChannel.toString());
    data.put("aioHandler", aioHandler.toString());
    log.info("data:{}", data);
    return data;
  }

  public String writeHttpResponse(HttpRequest request) {
    ChannelContext channelContext = request.getChannelContext();
    // 准备发送的数据
    Map<String, Object> data = new HashMap<>();
    data.put("code", 1);

    // 封装为httpResponse
    HttpResponse httpResponse = TioControllerContext.getResponse();
    httpResponse = Resps.json(httpResponse, data);

    // 调用Handler进行解码
    TioConfig tioConfig = channelContext.getTioConfig();
    AioHandler aioHandler = tioConfig.getAioHandler();
    ByteBuffer writeBuffer = aioHandler.encode(httpResponse, tioConfig, channelContext);

    // 异步写入数据到客户端
    channelContext.asynchronousSocketChannel.write(writeBuffer);

    return null;
  }

  public String writeHttpResponseWithCompletionHandler(HttpRequest request) {
    ChannelContext channelContext = request.getChannelContext();
    // 准备发送的数据
    Map<String, Object> data = new HashMap<>();
    data.put("code", 1);

    // 封装为httpResponse
    HttpResponse httpResponse = TioControllerContext.getResponse();
    httpResponse = Resps.json(httpResponse, data);

    // 调用Handler进行解码
    TioConfig tioConfig = channelContext.getTioConfig();
    AioHandler aioHandler = tioConfig.getAioHandler();
    ByteBuffer writeBuffer = aioHandler.encode(httpResponse, tioConfig, channelContext);

    // 异步写入数据到客户端
    channelContext.asynchronousSocketChannel.write(writeBuffer, request, new CompletionHandler<Integer, HttpRequest>() {

      @Override
      public void completed(Integer result, HttpRequest attachment) {
        // 进行其他操作
        log.info("result:{},attachment:{}", result, attachment);

        // 手动关闭连接
        try {
          channelContext.asynchronousSocketChannel.close();
        } catch (IOException e) {
          e.printStackTrace();
        }
      }

      @Override
      public void failed(Throwable exc, HttpRequest attachment) {
        try {
          channelContext.asynchronousSocketChannel.close();
        } catch (IOException e) {
          e.printStackTrace();
        }
      }

    });

    return null;
  }
}

访问测试

  • http://localhost/async/index
{
  "aioHandler": "com.litongjava.tio.boot.server.TioBootServerHandler@7a041eef",
  "tioConfig": "ServerTioConfig [name=tio-boot]",
  "channelContext": "server:0.0.0.0:80, client:0:0:0:0:0:0:0:1:4986",
  "asynchronousSocketChannel": "sun.nio.ch.WindowsAsynchronousSocketChannelImpl[connected local=/0:0:0:0:0:0:0:1:80 remote=/0:0:0:0:0:0:0:1:4986]"
}
  • http://localhost/async/asynchronousSocketChannel
{"code":1}
  • http://localhost/async/writeHttpResponseWithCompletionHandler
{"code":1}