Hummingbird routing and requests – The.Swift.Dev.


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. After all the server can test extra parameters and headers to construct the ultimate response, however once we speak about routing typically, we normally discuss with the trail elements. 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 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.submit("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 operate as a substitute of a block. Handler capabilities might 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. For those who do not present the primary parameter, the trail as a string, the route handler goes to be connected to the bottom group. 👍

You may as well prefix a path element with a colon, it will 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 may entry parameters inside your route handler by the req.parameters property. It is usually potential to register a number of elements utilizing a / character.

public extension HBApplication {
    
    func configure() throws {

        router.get { _ async throws in "Hiya, world!" }

        router.get("whats up/:identify") { req throws in
            guard let identify = req.parameters.get("identify") else {
                throw HBHTTPError(
                    .badRequest,
                    message: "Invalid identify parameter."
                )
            }
            return "Hiya, (identify)!"
        }

        let group = router.group("todos")
        group.get(use: listing)
        group.submit(use: create)
        
        let idGroup = group.group(":todoId")
        idGroup.head(use: test)
        idGroup.get(use: fetch)
        idGroup.put(use: replace)
        idGroup.patch(use: patch)
        idGroup.delete(use: delete)

        
        router.group("todos")
            .get(use: listing)
            .submit(use: create)
            .group(":todoId")
                .head(use: test)
                .get(use: fetch)
                .put(use: replace)
                .patch(use: patch)
                .delete(use: delete)

    }

    func listing(_ req: HBRequest) async throws -> HTTPResponseStatus { .okay }
    func test(_ 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 elements and the recursive model (*) to catch every thing. Additionally you should use the ${identify} syntax to catch a named request parameter even with a prefix or suffix, however you’ll be able to’t insert this in the course of a path element. (e.g. “prefix-${identify}.jpg” will not work, however “${identify}.jpg” is simply superb) 💡

import Hummingbird
import HummingbirdFoundation

extension HBApplication {

    func configure(_ args: AppArguments) throws {

        router.get("foo-${identify}", use: catchPrefix)
        router.get("${identify}.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("identify") ?? "n/a")
    }
    
    
    func catchSuffix(_ req: HBRequest) async throws -> String {
        "suffix: " + (req.parameters.get("identify") ?? "n/a")
    }
}

It is usually potential to edit the auto-generated response in the event you specify the .editResponse possibility.

router.get("foo", choices: .editResponse) { req -> String in
    req.response.standing = .okay
    req.response.headers.replaceOrAdd(
        identify: "Content material-Kind", 
        worth: "software/json"
    )
    return #"{"foo": "bar"}"#
}

Hummingbird assist 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 should use to iterate by the incoming ByteBuffer chunks when dealing with the request. 🔄

func configure() throws { 
    router.submit("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(
        tackle: .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, you need to notice that this methodology will return header values in a case-insensitive manner. If you wish to stream bigger information, 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 ~/check

You may check out streaming with a easy cURL script, be happy to experiment with these.

One other factor I might like to point out you is how one can entry question parameters and different properties utilizing the request object. Right here is an all-in-one instance, which you should 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 "Hiya, world!"
}

Anyway, there may be one extra tremendous cool characteristic in Hummingbird that I might 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, in the event you do not want async, you’ll be able to merely drop the key phrase each from the protocol identify & the tactic. I like this strategy 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 mechanically remodel the info.

With a purpose to make use of the customized route handler, you’ll be able to merely register the article sort.

import Hummingbird
import HummingbirdFoundation

public extension HBApplication {

    func configure() throws {
        
        encoder = JSONEncoder()
        decoder = JSONDecoder()
                
        
        router.submit("foo", use: MyRouteHandler.self)
    }
}

You may learn extra about how the encoding and decoding works in Hummingbird, however perhaps that matter deserves its personal weblog submit. When you’ve got questions or ideas, be happy to contact me. 🙈

Recent Articles

Related Stories

Leave A Reply

Please enter your comment!
Please enter your name here

Stay on op - Ge the daily news in your inbox