Routing on the server facet means the server goes to ship a response primarily based on the URL path that the shopper referred to as when firing up the HTTP request. In fact the server can verify further parameters and headers to construct the ultimate response, however once we speak about routing generally, we normally seek advice from the trail elements. Hummingbird makes use of a trie-based router, which is a quick and environment friendly manner of wanting up routes. It is fairly easy to reply to HTTP request utilizing the built-in router, you’ll be able to merely add your primary route handlers like this:
router.on("foo", methodology: .HEAD) { _ -> HTTPResponseStatus in .okay }
router.on("foo", methodology: .GET) { _ -> HTTPResponseStatus in .okay }
router.on("foo", methodology: .POST) { _ -> HTTPResponseStatus in .okay }
router.on("foo", methodology: .PUT) { _ -> HTTPResponseStatus in .okay }
router.on("foo", methodology: .PATCH) { _ -> HTTPResponseStatus in .okay }
router.on("foo", methodology: .DELETE) { _ -> HTTPResponseStatus in .okay }
router.head("foo") { _ -> HTTPResponseStatus in .okay }
router.get("foo") { _ -> HTTPResponseStatus in .okay }
router.put("foo") { _ -> HTTPResponseStatus in .okay }
router.publish("foo") { _ -> HTTPResponseStatus in .okay }
router.patch("foo") { _ -> HTTPResponseStatus in .okay }
router.delete("foo") { _ -> HTTPResponseStatus in .okay }
In Hummingbird it’s also attainable to register use a perform as a substitute of a block. Handler features may be async and throwing too, so you’ll be able to mark the blocks with these key phrases or use asynchronous Swift features when registering route handlers. When you do not present the primary parameter, the trail as a string, the route handler goes to be connected to the bottom group. 👍
You can too prefix a path element with a colon, this can flip that element right into a dynamic route parameter. The parameter goes to be named after the trail element, by merely dropping the colon prefix. You possibly can entry parameters inside your route handler by means of the req.parameters property. It is usually attainable to register a number of elements utilizing a / character.
public extension HBApplication {
func configure() throws {
router.get { _ async throws in "Good day, world!" }
router.get("howdy/:title") { req throws in
guard let title = req.parameters.get("title") else {
throw HBHTTPError(
.badRequest,
message: "Invalid title parameter."
)
}
return "Good day, (title)!"
}
let group = router.group("todos")
group.get(use: checklist)
group.publish(use: create)
let idGroup = group.group(":todoId")
idGroup.head(use: verify)
idGroup.get(use: fetch)
idGroup.put(use: replace)
idGroup.patch(use: patch)
idGroup.delete(use: delete)
router.group("todos")
.get(use: checklist)
.publish(use: create)
.group(":todoId")
.head(use: verify)
.get(use: fetch)
.put(use: replace)
.patch(use: patch)
.delete(use: delete)
}
func checklist(_ req: HBRequest) async throws -> HTTPResponseStatus { .okay }
func verify(_ req: HBRequest) async throws -> HTTPResponseStatus { .okay }
func fetch(_ req: HBRequest) async throws -> HTTPResponseStatus { .okay }
func create(_ req: HBRequest) async throws -> HTTPResponseStatus { .okay }
func replace(_ req: HBRequest) async throws -> HTTPResponseStatus { .okay }
func patch(_ req: HBRequest) async throws -> HTTPResponseStatus { .okay }
func delete(_ req: HBRequest) async throws -> HTTPResponseStatus { .okay }
}
It’s attainable to make use of a wildcard character () when detecting path elements and the recursive model (*) to catch all the pieces. Additionally you need to use the ${title} syntax to catch a named request parameter even with a prefix or suffix, however you’ll be able to’t insert this in the midst of a path element. (e.g. “prefix-${title}.jpg” will not work, however “${title}.jpg” is simply wonderful) 💡
import Hummingbird
import HummingbirdFoundation
extension HBApplication {
func configure(_ args: AppArguments) throws {
router.get("foo-${title}", use: catchPrefix)
router.get("${title}.jpg", use: catchSuffix)
router.get("*", use: catchOne)
router.get("*/*", use: catchTwo)
router.get("**", use: catchAll)
}
func catchOne(_ req: HBRequest) async throws -> String {
"one"
}
func catchTwo(_ req: HBRequest) async throws -> String {
"two"
}
func catchAll(_ req: HBRequest) async throws -> String {
"all: " + req.parameters.getCatchAll().joined(separator: ", ")
}
func catchPrefix(_ req: HBRequest) async throws -> String {
"prefix: " + (req.parameters.get("title") ?? "n/a")
}
func catchSuffix(_ req: HBRequest) async throws -> String {
"suffix: " + (req.parameters.get("title") ?? "n/a")
}
}
It is usually attainable to edit the auto-generated response for those who specify the .editResponse possibility.
router.get("foo", choices: .editResponse) { req -> String in
req.response.standing = .okay
req.response.headers.replaceOrAdd(
title: "Content material-Sort",
worth: "software/json"
)
return #"{"foo": "bar"}"#
}
Hummingbird help for physique streaming is wonderful, you’ll be able to stream a HTTP request physique by utilizing the .streamBody possibility. The physique stream has a sequence property, which you need to use to iterate by means of the incoming ByteBuffer chunks when dealing with the request. 🔄
func configure() throws {
router.publish("foo", choices: .streamBody) { req async throws -> String in
guard
let rawLength = req.headers["Content-Length"].first,
let size = Int(rawLength),
let stream = req.physique.stream
else {
throw HBHTTPError(
.badRequest,
message: "Lacking or invalid physique stream."
)
}
var rely: Int = 0
for attempt await chunk in stream.sequence {
rely += chunk.readableBytes
}
return String("(size) / (rely)")
}
}
let app = HBApplication(
configuration: .init(
handle: .hostname(hostname, port: port),
serverName: "Hummingbird",
maxUploadSize: 1 * 1024 * 1024 * 1024
)
)
As you’ll be able to see you’ll be able to simply entry all of the incoming headers by way of the req.headers container, you need to be aware that this methodology will return header values in a case-insensitive manner. If you wish to stream bigger information, you additionally should set a customized maxUploadSize utilizing the configuration object when initializing the HBApplication occasion.
curl -X POST http://localhost:8080/foo
-H "Content material-Size: 3"
--data-raw 'foo'
curl -X POST http://localhost:8080/foo
-H "content-Size: 5242880"
-T ~/check
You possibly can check out streaming with a easy cURL script, be happy to experiment with these.
One other factor I would like to indicate you is how you can entry question parameters and different properties utilizing the request object. Right here is an all-in-one instance, which you need to use as a cheatsheet… 😉
router.get("bar") { req async throws -> String in
struct Foo: Codable {
var a: String
}
print(req.methodology)
print(req.headers)
print(req.headers["accept"])
print(req.uri.queryParameters.get("q") ?? "n/a")
print(req.uri.queryParameters.get("key", as: Int.self) ?? 0)
if let buffer = req.physique.buffer {
let foo = attempt? JSONDecoder().decode(Foo.self, from: buffer)
print(foo ?? "n/a")
}
return "Good day, world!"
}
Anyway, there may be one further tremendous cool characteristic in Hummingbird that I would like to indicate you. It’s attainable to outline a route handler, this fashion you’ll be able to encapsulate all the pieces right into a single object. There may be an async model of the route handler protocol, for those who do not want async, you’ll be able to merely drop the key phrase each from the protocol title & the tactic. I really like this method lots. 😍
struct MyRouteHandler: HBAsyncRouteHandler {
struct Enter: Decodable {
let foo: String
}
struct Output: HBResponseEncodable {
let id: String
let foo: String
}
let enter: Enter
init(from request: HBRequest) throws {
self.enter = attempt request.decode(as: Enter.self)
}
func deal with(request: HBRequest) async throws -> Output {
.init(
id: "id-1",
foo: enter.foo
)
}
}
The request.decode methodology makes use of the built-in decoder, which it’s important to explicitly set for the applying, since we’ll talk utilizing JSON information, we will use the JSON encoder / decoder from Basis to robotically remodel the info.
As a way to make use of the customized route handler, you’ll be able to merely register the item kind.
import Hummingbird
import HummingbirdFoundation
public extension HBApplication {
func configure() throws {
encoder = JSONEncoder()
decoder = JSONDecoder()
router.publish("foo", use: MyRouteHandler.self)
}
}
You possibly can learn extra about how the encoding and decoding works in Hummingbird, however possibly that subject deserves its personal weblog publish. When you have questions or solutions, be happy to contact me. 🙈