iOS Development

Hummingbird routing and requests – The.Swift.Dev.

Written by admin



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 extra parameters and headers to construct the ultimate response, however once we speak about routing normally, we often consult with the trail elements. Hummingbird makes use of a trie-based router, which is a quick and environment friendly approach of trying 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", 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.submit("foo") { _ -> HTTPResponseStatus in .okay }
router.patch("foo") { _ -> HTTPResponseStatus in .okay }
router.delete("foo") { _ -> HTTPResponseStatus in .okay }


In Hummingbird additionally it is potential to register use a perform as an alternative of a block. Handler features could 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. Should 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 element with a colon, this may 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 way of the req.parameters property. Additionally it is potential to register a number of elements utilizing a / character.


public extension HBApplication {
    
    func configure() throws {

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

        router.get("hi there/:title") { req throws in
            guard let title = req.parameters.get("title") else {
                throw HBHTTPError(
                    .badRequest,
                    message: "Invalid title parameter."
                )
            }
            return "Hi there, (title)!"
        }

        let group = router.group("todos")
        group.get(use: listing)
        group.submit(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: listing)
            .submit(use: create)
            .group(":todoId")
                .head(use: verify)
                .get(use: fetch)
                .put(use: replace)
                .patch(use: patch)
                .delete(use: delete)

    }

    func listing(_ 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 elements, however there is no such thing as a catch-all sample simply but, so if you wish to deal with a number of stage of elements, it’s important 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
    }
}


Additionally it is potential to edit the auto-generated response when you specify the .editResponse choice.


router.get("foo", choices: .editResponse) { req -> String in
    req.response.standing = .okay
    req.response.headers.replaceOrAdd(
        title: "Content material-Kind", 
        worth: "utility/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 choice. The physique stream has a sequence property, which you should use to iterate by way of 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 depend: Int = 0
        for attempt await chunk in stream.sequence {
            depend += chunk.readableBytes
        }
        return String("(size) / (depend)")
    }
}


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 by way of the req.headers container, it is best to notice that this technique will return header values in a case-insensitive approach. If you wish to stream bigger information, you additionally need to 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 at liberty to experiment with these.


One other factor I would like to point out you is 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.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 "Hi there, world!"
}


Anyway, there may be one extra tremendous cool function in Hummingbird that I would like to point out you. It’s potential to outline a route handler, this fashion you’ll be able to encapsulate all the things right into a single object. There may be an async model of the route handler protocol, when you do not want async, you’ll be able to merely drop the key phrase each from the protocol title & the strategy. I really like this strategy loads. 😍


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 it’s important to explicitly set for the applying, since we will talk utilizing JSON knowledge, we are able to use the JSON encoder / decoder from Basis to mechanically rework the info.


In an effort to make use of the customized route handler, you’ll be able to merely register the thing sort.


import Hummingbird
import HummingbirdFoundation

public extension HBApplication {

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


You possibly can learn extra about how the encoding and decoding works in Hummingbird, however perhaps that subject deserves its personal weblog submit. In case you have questions or strategies, be at liberty to contact me. 🙈



About the author

admin

Leave a Comment