|
| 1 | +# Conteúdo |
| 2 | + |
| 3 | +A API de conteúdo do Vapor permite que você codifique/decodifique facilmente structs Codable para/de mensagens HTTP. A codificação [JSON](https://tools.ietf.org/html/rfc7159) é usada por padrão com suporte nativo para [Formulário Codificado por URL](https://pt.wikipedia.org/wiki/Codifica%C3%A7%C3%A3o_percentual#O_tipo_application/x-www-form-urlencoded) e [Multipart](https://tools.ietf.org/html/rfc2388). A API também é configurável, permitindo que você adicione, modifique ou substitua estratégias de codificação para determinados tipos de conteúdo HTTP. |
| 4 | + |
| 5 | +## Visão Geral |
| 6 | + |
| 7 | +Para entender como a API de conteúdo do Vapor funciona, você deve primeiro entender alguns conceitos básicos sobre mensagens HTTP. Dê uma olhada no exemplo de requisição a seguir. |
| 8 | + |
| 9 | +```http |
| 10 | +POST /greeting HTTP/1.1 |
| 11 | +content-type: application/json |
| 12 | +content-length: 18 |
| 13 | +
|
| 14 | +{"hello": "world"} |
| 15 | +``` |
| 16 | + |
| 17 | +Esta requisição indica que contém dados codificados em JSON usando o cabeçalho `content-type` e o tipo de mídia `application/json`. Conforme prometido, alguns dados JSON seguem após os cabeçalhos no corpo. |
| 18 | + |
| 19 | +### Struct de Conteúdo |
| 20 | + |
| 21 | +O primeiro passo para decodificar esta mensagem HTTP é criar um tipo Codable que corresponda à estrutura esperada. |
| 22 | + |
| 23 | +```swift |
| 24 | +struct Greeting: Content { |
| 25 | + var hello: String |
| 26 | +} |
| 27 | +``` |
| 28 | + |
| 29 | +Conformar o tipo a `Content` adicionará automaticamente conformidade a `Codable`, juntamente com utilitários adicionais para trabalhar com a API de conteúdo. |
| 30 | + |
| 31 | +Uma vez que você tenha a estrutura de conteúdo, você pode decodificá-la da requisição recebida usando `req.content`. |
| 32 | + |
| 33 | +```swift |
| 34 | +app.post("greeting") { req in |
| 35 | + let greeting = try req.content.decode(Greeting.self) |
| 36 | + print(greeting.hello) // "world" |
| 37 | + return HTTPStatus.ok |
| 38 | +} |
| 39 | +``` |
| 40 | + |
| 41 | +O método decode usa o tipo de conteúdo da requisição para encontrar um decodificador apropriado. Se nenhum decodificador for encontrado, ou se a requisição não contiver o cabeçalho de tipo de conteúdo, um erro `415` será lançado. |
| 42 | + |
| 43 | +Isso significa que essa rota aceita automaticamente todos os outros tipos de conteúdo suportados, como formulário codificado por URL: |
| 44 | + |
| 45 | +```http |
| 46 | +POST /greeting HTTP/1.1 |
| 47 | +content-type: application/x-www-form-urlencoded |
| 48 | +content-length: 11 |
| 49 | +
|
| 50 | +hello=world |
| 51 | +``` |
| 52 | + |
| 53 | +No caso de uploads de arquivos, sua propriedade de conteúdo deve ser do tipo `Data`. |
| 54 | + |
| 55 | +```swift |
| 56 | +struct Profile: Content { |
| 57 | + var name: String |
| 58 | + var email: String |
| 59 | + var image: Data |
| 60 | +} |
| 61 | +``` |
| 62 | + |
| 63 | +### Tipos de Mídia Suportados |
| 64 | + |
| 65 | +Abaixo estão os tipos de mídia que a API de conteúdo suporta por padrão. |
| 66 | + |
| 67 | +| nome | valor do cabeçalho | tipo de mídia | |
| 68 | +|--------------------|------------------------------------|----------------------------| |
| 69 | +| JSON | application/json | `.json` | |
| 70 | +| Multipart | multipart/form-data | `.formData` | |
| 71 | +| Formulário Codificado por URL | application/x-www-form-urlencoded | `.urlEncodedForm` | |
| 72 | +| Texto simples | text/plain | `.plainText` | |
| 73 | +| HTML | text/html | `.html` | |
| 74 | + |
| 75 | +Nem todos os tipos de mídia suportam todos os recursos de `Codable`. Por exemplo, JSON não suporta fragmentos de nível superior e Texto simples não suporta dados aninhados. |
| 76 | + |
| 77 | +## Consulta |
| 78 | + |
| 79 | +As APIs de Conteúdo do Vapor suportam o tratamento de dados codificados por URL na string de consulta da URL. |
| 80 | + |
| 81 | +### Decodificação |
| 82 | + |
| 83 | +Para entender como a decodificação de uma string de consulta URL funciona, dê uma olhada na requisição de exemplo a seguir. |
| 84 | + |
| 85 | +```http |
| 86 | +GET /hello?name=Vapor HTTP/1.1 |
| 87 | +content-length: 0 |
| 88 | +``` |
| 89 | + |
| 90 | +Assim como as APIs para lidar com o conteúdo do corpo da mensagem HTTP, o primeiro passo para analisar strings de consulta URL é criar uma `struct` que corresponda à estrutura esperada. |
| 91 | + |
| 92 | +```swift |
| 93 | +struct Hello: Content { |
| 94 | + var name: String? |
| 95 | +} |
| 96 | +``` |
| 97 | + |
| 98 | +Observe que `name` é uma `String` opcional, já que strings de consulta URL devem sempre ser opcionais. Se você quiser exigir um parâmetro, use um parâmetro de rota em vez disso. |
| 99 | + |
| 100 | +Agora que você tem uma struct `Content` para a string de consulta esperada desta rota, você pode decodificá-la. |
| 101 | + |
| 102 | +```swift |
| 103 | +app.get("hello") { req -> String in |
| 104 | + let hello = try req.query.decode(Hello.self) |
| 105 | + return "Hello, \(hello.name ?? "Anonymous")" |
| 106 | +} |
| 107 | +``` |
| 108 | + |
| 109 | +Esta rota resultaria na seguinte resposta, dada a requisição de exemplo acima: |
| 110 | + |
| 111 | +```http |
| 112 | +HTTP/1.1 200 OK |
| 113 | +content-length: 12 |
| 114 | +
|
| 115 | +Hello, Vapor |
| 116 | +``` |
| 117 | + |
| 118 | +Se a string de consulta fosse omitida, como na seguinte requisição, o nome "Anonymous" seria usado em vez disso. |
| 119 | + |
| 120 | +```http |
| 121 | +GET /hello HTTP/1.1 |
| 122 | +content-length: 0 |
| 123 | +``` |
| 124 | + |
| 125 | +### Valor Único |
| 126 | + |
| 127 | +Além de decodificar para uma struct `Content`, o Vapor também suporta a busca de valores únicos da string de consulta usando subscripts. |
| 128 | + |
| 129 | +```swift |
| 130 | +let name: String? = req.query["name"] |
| 131 | +``` |
| 132 | + |
| 133 | +## Ganchos |
| 134 | + |
| 135 | +O Vapor chamará automaticamente `beforeEncode` e `afterDecode` em um tipo `Content`. Implementações padrão são fornecidas que não fazem nada, mas você pode usar esses métodos para executar lógica personalizada. |
| 136 | + |
| 137 | +```swift |
| 138 | +// Executa após este Content ser decodificado. `mutating` é necessário apenas para structs, não para classes. |
| 139 | +mutating func afterDecode() throws { |
| 140 | + // O nome pode não ser passado, mas se for, não pode ser uma string vazia. |
| 141 | + self.name = self.name?.trimmingCharacters(in: .whitespacesAndNewlines) |
| 142 | + if let name = self.name, name.isEmpty { |
| 143 | + throw Abort(.badRequest, reason: "Name must not be empty.") |
| 144 | + } |
| 145 | +} |
| 146 | + |
| 147 | +// Executa antes deste Content ser codificado. `mutating` é necessário apenas para structs, não para classes. |
| 148 | +mutating func beforeEncode() throws { |
| 149 | + // Deve sempre passar um nome de volta, e ele não pode ser uma string vazia. |
| 150 | + guard |
| 151 | + let name = self.name?.trimmingCharacters(in: .whitespacesAndNewlines), |
| 152 | + !name.isEmpty |
| 153 | + else { |
| 154 | + throw Abort(.badRequest, reason: "Name must not be empty.") |
| 155 | + } |
| 156 | + self.name = name |
| 157 | +} |
| 158 | +``` |
| 159 | + |
| 160 | +## Substituir Padrões |
| 161 | + |
| 162 | +Os codificadores e decodificadores padrão usados pelas APIs de Conteúdo do Vapor podem ser configurados. |
| 163 | + |
| 164 | +### Global |
| 165 | + |
| 166 | +`ContentConfiguration.global` permite que você altere os codificadores e decodificadores que o Vapor usa por padrão. Isso é útil para mudar como toda a sua aplicação analisa e serializa dados. |
| 167 | + |
| 168 | +```swift |
| 169 | +// cria um novo codificador JSON que usa datas em formato de timestamp Unix |
| 170 | +let encoder = JSONEncoder() |
| 171 | +encoder.dateEncodingStrategy = .secondsSince1970 |
| 172 | + |
| 173 | +// substitui o codificador global usado para o tipo de mídia `.json` |
| 174 | +ContentConfiguration.global.use(encoder: encoder, for: .json) |
| 175 | +``` |
| 176 | + |
| 177 | +Modificar `ContentConfiguration` é geralmente feito em `configure.swift`. |
| 178 | + |
| 179 | +### Uso Único |
| 180 | + |
| 181 | +Chamadas para métodos de codificação e decodificação como `req.content.decode` suportam a passagem de codificadores personalizados para usos únicos. |
| 182 | + |
| 183 | +```swift |
| 184 | +// cria um novo decodificador JSON que usa datas em formato de timestamp Unix |
| 185 | +let decoder = JSONDecoder() |
| 186 | +decoder.dateDecodingStrategy = .secondsSince1970 |
| 187 | + |
| 188 | +// decodifica a struct Hello usando um decodificador personalizado |
| 189 | +let hello = try req.content.decode(Hello.self, using: decoder) |
| 190 | +``` |
| 191 | + |
| 192 | +## Codificadores Personalizados |
| 193 | + |
| 194 | +Aplicativos e pacotes de terceiros podem adicionar suporte para tipos de mídia que o Vapor não suporta por padrão, criando codificadores personalizados. |
| 195 | + |
| 196 | +### Conteúdo |
| 197 | + |
| 198 | +O Vapor especifica dois protocolos para codificadores capazes de lidar com conteúdo nos corpos de mensagens HTTP: `ContentDecoder` e `ContentEncoder`. |
| 199 | + |
| 200 | +```swift |
| 201 | +public protocol ContentEncoder { |
| 202 | + func encode<E>(_ encodable: E, to body: inout ByteBuffer, headers: inout HTTPHeaders) throws |
| 203 | + where E: Encodable |
| 204 | +} |
| 205 | + |
| 206 | +public protocol ContentDecoder { |
| 207 | + func decode<D>(_ decodable: D.Type, from body: ByteBuffer, headers: HTTPHeaders) throws -> D |
| 208 | + where D: Decodable |
| 209 | +} |
| 210 | +``` |
| 211 | + |
| 212 | +Conformar-se a esses protocolos permite que seus codificadores personalizados sejam registrados em `ContentConfiguration`, conforme especificado acima. |
| 213 | + |
| 214 | +### Consulta de URL |
| 215 | + |
| 216 | +O Vapor especifica dois protocolos para codificadores capazes de lidar com conteúdo em strings de consulta de URL: `URLQueryDecoder` e `URLQueryEncoder`. |
| 217 | + |
| 218 | +```swift |
| 219 | +public protocol URLQueryDecoder { |
| 220 | + func decode<D>(_ decodable: D.Type, from url: URI) throws -> D |
| 221 | + where D: Decodable |
| 222 | +} |
| 223 | + |
| 224 | +public protocol URLQueryEncoder { |
| 225 | + func encode<E>(_ encodable: E, to url: inout URI) throws |
| 226 | + where E: Encodable |
| 227 | +} |
| 228 | +``` |
| 229 | + |
| 230 | +Conformar-se a esses protocolos permite que seus codificadores personalizados sejam registrados em `ContentConfiguration` para lidar com strings de consulta de URL usando os métodos `use(urlEncoder:)` e `use(urlDecoder:)`. |
| 231 | + |
| 232 | +### `ResponseEncodable` Personalizado |
| 233 | + |
| 234 | +Outra abordagem envolve a implementação de `ResponseEncodable` em seus tipos. Considere este tipo de embrulho `HTML` trivial: |
| 235 | + |
| 236 | +```swift |
| 237 | +struct HTML { |
| 238 | + let value: String |
| 239 | +} |
| 240 | +``` |
| 241 | + |
| 242 | +Em seguida, sua implementação `ResponseEncodable` seria assim: |
| 243 | + |
| 244 | +```swift |
| 245 | +extension HTML: ResponseEncodable { |
| 246 | + public func encodeResponse(for request: Request) -> EventLoopFuture<Response> { |
| 247 | + var headers = HTTPHeaders() |
| 248 | + headers.add(name: .contentType, value: "text/html") |
| 249 | + return request.eventLoop.makeSucceededFuture(.init( |
| 250 | + status: .ok, headers: headers, body: .init(string: value) |
| 251 | + )) |
| 252 | + } |
| 253 | +} |
| 254 | +``` |
| 255 | + |
| 256 | +Se estiver usando `async`/`await`, você pode usar `AsyncResponseEncodable`: |
| 257 | + |
| 258 | +```swift |
| 259 | +extension HTML: AsyncResponseEncodable { |
| 260 | + public func encodeResponse(for request: Request) async throws -> Response { |
| 261 | + var headers = HTTPHeaders() |
| 262 | + headers.add(name: .contentType, value: "text/html") |
| 263 | + return .init(status: .ok, headers: headers, body: .init(string: value)) |
| 264 | + } |
| 265 | +} |
| 266 | +``` |
| 267 | + |
| 268 | +Observe que isso permite personalizar o cabeçalho `Content-Type`. Consulte a [referência do HTTPHeaders](https://api.vapor.codes/vapor/documentation/vapor/response/headers) para mais detalhes. |
| 269 | + |
| 270 | +Em seguida, você pode usar `HTML` como um tipo de resposta em suas rotas: |
| 271 | + |
| 272 | +```swift |
| 273 | +app.get { _ in |
| 274 | + HTML(value: """ |
| 275 | + <html> |
| 276 | + <body> |
| 277 | + <h1>Hello, World!</h1> |
| 278 | + </body> |
| 279 | + </html> |
| 280 | + """) |
| 281 | +} |
| 282 | +``` |
0 commit comments