You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何捕获withSizeLimit指令抛出的EntityStreamSizeException

问题分析与解决方案

核心原因

你遇到的问题根源在于:EntityStreamSizeException 并不会直接以异常形式抛出到 handleException 的作用域中,而是被Akka HTTP自动转换为请求实体拒绝(RequestEntityRejection);同时你之前的指令顺序错误,handleRejection/handleException 放在了parameter指令内部,无法捕获到实体解析阶段触发的大小超限拒绝。

正确解决方案

步骤1:编写正确的自定义拒绝处理器

针对Akka HTTP的EntityStreamSizeExceededRejection(部分版本通过RequestEntityRejection包装)编写处理器,返回自定义的413 Payload Too Large响应:

import akka.http.scaladsl.server.RejectionHandler
import akka.http.scaladsl.server.Directives._
import akka.http.scaladsl.model.StatusCodes
import akka.http.scaladsl.server.rejections.{EntityStreamSizeExceededRejection, RequestEntityRejection}

// 适配不同Akka HTTP版本的两种写法:
// 写法1:直接匹配EntityStreamSizeExceededRejection(多数新版本)
val customRejectionHandler: RejectionHandler = RejectionHandler.newBuilder()
  .handle {
    case EntityStreamSizeExceededRejection(limit, actualSize) =>
      val limitMB = limit / (1024 * 1024)
      val actualMB = actualSize / (1024 * 1024)
      complete(StatusCodes.PayloadTooLarge, s"文件大小超过限制:最大允许${limitMB}MB,实际上传${actualMB}MB")
  }
  .result()

// 写法2:匹配RequestEntityRejection包装的异常(部分旧版本)
// val customRejectionHandler: RejectionHandler = RejectionHandler.newBuilder()
//   .handle {
//     case RequestEntityRejection(_, ex: EntityStreamSizeException) =>
//       val limitMB = ex.limit / (1024 * 1024)
//       val actualMB = ex.actualSize / (1024 * 1024)
//       complete(StatusCodes.PayloadTooLarge, s"文件大小超过${limitMB}MB限制,实际上传${actualMB}MB")
//   }
//   .result()

步骤2:调整路由指令顺序

将handleRejection放在路由的最外层,确保它能覆盖到withSizeLimit和entity指令的执行范围:

final val MEDIA_FILE_UPLOAD_MAX_SIZE = 30 * 1024 * 1024

def uploadMediaObjectForASite(): Route = 
  handleRejection(customRejectionHandler) {
    path("Some path") { userId =>
      post {
        parameter(Symbol("location_id").as[String]) { locationId =>
          withSizeLimit(MEDIA_FILE_UPLOAD_MAX_SIZE) {
            withRequestTimeout(120.seconds) {
              entity(as[Multipart.FormData]) { formData =>
                val metadataList = List(TITLE, DESCRIPTION, UPLOADED_BY)
                // 执行文件上传逻辑
                complete(StatusCodes.OK, "文件上传成功")
              }
            }
          }
        }
      }
    }
  }

可选:全局生效配置

如果希望所有路由都使用该自定义拒绝处理器,可以在启动HTTP服务器时配置全局默认处理器:

import akka.http.scaladsl.Http
import akka.http.scaladsl.settings.HttpServerSettings

val serverSettings = HttpServerSettings(system)
  .withDefaultRejectionHandler(customRejectionHandler)

val bindingFuture = Http()
  .newServerAt("0.0.0.0", 8080)
  .withSettings(serverSettings)
  .bind(uploadMediaObjectForASite())

为什么之前的方法无效

  1. handleException无效:Akka HTTP会将EntityStreamSizeException转换为Rejection,不会直接抛出到handleException的处理流程中。
  2. handleRejection位置错误:你之前将其放在parameter指令内部,而实体大小检查发生在entity解析阶段,此时拒绝已经触发但未被外层处理器捕获。
  3. 拒绝类型匹配错误:之前的代码试图直接匹配EntityStreamSizeException,但实际需要匹配的是Akka HTTP定义的EntityStreamSizeExceededRejection或包装它的RequestEntityRejection。

内容的提问来源于stack exchange,提问作者Stolaine

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.13 22:47:51