Content¶
Met Vapor's content API kunt u eenvoudig Codable structs coderen/decoderen naar / van HTTP berichten. JSON codering wordt standaard gebruikt met out-of-the-box ondersteuning voor URL-Encoded Form en Multipart. De API is ook configureerbaar, zodat u encoding strategieën voor bepaalde HTTP-inhoudstypen kunt toevoegen, wijzigen of vervangen.
Overzicht¶
Om te begrijpen hoe Vapor's content API werkt, moet u eerst een paar basisbegrippen over HTTP berichten begrijpen. Kijk eens naar het volgende voorbeeld verzoek.
POST /greeting HTTP/1.1
content-type: application/json
content-length: 18
{"hello": "world"}
Dit verzoek geeft aan dat het JSON-gecodeerde gegevens bevat met behulp van de content-type
header en application/json
media type. Zoals beloofd, volgen er JSON gegevens na de headers in de body.
Content Struct¶
De eerste stap bij het decoderen van dit HTTP-bericht is het maken van een Codable-type dat overeenkomt met de verwachte structuur.
struct Greeting: Content {
var hello: String
}
Conformeren van het type aan Content
zal automatisch conformeren aan Codable
samen met extra hulpprogramma's voor het werken met de content API.
Als je eenmaal de inhoudsstructuur hebt, kun je deze decoderen uit het inkomende verzoek met req.content
.
app.post("greeting") { req in
let greeting = try req.content.decode(Greeting.self)
print(greeting.hello) // "world"
return HTTPStatus.ok
}
De decodeer methode gebruikt het inhoudstype van het verzoek om een geschikte decoder te vinden. Als er geen decoder gevonden wordt, of het verzoek bevat geen inhoudstype header, dan wordt er een 415
foutmelding gegeven.
Dat betekent dat deze route automatisch alle andere ondersteunde inhoudstypes accepteert, zoals url-gecodeerde formulieren:
POST /greeting HTTP/1.1
content-type: application/x-www-form-urlencoded
content-length: 11
hello=world
In het geval van bestand uploads, moet de inhoudseigenschap van het type Data
zijn
struct Profile: Content {
var name: String
var email: String
var image: Data
}
Ondersteunde Media Types¶
Hieronder staan de mediatypes die de inhoud-API standaard ondersteunt.
naam | header waarde | media type |
---|---|---|
JSON | application/json | .json |
Multipart | multipart/form-data | .formData |
URL-Encoded Form | application/x-www-form-urlencoded | .urlEncodedForm |
Plaintext | text/plain | .plainText |
HTML | text/html | .html |
Niet alle media types ondersteunen alle Codable
eigenschappen. Bijvoorbeeld, JSON ondersteunt geen top-level fragmenten en Plaintext ondersteunt geen geneste gegevens.
Query¶
Vapor's Content API's ondersteunen het omgaan met URL gecodeerde data in de URL's query string.
Decoderen¶
Om te begrijpen hoe het decoderen van een URL querystring werkt, bekijk eens het volgende voorbeeld verzoek.
GET /hello?name=Vapor HTTP/1.1
content-length: 0
Net als de API's voor het verwerken van HTTP bericht inhoud, is de eerste stap voor het parsen van URL query strings het maken van een struct
die overeenkomt met de verwachte structuur.
struct Hello: Content {
var name: String?
}
Merk op dat name
een optionele String
is, omdat URL query strings altijd optioneel moeten zijn. Als je een parameter wilt verplichten, gebruik dan een route parameter.
Nu dat je een Content
struct hebt voor de verwachte query string van deze route, kun je deze decoderen.
app.get("hello") { req -> String in
let hello = try req.query.decode(Hello.self)
return "Hello, \(hello.name ?? "Anonymous")"
}
Deze route zou resulteren in het volgende antwoord, gegeven het voorbeeldverzoek van hierboven:
HTTP/1.1 200 OK
content-length: 12
Hello, Vapor
Als de querystring wordt weggelaten, zoals in het volgende verzoek, wordt in plaats daarvan de naam "Anonymous" gebruikt.
GET /hello HTTP/1.1
content-length: 0
Enkele Waarde¶
Naast het decoderen naar een Content
struct, ondersteunt Vapor ook het ophalen van enkele waarden uit de query string met behulp van subscripts.
let name: String? = req.query["name"]
Hooks¶
Vapor zal automatisch beforeEncode
en afterDecode
aanroepen op een Content
type. Er zijn standaard implementaties die niets doen, maar je kunt deze methodes gebruiken om aangepaste logica uit te voeren.
// Wordt uitgevoerd nadat deze inhoud is gedecodeerd. `muteren` is alleen nodig voor structs, niet voor klassen.
mutating func afterDecode() throws {
// Naam mag niet worden doorgegeven, maar als dat wel het geval is, mag het geen lege string zijn.
self.name = self.name?.trimmingCharacters(in: .whitespacesAndNewlines)
if let name = self.name, name.isEmpty {
throw Abort(.badRequest, reason: "Name must not be empty.")
}
}
// Wordt uitgevoerd voordat deze inhoud wordt gecodeerd. `muteren` is alleen nodig voor structs, niet voor klassen.
mutating func beforeEncode() throws {
// Je moet *altijd* een naam teruggeven, en het mag geen lege string zijn.
guard
let name = self.name?.trimmingCharacters(in: .whitespacesAndNewlines),
!name.isEmpty
else {
throw Abort(.badRequest, reason: "Name must not be empty.")
}
self.name = name
}
Standaarden Overschrijven¶
De standaard encoders en decoders die door Vapor's Content API's worden gebruikt, kunnen worden geconfigureerd.
Global¶
Met ContentConfiguration.global
kunt u de encoders en decoders wijzigen die Vapor standaard gebruikt. Dit is handig om te veranderen hoe uw hele applicatie data parseert en serialiseert.
// maak een nieuwe JSON encoder die unix-timestamp data gebruikt
let encoder = JSONEncoder()
encoder.dateEncodingStrategy = .secondsSince1970
// overschrijf de globale codeur die gebruikt wordt voor het `.json` media type
ContentConfiguration.global.use(encoder: encoder, for: .json)
Het muteren van ContentConfiguration
wordt meestal gedaan in configure.swift
.
Eenmalig¶
Aanroepen van codeer en decodeer methodes zoals req.content.decode
ondersteunen het doorgeven van aangepaste codeerders voor eenmalig gebruik.
// maak een nieuwe JSON encoder die unix-timestamp data gebruikt
let decoder = JSONDecoder()
decoder.dateDecodingStrategy = .secondsSince1970
// decodeert Hello struct met behulp van aangepaste decoder
let hello = try req.content.decode(Hello.self, using: decoder)
Aangepaste Coders¶
Toepassingen en pakketten van derden kunnen ondersteuning toevoegen voor mediatypes die Vapor standaard niet ondersteunt door aangepaste codeerders te maken.
Content¶
Vapor specificeert twee protocollen voor codeerders die in staat zijn om inhoud in HTTP berichtlichamen te behandelen: ContentDecoder
en ContentEncoder
.
public protocol ContentEncoder {
func encode<E>(_ encodable: E, to body: inout ByteBuffer, headers: inout HTTPHeaders) throws
where E: Encodable
}
public protocol ContentDecoder {
func decode<D>(_ decodable: D.Type, from body: ByteBuffer, headers: HTTPHeaders) throws -> D
where D: Decodable
}
Door te voldoen aan deze protocollen kunnen uw aangepaste codeerders worden geregistreerd bij ContentConfiguration
zoals hierboven gespecificeerd.
URL Query¶
Vapor specificeert twee protocollen voor codeerders die in staat zijn om inhoud in URL query strings te verwerken: URLQueryDecoder
en URLQueryEncoder
.
public protocol URLQueryDecoder {
func decode<D>(_ decodable: D.Type, from url: URI) throws -> D
where D: Decodable
}
public protocol URLQueryEncoder {
func encode<E>(_ encodable: E, to url: inout URI) throws
where E: Encodable
}
Door te voldoen aan deze protocollen kunnen uw aangepaste codeerders worden geregistreerd bij ContentConfiguration
voor het afhandelen van URL query strings met behulp van de use(urlEncoder:)
en use(urlDecoder:)
methoden.
Aangepaste ResponseEncodable
¶
Een andere aanpak is het implementeren van ResponseEncodable
op je types. Beschouw deze triviale HTML
wrapper type:
struct HTML {
let value: String
}
Dan zou de ResponseEncodable
implementatie er als volgt uitzien:
extension HTML: ResponseEncodable {
public func encodeResponse(for request: Request) -> EventLoopFuture<Response> {
var headers = HTTPHeaders()
headers.add(name: .contentType, value: "text/html")
return request.eventLoop.makeSucceededFuture(.init(
status: .ok, headers: headers, body: .init(string: value)
))
}
}
Als je async
/await
gebruikt, kun je AsyncResponseEncodable
gebruiken:
extension HTML: AsyncResponseEncodable {
public func encodeResponse(for request: Request) async throws -> Response {
var headers = HTTPHeaders()
headers.add(name: .contentType, value: "text/html")
return .init(status: .ok, headers: headers, body: .init(string: value))
}
}
Merk op dat dit het mogelijk maakt om de Content-Type
header aan te passen. Zie HTTPHeaders
reference voor meer details.
U kunt dan HTML
gebruiken als response type in uw routes:
app.get { _ in
HTML(value: """
<html>
<body>
<h1>Hello, World!</h1>
</body>
</html>
""")
}