HTTP.Body
承载着HTTP.Message
,用于底层数据的传输。这些数据可能是Json,html,文本或者图片。
public enum Body {
case data(Bytes)
case chunked((ChunkStream) throws -> Void)
}
Data Case
data
是HTTP.Message
中body
最常用的,它是简单的字节数组,一系列与之相关的类型或协议都定义在header
的Content-Type
中。下面来看一些🌰。
Application/JSON
如果我们的Content-Type
中包含application/json
,那表明底层数据是序列化的JSON数据。
if let contentType = req.headers["Content-Type"], contentType.contains("application/json"), let bytes = req.body.bytes {
let json = try JSON(bytes: bytes)
print("Got JSON: \(json)")
}
Image/PNG
如果我们的Content-Type
中包含image/png
,那表明底层数据时编码的png图片
if let contentType = req.headers["Content-Type"], contentType.contains("image/png"), let bytes = req.body.bytes {
try database.save(image: bytes)
}
Chunked Case
在vapor中chunked
仅使用与外部HTTP.Message
,传统的response角色是收集整个body然后将其传递。我们可以利用代码块异步发送body。
let body: Body = Body.chunked(sender)
return Response(status: .ok, body: body)
我们可以手动实现,或者使用Vapor内置的初始方法快捷创建代码块body。
return Response(status: .ok) { chunker in
for name in ["joe", "pam", "cheryl"] {
sleep(1)
try chunker.send(name)
}
try chunker.close()
}
Note: 注意在chunker销毁之前调用close()。
BodyRepresentable
除了常见的Body具体类型,Vapor还广泛的支持BodyRepresentable
,这意味着对象可以转换为Body
类型互换使用。比如:
return Response(body: "Hello, World!")
上例中字符串会转换为字节添加到body中。
实际我们最好使用
return "Hello, World!"
,Vapor会自动为Content-Type
设置适当的值。
看一下这是如何实现的:
public protocol BodyRepresentable {
func makeBody() -> Body
}
Custom
我们可以将自定义的类型遵守HTTP.BodyRepresentable
协议。如下例子中假设我们有一个.vpr
文件,可转换为VPRFile
model,就可以作为body使用。
extension VPRFile: HTTP.BodyRepresentable {
func makeBody() -> Body {
// collect bytes
return .data(bytes)
}
}
你可能注意到了协议中是包含“thows”的,但是我们的实现中没有,这在swift中是完全可以的。这样你在手动调用该方法时就不用再抛出异常了。
然后我们就可以直接在Response
中使用VRP
文件了。
drop.get("files", ":file-name") { request in
let filename = try request.parameters.extract("file-name") as String
let file = VPRFileManager.fetch(filename)
return Response(status: .ok, headers: ["Content-Type": "file/vpr"], body: file)
}
实际上,如果我们经常重复这个操作,我们可能会将VPRFile
直接与ResponseRepresentable
配合使用:
extension VPRFile: HTTP.ResponseRepresentable {
func makeResponse() -> Response {
return Response(
status: .ok,
headers: ["Content-Type": "file/vpr"],
body: file
)
}
}
上面的例子也会改成这样:
drop.get("files", ":file-name") { request in
let filename = try request.parameters.extract("file-name") as String
return VPRFileManager.fetch(filename)
}
我们也可以使用类型安全的路由使其更简洁:
drop.get("files", String.self) { request, filename in
return VPRFileManager.fetch(filename)
}