Skip to content

DecodeExceptionHandler ​

DecodeExceptionHandler 是 tio-boot 的一个核心组件,用于处理消息解码失败的情况。这类失败通常表明可能存在网络攻击。本文档将详细介绍如何配置和实现自定义的 DecodeExceptionHandler,以提升您的 tio-boot 服务器的安全性和稳定性。

目录 ​

  1. 服务器启动前的配置
  2. 实现自定义的 DecodeExceptionHandler
  3. 测试 DecodeExceptionHandler
  4. 增强 Handler:日志记录、数据库存储和报警
  5. 完整代码示例
  6. 详细说明

服务器启动前的配置 ​

在初始化服务器之前,需设置自定义的 DecodeExceptionHandler。此配置确保服务器启动时,任何解码异常都能被适当地处理。

java
package nexus.io.tio.web.hello.config;

import nexus.io.annotation.BeforeStartConfiguration;
import nexus.io.annotation.Initialization;
import nexus.io.tio.boot.server.TioBootServer;
import nexus.io.tio.web.hello.handler.MyDecodeExceptionHandler;

@AopBeforeStartConfiguration
public class BeforeServerConfig {

  @Initialization
  public void config() {
    MyDecodeExceptionHandler myDecodeExceptionHandler = new MyDecodeExceptionHandler();
    TioBootServer.me().setDecodeExceptionHandler(myDecodeExceptionHandler);
  }
}

说明: ​

  • 包声明:组织类文件在项目结构中的位置。
  • 导入:引入必要的类和注解。
  • 注解:
    • @AopBeforeStartConfiguration:标识此配置应在服务器启动前应用。
    • @Initialization:标记在初始化阶段执行的方法。
  • 配置方法:
    • 实例化 MyDecodeExceptionHandler。
    • 将其设置为 TioBootServer 的 DecodeExceptionHandler。

实现自定义的 DecodeExceptionHandler ​

自定义的处理器通过关闭受影响的通道并记录相关信息来处理解码异常。

java
package nexus.io.tio.web.hello.handler;

import java.nio.ByteBuffer;

import nexus.io.tio.boot.decode.TioDecodeExceptionHandler;
import nexus.io.tio.core.ChannelContext;
import nexus.io.tio.core.Tio;
import nexus.io.tio.core.exception.TioDecodeException;
import nexus.io.tio.http.common.HttpConfig;

public class MyDecodeExceptionHandler implements TioDecodeExceptionHandler {

  @Override
  public void handle(ByteBuffer buffer, ChannelContext channelContext, HttpConfig httpConfig, TioDecodeException e) {
    Tio.close(channelContext, "MyDecodeExceptionHandler");

    // 创建缓冲区的只读副本
    ByteBuffer readOnlyBuffer = buffer.asReadOnlyBuffer();
    readOnlyBuffer.position(0); // 重置位置,从头开始读取

    try {
      if (readOnlyBuffer.hasRemaining()) {
        byte[] bytes = new byte[readOnlyBuffer.remaining()];
        readOnlyBuffer.get(bytes);
        System.out.println("Buffer content: " + new String(bytes));
      } else {
        System.out.println("Buffer is empty.");
      }
      System.out.println("TioDecodeException: " + e.toString());
    } catch (Exception ex) {
      System.err.println("读取缓冲区时出错: " + ex.getMessage());
    }
  }
}

说明: ​

  • 类声明:实现 TioDecodeExceptionHandler 接口,定义解码失败时的自定义行为。
  • handle 方法:
    • 关闭通道:确保断开有问题的连接,防止潜在威胁。
    • 缓冲区处理:
      • 创建只读缓冲区副本,以安全访问数据而不修改原始缓冲区。
      • 重置缓冲区的位置,从头开始读取。
      • 尝试提取并打印缓冲区内容以便调试。
    • 异常日志记录:记录解码异常的详细信息。

测试 DecodeExceptionHandler ​

为了验证处理器的功能,发送可能导致解码失败的测试请求,例如发送格式错误的查询参数。

测试请求: ​

http://localhost/?1==1

预期输出: ​

Buffer content: GET /?1==1 HTTP/1.1
Host: localhost
Connection: keep-alive
sec-ch-ua: "Google Chrome";v="131", "Chromium";v="131", "Not_A Brand";v="24"
sec-ch-ua-mobile: ?0
sec-ch-ua-platform: "Windows"
Upgrade-Insecure-Requests: 1
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/131.0.0.0 Safari/537.36
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7
Sec-Fetch-Site: none
Sec-Fetch-Mode: navigate
Sec-Fetch-User: ?1
Sec-Fetch-Dest: document
Accept-Encoding: gzip, deflate, br, zstd
Accept-Language: en-US,en;q=0.9,zh-CN;q=0.8,zh;q=0.7
Cookie: xxx

TioDecodeException: nexus.io.tio.core.exception.TioDecodeException: 1==1 contain multi=

说明: ​

  • 格式错误的查询参数:查询参数 ?1==1 故意格式错误,以触发解码异常。
  • 处理器响应:MyDecodeExceptionHandler 捕获异常,记录缓冲区内容并输出异常详情。

增强 Handler:日志记录、数据库存储和报警 ​

为了提升 DecodeExceptionHandler 的健壮性,建议记录异常详情、将其存储到数据库中以供审计,并设置报警机制以便及时通知相关人员。

增强后的 Handler 实现: ​

java
import java.nio.ByteBuffer;
import java.util.Date;

import nexus.io.constatns.ServerConfigKeys;
import nexus.io.db.activerecord.Db;
import nexus.io.db.activerecord.Row;
import nexus.io.tio.boot.decode.TioDecodeExceptionHandler;
import nexus.io.tio.core.ChannelContext;
import nexus.io.tio.core.Tio;
import nexus.io.tio.core.exception.TioDecodeException;
import nexus.io.tio.http.common.HttpConfig;
import nexus.io.tio.utils.environment.EnvUtils;
import nexus.io.tio.utils.network.IpUtils;
import nexus.io.tio.utils.notification.NotifactionWarmModel;
import nexus.io.tio.utils.snowflake.SnowflakeIdUtils;

import lombok.extern.slf4j.Slf4j;

@Slf4j
public class MyDecodeExceptionHandler implements TioDecodeExceptionHandler {

  @Override
  public void handle(ByteBuffer buffer, ChannelContext channelContext, HttpConfig httpConfig, TioDecodeException e) {
    // 关闭有问题的通道
    Tio.close(channelContext, "MyDecodeExceptionHandler");

    String exceptionString = e.toString();
    log.error("发生 DecodeException: {}", exceptionString);

    // 创建缓冲区的只读副本
    ByteBuffer readOnlyBuffer = buffer.asReadOnlyBuffer();
    readOnlyBuffer.position(0); // 重置位置,从头开始读取

    StringBuilder contentBuilder = new StringBuilder();
    try {
      if (readOnlyBuffer.hasRemaining()) {
        byte[] bytes = new byte[readOnlyBuffer.remaining()];
        readOnlyBuffer.get(bytes);
        contentBuilder.append(new String(bytes));
      } else {
        log.error("缓冲区为空。");
        contentBuilder.append("缓冲区为空。");
      }
      contentBuilder.append(" 异常: ").append(exceptionString);
    } catch (Exception ex) {
      log.error("读取缓冲区时出错: {}", ex.getMessage());
    }

    // 准备报警模型
    NotifactionWarmModel model = new NotifactionWarmModel();
    String localIp = IpUtils.getLocalIp();
    model.setAppGroupName(RedBookDefaultValue.appGroupName);
    model.setAppName(EnvUtils.get(ServerConfigKeys.APP_NAME));
    model.setWarningName("MyDecodeExceptionHandler");
    model.setLevel("II");
    model.setAppEnv(EnvUtils.env());
    model.setDeviceName(localIp);
    model.setTime(ZonedDateTime.now());
    model.setContent(contentBuilder.toString());

    // 将异常详情保存到数据库
    try {
      long id = SnowflakeIdUtils.id();
      Db.save("tio_boot_admin_system_exception", Row.by("id", id).set("exception", exceptionString));
    } catch (Exception dbEx) {
      log.error("将异常保存到数据库失败: {}", dbEx.getMessage());
    }

    // 发送报警通知
    try {
      AlarmUtils.send(model);
    } catch (Exception alarmEx) {
      log.error("发送报警通知失败: {}", alarmEx.getMessage());
    }
  }
}

说明: ​

  • 日志记录:
    • 使用 Slf4j 进行结构化和基于级别的日志记录。
    • 记录异常详情以及在处理缓冲区时遇到的问题。
  • 数据库存储:
    • 使用 ActiveRecord 模式 (Db.save 和 Row.by) 将异常详情持久化到 tio_boot_admin_system_exception 表中。
    • 使用 SnowflakeIdUtils 生成每条记录的唯一标识符。
    • 优雅地处理潜在的数据库异常,通过日志记录错误信息。
  • 报警机制:
    • 构建包含异常和服务器环境相关信息的 NotifactionWarmModel。
    • 使用 AlarmUtils.send(model) 发送通知,以便管理员或监控系统及时获知异常。
    • 确保发送报警失败时不会中断处理器的执行,并记录相关错误。

完整代码示例 ​

1. 服务器启动前的配置 ​

java
package nexus.io.tio.web.hello.config;

import nexus.io.annotation.BeforeStartConfiguration;
import nexus.io.annotation.Initialization;
import nexus.io.tio.boot.server.TioBootServer;
import nexus.io.tio.web.hello.handler.MyDecodeExceptionHandler;

@AopBeforeStartConfiguration
public class BeforeServerConfig {

  @Initialization
  public void config() {
    MyDecodeExceptionHandler myDecodeExceptionHandler = new MyDecodeExceptionHandler();
    TioBootServer.me().setDecodeExceptionHandler(myDecodeExceptionHandler);
  }
}

3. 增强的自定义 DecodeExceptionHandler(包含日志记录、数据库存储和报警) ​

java
import java.nio.ByteBuffer;
import java.util.Date;

import nexus.io.constatns.ServerConfigKeys;
import nexus.io.db.activerecord.Db;
import nexus.io.db.activerecord.Row;
import nexus.io.tio.boot.decode.TioDecodeExceptionHandler;
import nexus.io.tio.core.ChannelContext;
import nexus.io.tio.core.Tio;
import nexus.io.tio.core.exception.TioDecodeException;
import nexus.io.tio.http.common.HttpConfig;
import nexus.io.tio.utils.environment.EnvUtils;
import nexus.io.tio.utils.network.IpUtils;
import nexus.io.tio.utils.notification.NotifactionWarmModel;
import nexus.io.tio.utils.snowflake.SnowflakeIdUtils;

import lombok.extern.slf4j.Slf4j;

@Slf4j
public class MyDecodeExceptionHandler implements TioDecodeExceptionHandler {

  @Override
  public void handle(ByteBuffer buffer, ChannelContext channelContext, HttpConfig httpConfig, TioDecodeException e) {
    // 关闭有问题的通道
    Tio.close(channelContext, "MyDecodeExceptionHandler");

    String exceptionString = e.toString();
    log.error("发生 DecodeException: {}", exceptionString);

    // 创建缓冲区的只读副本
    ByteBuffer readOnlyBuffer = buffer.asReadOnlyBuffer();
    readOnlyBuffer.position(0); // 重置位置,从头开始读取

    StringBuilder contentBuilder = new StringBuilder();
    try {
      if (readOnlyBuffer.hasRemaining()) {
        byte[] bytes = new byte[readOnlyBuffer.remaining()];
        readOnlyBuffer.get(bytes);
        contentBuilder.append(new String(bytes));
      } else {
        log.error("缓冲区为空。");
        contentBuilder.append("缓冲区为空。");
      }
      contentBuilder.append(" 异常: ").append(exceptionString);
    } catch (Exception ex) {
      log.error("读取缓冲区时出错: {}", ex.getMessage());
    }

    // 准备报警模型
    NotifactionWarmModel model = new NotifactionWarmModel();
    String localIp = IpUtils.getLocalIp();
    model.setAppGroupName(RedBookDefaultValue.appGroupName);
    model.setAppName(EnvUtils.get(ServerConfigKeys.APP_NAME));
    model.setWarningName("MyDecodeExceptionHandler");
    model.setLevel("II");
    model.setAppEnv(EnvUtils.env());
    model.setDeviceName(localIp);
    model.setTime(ZonedDateTime.now());
    model.setContent(contentBuilder.toString());

    // 将异常详情保存到数据库
    try {
      long id = SnowflakeIdUtils.id();
      Db.save("tio_boot_admin_system_exception", Row.by("id", id).set("exception", exceptionString));
    } catch (Exception dbEx) {
      log.error("将异常保存到数据库失败: {}", dbEx.getMessage());
    }

    // 发送报警通知
    try {
      AlarmUtils.send(model);
    } catch (Exception alarmEx) {
      log.error("发送报警通知失败: {}", alarmEx.getMessage());
    }
  }
}

详细说明 ​

1. 配置类 (BeforeServerConfig) ​

  • 目的:在服务器启动前设置自定义的 DecodeExceptionHandler。
  • 注解:
    • @AopBeforeStartConfiguration:确保配置在服务器初始化前应用。
    • @Initialization:标记执行配置的方法。
  • 方法 config():
    • 实例化 MyDecodeExceptionHandler。
    • 注册到 TioBootServer 实例中,以处理解码异常。

2. 基础 Handler (MyDecodeExceptionHandler) ​

  • 目的:通过关闭通道和记录缓冲区内容及异常详情来处理解码异常。
  • 关键步骤:
    • 关闭通道:防止有问题的连接继续通信,提升安全性。
    • 缓冲区处理:
      • 创建只读缓冲区副本以安全访问数据。
      • 重置缓冲区位置以从头读取。
      • 提取并打印缓冲区内容(若有)以供调试。
    • 异常日志记录:将异常详情输出到控制台,便于调试。

3. 增强的 Handler(包含日志记录、数据库存储和报警) ​

  • 目的:在基础功能上增加日志记录、将异常详情持久化到数据库以及发送报警通知,以提升处理器的全面性和可靠性。
  • 关键增强:
    • 日志记录:
      • 使用 Slf4j 进行结构化和基于级别的日志记录。
      • 记录异常详情以及处理缓冲区时的任何问题。
    • 数据库存储:
      • 使用 ActiveRecord 模式 (Db.save 和 Row.by) 将异常详情保存到 tio_boot_admin_system_exception 表中。
      • 使用 SnowflakeIdUtils 生成每条记录的唯一标识符。
      • 处理可能的数据库异常,通过日志记录错误信息,避免影响主流程。
    • 报警机制:
      • 构建包含异常和服务器环境信息的 NotifactionWarmModel。
      • 使用 AlarmUtils.send(model) 发送报警通知,确保相关人员及时获知异常。
      • 确保在发送报警失败时不会中断处理流程,并记录相关错误信息。

4. 测试 Handler ​

  • 测试场景:发送格式错误的 HTTP 请求(例如 http://localhost/?1==1)以触发解码异常。
  • 预期行为:
    • 自定义处理器关闭与请求相关的连接。
    • 记录缓冲区内容和异常详情。
    • 在增强的处理器中,将异常详情保存到数据库并发送报警通知。

通过如上所述的方式实现和增强 DecodeExceptionHandler,您将显著提升 tio-boot 服务器的安全性。妥善处理解码异常不仅有助于缓解潜在的攻击风险,还能维护服务器操作的完整性和可靠性。

MIT Licensed