文件上传

Netty-Boot 文件上传处理

Netty-Boot 项目中,文件上传是一个常见的需求。Netty 提供了强大的 HTTP 处理能力,可以通过 multipart/form-data 实现文件上传。本文将介绍如何通过 Netty-Boot 处理文件上传,并解析其工作原理。

文件上传原理

  1. HTTP POST 和 multipart/form-data:文件上传通常通过 HTTP POST 请求,Content-Type 设置为 multipart/form-data。这使得文件和表单数据一起发送到服务器。
  2. Netty 的 HttpPostRequestDecoder:Netty 提供了 HttpPostRequestDecoder,它可以解析 multipart/form-data 请求,将其中的文件和表单字段分离开来,方便处理。
  3. 保存文件:在接收到文件数据后,可以将其保存到本地磁盘或进行进一步的处理。Netty 通过 FileUpload 类来表示文件上传项。
  4. 请求大小限制:默认情况下,Netty 支持的文件大小为 10 MB。如果需要上传更大的文件,可以通过 http.multipart.max-request-size 参数调整大小限制,单位为字节。

文件上传实现

1. 文件上传大小限制配置

默认情况下,Netty 支持的文件上传大小为 10MB。如果需要上传更大的文件,可以在配置文件中增加以下参数:

http.multipart.max-request-size=10485760  # 10MB

如果你需要增加上传文件的大小限制,可以调整此值。例如,20MB:

http.multipart.max-request-size=20971520  # 20MB

2. 创建 UploadHandler 处理上传

UploadHandler 类中,我们使用 HttpPostRequestDecoder 解析上传的 multipart/form-data 请求。解析后,可以通过 FileUpload 对象获取上传的文件并将其保存到本地。

package com.litongjava.netty.im.handler;

import java.io.File;
import java.io.IOException;

import com.litongjava.model.body.RespBodyVo;
import com.litongjava.netty.boot.utils.HttpResponseUtils;

import io.netty.channel.ChannelHandlerContext;
import io.netty.handler.codec.http.FullHttpRequest;
import io.netty.handler.codec.http.FullHttpResponse;
import io.netty.handler.codec.http.HttpHeaderNames;
import io.netty.handler.codec.http.HttpHeaderValues;
import io.netty.handler.codec.http.multipart.DefaultHttpDataFactory;
import io.netty.handler.codec.http.multipart.FileUpload;
import io.netty.handler.codec.http.multipart.HttpPostRequestDecoder;
import io.netty.handler.codec.http.multipart.InterfaceHttpData;
import io.netty.util.CharsetUtil;
import lombok.extern.slf4j.Slf4j;

@Slf4j
public class UploadHandler {

  public FullHttpResponse upload(ChannelHandlerContext ctx, FullHttpRequest httpRequest) {
    // 检查请求是否为 multipart/form-data
    if (!httpRequest.method().name().equals("POST")) {
      log.error("上传请求不是POST请求");
      return createErrorResponse("Invalid request method");
    }
    String contentType = httpRequest.headers().get(HttpHeaderNames.CONTENT_TYPE);
    if (contentType == null || !contentType.startsWith(HttpHeaderValues.MULTIPART_FORM_DATA.toString())) {
      log.error("Content-Type 不是 multipart/form-data");
      return createErrorResponse("Invalid Content-Type");
    }

    // 使用 HttpPostRequestDecoder 来解析上传文件
    HttpPostRequestDecoder decoder = new HttpPostRequestDecoder(new DefaultHttpDataFactory(false), httpRequest, CharsetUtil.UTF_8);
    try {
      // 处理每个文件或者表单项
      while (decoder.hasNext()) {
        InterfaceHttpData data = decoder.next();
        if (data != null && data.getHttpDataType() == InterfaceHttpData.HttpDataType.FileUpload) {
          FileUpload fileUpload = (FileUpload) data;
          if (fileUpload.isCompleted()) {
            // 保存文件到本地或处理
            File uploadFolder = new File("files");
            if (!uploadFolder.exists()) {
              uploadFolder.mkdirs();
            }
            File file = new File("files/" + fileUpload.getFilename());
            fileUpload.renameTo(file);
            log.info("文件上传成功: {}", file.getAbsolutePath());
          }
        }
      }
    } catch (HttpPostRequestDecoder.EndOfDataDecoderException e) {
      log.info("所有数据已经处理完毕");
    } catch (IOException e) {
      log.error("文件上传失败", e);
      return createErrorResponse("File upload failed");
    } finally {
      decoder.destroy(); // 释放资源
    }

    // 返回成功响应
    return createSuccessResponse();
  }

  private FullHttpResponse createErrorResponse(String message) {
    RespBodyVo ok = RespBodyVo.fail(message);
    return HttpResponseUtils.json(ok);
  }

  private FullHttpResponse createSuccessResponse() {
    RespBodyVo ok = RespBodyVo.ok();
    return HttpResponseUtils.json(ok);
  }
}

3. 注册 UploadHandler 处理器

UploadHandler 处理器注册到 Netty-Boot 的路由器中,以便接收文件上传请求:

UploadHandler uploadHandler = Aop.get(UploadHandler.class);
httpRequestRouter.add("/upload", uploadHandler::upload);

4. 测试文件上传

通过 curl 或其他 HTTP 客户端工具(如 Postman)测试文件上传:

curl --location --request POST 'http://127.0.0.1/upload' \
--header 'User-Agent: Apifox/1.0.0 (https://apifox.com)' \
--header 'Accept: */*' \
--header 'Host: 127.0.0.1' \
--header 'Connection: keep-alive' \
--header 'Content-Type: multipart/form-data; boundary=--------------------------366765811236362307119867' \
--form 'file=@"textbook-Thomas H. Cormen, Charles E. Leiserson, Ronald L. Rivest, Clifford Stein - Introduction to Algorithms-MIT Press (2022) (2).pdf"'

此命令将文件上传到 http://127.0.0.1/upload 接口。

5. 处理上传请求流程

  1. POST 请求验证:首先验证上传的请求方法是否为 POST,并检查 Content-Type 是否为 multipart/form-data,如果不是,返回错误响应。
  2. 文件解析:通过 HttpPostRequestDecoder 解析上传的表单数据,找到文件上传项(FileUpload)。
  3. 文件保存:将上传的文件保存到指定目录(files/)。如果该目录不存在,则创建目录。
  4. 返回响应:文件上传成功后,返回一个成功的 JSON 响应。如果上传失败或请求不符合要求,返回错误响应。

总结

本文详细介绍了如何通过 Netty-Boot 实现文件上传。通过配置文件限制上传文件大小,使用 HttpPostRequestDecoder 解析 multipart/form-data 请求,开发者可以轻松处理文件上传请求,并将文件存储到服务器指定目录。