HTTP is all about sending and receiving knowledge over the community. Initially it was solely utilized to switch HTML paperwork, however these days we use HTTP to switch CSS, JavaScript, JSON and lots of different knowledge sorts. In line with the requirements, the Content material-Kind and Content material-Size headers can be utilized to have a greater understanding in regards to the knowledge contained in the physique of the HTTP request.
Fashionable net servers can mechanically ship again these headers based mostly on the article you come in a request handler perform. That is the case with Hummingbird, it has built-in encoding and decoding help, which makes the information transformation course of actually easy.
For instance if we setup the next route handler and name the good day endpoint utilizing cURL with the -i flag, the output will include a bit extra details about the response. ℹ️
router.get("good day") { _ in "good day" }
There are some primary headers within the response, the content-type
header incorporates the kind of the physique, which is presently a plain textual content with an UTF-8 encoded string, since we have returned a String
kind utilizing our Swift code. The content-length
is 5, as a result of the character rely of good day is 5.
There are another headers, however ignore these, the fascinating half for us is the content-type header, and the way it’s injected into the response. Each Hummingbird software has an encoder
and a decoder
property. The default values for these are NullEncoder
and NullDecoder
. The encoders can magically add the right content material kind header to the response and encode some object right into a HTTP response knowledge. Not every little thing is response encodable and decodable by default, however you possibly can encode String
objects in Hummingbird by default. 👍
Encoding and decoding JSON objects
Most of the server-side Swift methods are used to create JSON-based RESTful API backends for cell frontends. Hummingbird may help you with this, because it has built-in encoding and decoding help for JSON objects by means of the Codable protocol.
First it’s important to import the HummingbirdFoundation
library, since it’s a standalone helper software constructed across the Basis framework, and that package deal incorporates the Codable kind extensions. Subsequent it’s important to setup the encoder and decoder utilizing a JSONEncoder
and JSONDecoder
occasion. After this, you possibly can simply remodel incoming HTTP physique objects into Swift knowledge constructions and return with them as nicely. Let me present you a fast instance. ⤵️
import Hummingbird
import HummingbirdFoundation
struct Foo: Codable {
let bar: String
let baz: Int
}
extension Foo: HBResponseCodable {}
extension HBApplication {
func configure(_ args: AppArguments) throws {
decoder = JSONDecoder()
encoder = JSONEncoder()
router.submit("foo") { req async throws -> Foo in
guard let foo = attempt? req.decode(as: Foo.self) else {
throw HBHTTPError(.badRequest, message: "Invalid request physique.")
}
return foo
}
}
}
As you possibly can see the kind of the returned content material is now correctly set to software/json
and the size can also be supplied by default. We have been additionally capable of decode the Foo
object from the request physique and mechanically encode the article after we returned with it.
Codable routing works like magic and these days it is a fairly commonplace method if it involves server-side Swift frameworks. Enjoyable reality: this method was initially ‘invented’ for Swift by the builders of the Kitura framework. Thanks. 🙏
The HBResponseCodable
and the HBResponseEncodable
protocols are the essential constructing blocks and the HBRequestDecoder and the HBResponseEncoder are answerable for this magic. They make it attainable to decode a Decodable
object from a HBRequest and encode issues right into a HBResponse object and likewise present further headers. If you want to know extra, I extremely suggest to try the JSONCodign.swift file contained in the framework. 😉
Encoding and decoding HTML types
I do not wish to get an excessive amount of into the small print of constructing types utilizing HTML code, by the way in which there’s a higher means utilizing SwiftHtml, however I would wish to focus extra on the underlying knowledge switch mechanism and the enctype attribute. There are 3 attainable, however solely two helpful values of the encoding kind:
- software/x-www-form-urlencoded
- multipart/form-data
URL encoding and decoding is supported out of the field when utilizing HummingbirdFoundation, it is a easy wrapper across the URL encoding mechanism to simply help knowledge transformation.
decoder = URLEncodedFormDecoder()
encoder = URLEncodedFormEncoder()
In order that’s one method to course of a URL encoded type, the opposite model relies on the multipart method, which has no built-in help in Hummingbird, however you should utilize the multipart-kit library from the Vapor framework to course of such types. You will discover a working instance right here. I even have an article about easy methods to add information utilizing multipart type knowledge requests. So there are many sources on the market, that is why I will not embody an instance on this article. 😅
Header based mostly encoding and decoding
First we have now to implement a customized request decoder and a response encoder. Within the decoder, we’ll verify the Content material-Kind
header for a given request and decode the HTTP physique based mostly on that. The encoder will do the very same factor, however the response physique output goes to depend upon the Settle for
header area. Here is how one can implement it:
struct AppDecoder: HBRequestDecoder {
func decode<T>(
_ kind: T.Kind,
from req: HBRequest
) throws -> T the place T: Decodable {
swap req.headers["content-type"].first {
case "software/json", "software/json; charset=utf-8":
return attempt JSONDecoder().decode(kind, from: req)
case "software/x-www-form-urlencoded":
return attempt URLEncodedFormDecoder().decode(kind, from: req)
default:
throw HBHTTPError(.badRequest)
}
}
}
struct AppEncoder: HBResponseEncoder {
func encode<T>(
_ worth: T,
from req: HBRequest
) throws -> HBResponse the place T: Encodable {
swap req.headers["accept"].first {
case "software/json":
return attempt JSONEncoder().encode(worth, from: req)
case "software/x-www-form-urlencoded":
return attempt URLEncodedFormEncoder().encode(worth, from: req)
default:
throw HBHTTPError(.badRequest)
}
}
}
Now in the event you change the configuration and use the AppEncoder & AppDecoder it is best to be capable to reply based mostly on the Settle for header and course of the enter based mostly on the Content material-Kind header.
import Hummingbird
import HummingbirdFoundation
struct Foo: Codable {
let bar: String
let baz: Int
}
extension Foo: HBResponseEncodable {}
extension Foo: HBResponseCodable {}
extension HBApplication {
func configure(_ args: AppArguments) throws {
decoder = AppDecoder()
encoder = AppEncoder()
router.submit("foo") { req async throws -> Foo in
guard let foo = attempt? req.decode(as: Foo.self) else {
throw HBHTTPError(.badRequest, message: "Invalid request physique.")
}
return foo
}
}
}
Be at liberty to mess around with some cURL snippets… 👾
curl -i -X POST http://localhost:8080/foo
-H "Content material-Kind: software/x-www-form-urlencoded"
-H "Settle for: software/json"
--data-raw 'bar=bar&baz=42'
curl -i -X POST http://localhost:8080/foo
-H "Content material-Kind: software/json"
-H "Settle for: software/x-www-form-urlencoded"
--data-raw '{"bar": "bar", "baz": 42}'
curl -i -X POST http://localhost:8080/foo
-H "Content material-Kind: software/json"
-H "Settle for: multipart/form-data"
--data-raw '{"bar": "bar", "baz": 42}'
So, based mostly on this text it is best to be capable to implement help to much more content material sorts by merely extending the app encoder and decoder. After all you may need to import some further package deal dependencies, however that is positive.
Uncooked requests and responses
Yet another little factor, earlier than I finish this text: you possibly can entry the uncooked request physique knowledge and ship again a uncooked response utilizing the HBResponse
object like this:
router.submit("foo") { req async throws -> HBResponse in
if let buffer = req.physique.buffer {
let rawInputData = buffer.getData(
at: 0,
size: buffer.readableBytes
)
print(rawInputData)
}
if let sequence = req.physique.stream?.sequence {
for attempt await chunk in sequence {
print(chunk)
}
}
guard let knowledge = "good day".knowledge(utilizing: .utf8) else {
throw HBHTTPError(.internalServerError)
}
return .init(
standing: .okay,
headers: .init(),
physique: .byteBuffer(.init(knowledge: knowledge))
)
}
For smaller requests, you should utilize the req.physique.buffer
property and switch it right into a Information kind if wanted. Hummingbird has nice help for the brand new Swift Concurreny API, so you should utilize the sequence on the physique stream in the event you want chunked reads. Now just one query left:
What sorts ought to I help?
The reply is straightforward: it relies upon. Like actually. These days I began to ditch multipart encoding and I desire to speak with my API utilizing REST (JSON) and add information as uncooked HTTP physique. I by no means actually needed to help URL encoding, as a result of in the event you submit HTML types, you will ultimately face the necessity of file add and that will not work with URL encoded types, however solely with multipart.
In conclusion I would say that the excellent news is that we have now loads of alternatives and if you wish to present help for many of those sorts you do not have to reinvent the wheel in any respect. The multipart-kit library is constructed into Vapor 4, however that is one of many causes I began to love Hummingbird a bit extra, as a result of I can solely embody what I really want. Anyway, competitors is an effective factor to have on this case, as a result of hopefully each frameworks will evolve for good… 🙃