Routing on the server aspect 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 discuss routing normally, we normally discuss with the trail parts. Hummingbird makes use of a trie-based router, which is a quick and environment friendly manner of trying up routes. It is fairly easy to answer HTTP request utilizing the built-in router, you’ll be able to merely add your fundamental route handlers like this:
router.on("foo", technique: .HEAD) { _ -> HTTPResponseStatus in .okay }
router.on("foo", technique: .GET) { _ -> HTTPResponseStatus in .okay }
router.on("foo", technique: .POST) { _ -> HTTPResponseStatus in .okay }
router.on("foo", technique: .PUT) { _ -> HTTPResponseStatus in .okay }
router.on("foo", technique: .PATCH) { _ -> HTTPResponseStatus in .okay }
router.on("foo", technique: .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 is usually potential to register use a perform as an alternative of a block. Handler capabilities could be async and throwing too, so you’ll be able to mark the blocks with these key phrases or use asynchronous Swift capabilities 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 may also prefix a path part with a colon, this can flip that part right into a dynamic route parameter. The parameter goes to be named after the trail part, by merely dropping the colon prefix. You’ll be able to entry parameters inside your route handler by means of the req.parameters
property. It is usually potential to register a number of parts utilizing a / character.
public extension HBApplication {
func configure() throws {
router.get { _ async throws in "Hiya, world!" }
router.get("howdy/:title") { req throws in
guard let title = req.parameters.get("title") else {
throw HBHTTPError(
.badRequest,
message: "Invalid title parameter."
)
}
return "Hiya, (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 potential to make use of a wildcard character when detecting path parts, however there isn’t a catch-all sample simply but, so if you wish to deal with a number of degree of parts, you need to register a route handler like this (e.g. /foo
, /foo/bar
, /foo/bar/baz
will work, however /a/b/c/d
will not 😢):
import Hummingbird
import HummingbirdFoundation
public extension HBApplication {
func configure() throws {
router.get("*", use: catchAll)
router.get("*/*", use: catchAll)
router.get("*/*/*", use: catchAll)
}
func catchAll(_ req: HBRequest) async throws -> String {
return req.uri.path
}
}
It is usually potential to edit the auto-generated response should you specify the .editResponse
choice.
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 assist for physique streaming is superb, you’ll be able to stream a HTTP request physique by utilizing the .streamBody
choice. 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 depend: Int = 0
for attempt await chunk in stream.sequence {
depend += chunk.readableBytes
}
return String("(size) / (depend)")
}
}
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 through the req.headers
container, it’s best to notice that this technique will return header values in a case-insensitive manner. If you wish to stream bigger recordsdata, you additionally must 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 ~/take a look at
You’ll be able to check out streaming with a easy cURL script, be happy to experiment with these.
One other factor I would like to point out you is the best way to 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.technique)
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 "Hiya, world!"
}
Anyway, there may be one further tremendous cool function in Hummingbird that I would like to point out you. It’s potential to outline a route handler, this manner you’ll be able to encapsulate every thing right into a single object. There may be an async model of the route handler protocol, should you 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 strategy so much. 😍
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
technique makes use of the built-in decoder, which you need to explicitly set for the applying, since we will talk utilizing JSON information, we are able to use the JSON encoder / decoder from Basis to mechanically remodel the information.
To be able to make use of the customized route handler, you’ll be able to merely register the item sort.
import Hummingbird
import HummingbirdFoundation
public extension HBApplication {
func configure() throws {
encoder = JSONEncoder()
decoder = JSONDecoder()
router.publish("foo", use: MyRouteHandler.self)
}
}
You’ll be able to learn extra about how the encoding and decoding works in Hummingbird, however perhaps that matter deserves its personal weblog publish. In case you have questions or options, be happy to contact me. 🙈