题目
设计一个简单的Akka HTTP路由,实现GET和POST请求处理
信息
- 类型:问答
- 难度:⭐⭐
考点
Akka HTTP路由定义, 请求处理, 序列化/反序列化
快速回答
实现一个包含以下功能的路由:
- GET /items 返回所有物品的JSON列表
- POST /items 接收JSON格式的新物品并返回创建状态
- 使用case class表示数据模型
- 处理JSON序列化/反序列化
核心实现步骤
以下是一个完整的Akka HTTP路由实现:
import akka.actor.typed.ActorSystem
import akka.actor.typed.scaladsl.Behaviors
import akka.http.scaladsl.server.Directives._
import akka.http.scaladsl.marshallers.sprayjson.SprayJsonSupport._
import spray.json.DefaultJsonProtocol._
// 1. 定义数据模型和JSON格式
case class Item(id: Int, name: String, price: Double)
object JsonFormats {
implicit val itemFormat = jsonFormat3(Item)
}
// 2. 创建路由
class ItemRoutes {
import JsonFormats._
// 模拟数据存储
private var items = Vector(
Item(1, "Keyboard", 29.99),
Item(2, "Mouse", 19.95)
)
val routes =
pathPrefix("items") {
concat(
// GET /items 返回所有物品
get {
complete(items)
},
// POST /items 创建新物品
post {
entity(as[Item]) { item =>
items :+= item.copy(id = items.size + 1) // 模拟ID生成
complete(s"Item ${item.name} created")
}
}
)
}
}
// 3. 启动HTTP服务器
object Main extends App {
implicit val system = ActorSystem(Behaviors.empty, "AkkaHTTPExample")
val routes = new ItemRoutes().routes
val bindingFuture = Http().newServerAt("localhost", 8080).bind(routes)
println(s"Server online at http://localhost:8080/")
}原理说明
- 路由结构:使用Akka HTTP的DSL构建路由树,
pathPrefix定义公共路径,concat组合多个路由 - JSON处理:通过Spray JSON实现自动序列化(
complete(items))和反序列化(entity(as[Item])) - 幂等性处理:POST请求中
copy(id = ...)确保服务端控制ID生成,避免客户端重复提交问题
最佳实践
- 分离关注点:路由定义、JSON格式、业务逻辑分层组织
- 错误处理:实际项目中应添加异常处理,例如:
handleExceptions(ExceptionHandler { case _: IllegalArgumentException => complete(StatusCodes.BadRequest, "Invalid data") }) - 状态码规范:POST成功应返回201 Created,示例中可添加:
complete(StatusCodes.Created, ...)
常见错误
- JSON格式不匹配:case class字段与JSON字段不一致导致解析失败
- 线程安全问题:示例中
var items非线程安全,生产环境需使用Actor或持久化存储 - 缺少内容协商:未指定
Content-Type头可能导致客户端解析错误,应显式指定:post { entity(as[Item]) { ... } }
扩展知识
- 路由测试:使用Akka HTTP TestKit编写单元测试
- 依赖注入:结合MacWire或Guice管理路由依赖
- 流式处理:对于大文件上传/下载,可使用Akka Streams集成
- CQRS模式:复杂场景可将读写操作分离到不同路由