From 8856560c874ef60af340a348c7df744f147c23fa Mon Sep 17 00:00:00 2001 From: Benoit BRIATTE Date: Sat, 3 Jun 2017 17:53:21 +0200 Subject: [PATCH] add chaining method pattern using binder classes --- README.md | 29 ++--- Sources/CompositeMiddlewareBinder.swift | 39 ++++++ Sources/Middleware.swift | 8 -- Sources/MiddlewareBinder.swift | 33 +++++ Sources/MiddlewareIterator.swift | 10 +- Sources/MiddlewareRegistry.swift | 34 ++++++ Sources/MiddlewareWrapper.swift | 10 +- Sources/RouteContext.swift | 8 -- Sources/RouteMiddlewareBinder.swift | 35 ++++++ Sources/RouterMiddleware.swift | 153 ++++++++---------------- 10 files changed, 205 insertions(+), 154 deletions(-) create mode 100644 Sources/CompositeMiddlewareBinder.swift create mode 100644 Sources/MiddlewareBinder.swift create mode 100644 Sources/MiddlewareRegistry.swift create mode 100644 Sources/RouteMiddlewareBinder.swift diff --git a/README.md b/README.md index cd12509..7953ef5 100644 --- a/README.md +++ b/README.md @@ -17,7 +17,7 @@ import PackageDescription let package = Package( name: "XXX", dependencies: [ - .Package(url: "https://github.com/Digipolitan/perfect-middleware-swift.git", majorVersion: 1, minor: 0) + .Package(url: "https://github.com/Digipolitan/perfect-middleware-swift.git", majorVersion: 1) ] ) ``` @@ -31,7 +31,7 @@ let server = HTTPServer() let router = RouterMiddleware() -router.get(path: "/") { (context) in +router.get(path: "/").bind { (context) in context.response.setBody(string: "It Works !").completed() context.next() } @@ -51,18 +51,18 @@ do { Passing data between middlewares, you can provide 2 or more middleware for the same route and shared data across each middleware using the context ```swift -router.get(path: "/") { (context) in +router.get(path: "/") + .bind { (context) in context["name"] = "Steve" context.next() -} - -router.get(path: "/") { (context) in + } + .bind { (context) in guard let name = context["name"] as? String else { return } context.response.setBody(string: "hello mr. \(name)!").completed() context.next() -} + } ``` It's possible to create and register Middleware subsclasses instead of closures @@ -80,15 +80,15 @@ class RandomMiddleware: Middleware { Register Middleware as follow : ```swift -router.post(path: "/random", middleware: RandomMiddleware()) - -router.post(path: "/random") { (context) in +router.post(path: "/random") + .bind(RandomMiddleware()) + .bind { (context) in guard let rand = context["rand"] as? UInt32 else { return } context.response.setBody(string: "the result \(rand)").completed() context.next() -} + } ``` ## Advanced @@ -140,12 +140,13 @@ let router = RouterMiddleware() let childRouter = RouterMiddleware() -childRouter.get(path: "/name") { (context) in +childRouter.get(path: "/name") + .bind { (context) in context.response.setBody(string: "My name is").completed() context.next() -} + } -router.use(path: "/user", router: childRouter) +router.use(path: "/user", child: childRouter) server.use(router: router) ``` diff --git a/Sources/CompositeMiddlewareBinder.swift b/Sources/CompositeMiddlewareBinder.swift new file mode 100644 index 0000000..ff1bcaa --- /dev/null +++ b/Sources/CompositeMiddlewareBinder.swift @@ -0,0 +1,39 @@ +// +// Created by Benoit BRIATTE on 03/06/2017. +// + +/** + * Composite MiddlewareBinder back an array of MiddlewareBinder + * This class is used when the users want to bind the same middleware to 2 or more HTTPMethod in the same time + * @author Benoit BRIATTE http://www.digipolitan.com + * @copyright 2017 Digipolitan. All rights reserved. + */ +internal class CompositeMiddlewareBinder: MiddlewareBinder { + + public let children: [MiddlewareBinder] + + public init(children: [MiddlewareBinder]) { + self.children = children + } + + @discardableResult + public func bind(_ middleware: Middleware) -> Self { + self.children.forEach { child in + child.bind(middleware) + } + return self + } + + @discardableResult + public func bind(_ middlewares: [Middleware]) -> Self { + self.children.forEach { child in + child.bind(middlewares) + } + return self + } + + @discardableResult + public func bind(_ handler: @escaping MiddlewareHandler) -> Self { + return self.bind(MiddlewareWrapper(handler: handler)) + } +} \ No newline at end of file diff --git a/Sources/Middleware.swift b/Sources/Middleware.swift index ca9670d..2f37d7c 100644 --- a/Sources/Middleware.swift +++ b/Sources/Middleware.swift @@ -1,11 +1,3 @@ -// -// Middleware.swift -// PerfectMiddleware -// -// Created by Benoit BRIATTE on 17/04/2017. -// -// - import PerfectHTTP /** diff --git a/Sources/MiddlewareBinder.swift b/Sources/MiddlewareBinder.swift new file mode 100644 index 0000000..1d197f8 --- /dev/null +++ b/Sources/MiddlewareBinder.swift @@ -0,0 +1,33 @@ +import PerfectHTTP + +/** + * MiddlewareBinder protocol, is used to bind middlewares or handlers inside a route with a method chaining pattern + * @author Benoit BRIATTE http://www.digipolitan.com + * @copyright 2017 Digipolitan. All rights reserved. + */ +public protocol MiddlewareBinder { + + /** + * Register a middleware + * @param middleware The middleware + * @return MiddlewareBinder + */ + @discardableResult + func bind(_ middleware: Middleware) -> Self + + /** + * Register an array of middleware + * @param middlewares The array + * @return MiddlewareBinder + */ + @discardableResult + func bind(_ middlewares: [Middleware]) -> Self + + /** + * Register a closure + * @param handler The handler + * @return MiddlewareBinder + */ + @discardableResult + func bind(_ handler: @escaping MiddlewareHandler) -> Self +} \ No newline at end of file diff --git a/Sources/MiddlewareIterator.swift b/Sources/MiddlewareIterator.swift index dc1f765..a014524 100644 --- a/Sources/MiddlewareIterator.swift +++ b/Sources/MiddlewareIterator.swift @@ -1,11 +1,3 @@ -// -// MiddlewareIterator.swift -// PerfectMiddleware -// -// Created by Benoit BRIATTE on 20/04/2017. -// -// - import PerfectHTTP /** @@ -14,7 +6,7 @@ import PerfectHTTP * @author Benoit BRIATTE http://www.digipolitan.com * @copyright 2017 Digipolitan. All rights reserved. */ -class MiddlewareIterator: RouteContext { +internal class MiddlewareIterator: RouteContext { public let request: HTTPRequest public let response: HTTPResponse diff --git a/Sources/MiddlewareRegistry.swift b/Sources/MiddlewareRegistry.swift new file mode 100644 index 0000000..89e1df7 --- /dev/null +++ b/Sources/MiddlewareRegistry.swift @@ -0,0 +1,34 @@ +import PerfectHTTP + +/** + * Registry of RouteMiddlewareBinder, back all the route binding of the RouterMiddleware + * @author Benoit BRIATTE http://www.digipolitan.com + * @copyright 2017 Digipolitan. All rights reserved. + */ +internal class RouteMiddlewareRegistry { + + public private(set) var binders: [HTTPMethod: [String: RouteMiddlewareBinder]] + + public init() { + self.binders = [HTTPMethod: [String: RouteMiddlewareBinder]]() + } + + /** + * Find or create a RouteMiddlewareBinder + * @param method The HTTPMethod + * @param path The route path + * @return Old RouteMiddlewareBinder with the combination of HTTPMethod / path otherwise a new one + */ + public func findOrCreate(method: HTTPMethod, path: String) -> RouteMiddlewareBinder { + let sanitizePath = RouterMiddleware.sanitize(path: path) + if self.binders[method] == nil { + self.binders[method] = [String: RouteMiddlewareBinder]() + } + if let binder = self.binders[method]![sanitizePath] { + return binder + } + let binder = RouteMiddlewareBinder() + self.binders[method]![sanitizePath] = binder + return binder + } +} \ No newline at end of file diff --git a/Sources/MiddlewareWrapper.swift b/Sources/MiddlewareWrapper.swift index efd73bd..f6375d4 100644 --- a/Sources/MiddlewareWrapper.swift +++ b/Sources/MiddlewareWrapper.swift @@ -1,11 +1,3 @@ -// -// MiddlewareWrapper.swift -// PerfectMiddleware -// -// Created by Benoit BRIATTE on 17/04/2017. -// -// - import PerfectHTTP /** @@ -13,7 +5,7 @@ import PerfectHTTP * @author Benoit BRIATTE http://www.digipolitan.com * @copyright 2017 Digipolitan. All rights reserved. */ -class MiddlewareWrapper: Middleware { +internal class MiddlewareWrapper: Middleware { private let handler: MiddlewareHandler diff --git a/Sources/RouteContext.swift b/Sources/RouteContext.swift index 9793c06..18b8ec7 100644 --- a/Sources/RouteContext.swift +++ b/Sources/RouteContext.swift @@ -1,11 +1,3 @@ -// -// RouteContext.swift -// PerfectMiddleware -// -// Created by Benoit BRIATTE on 17/04/2017. -// -// - import PerfectHTTP /** diff --git a/Sources/RouteMiddlewareBinder.swift b/Sources/RouteMiddlewareBinder.swift new file mode 100644 index 0000000..5c5c276 --- /dev/null +++ b/Sources/RouteMiddlewareBinder.swift @@ -0,0 +1,35 @@ +// +// Created by Benoit BRIATTE on 03/06/2017. +// + +import Foundation + +/** + * Default implementation of the MiddlewareBinder + * Allows the user to register middleware and handler inside an array + */ +internal class RouteMiddlewareBinder: MiddlewareBinder { + + public var middlewares: [Middleware] + + public init() { + self.middlewares = [] + } + + @discardableResult + public func bind(_ middleware: Middleware) -> Self { + self.middlewares.append(middleware) + return self + } + + @discardableResult + public func bind(_ middlewares: [Middleware]) -> Self { + self.middlewares.append(contentsOf: middlewares) + return self + } + + @discardableResult + public func bind(_ handler: @escaping MiddlewareHandler) -> Self { + return self.bind(MiddlewareWrapper(handler: handler)) + } +} \ No newline at end of file diff --git a/Sources/RouterMiddleware.swift b/Sources/RouterMiddleware.swift index 4258269..2003001 100644 --- a/Sources/RouterMiddleware.swift +++ b/Sources/RouterMiddleware.swift @@ -1,11 +1,3 @@ -// -// RouterMiddleware.swift -// PerfectMiddleware -// -// Created by Benoit BRIATTE on 20/04/2017. -// -// - import PerfectLib import PerfectHTTP import PerfectHTTPServer @@ -33,10 +25,27 @@ open class RouterMiddleware { private var afterAll: [Middleware] private var notFound: Middleware? private var children: [String: RouterMiddleware] - private var registry: RoutesRegistry + private var registry: RouteMiddlewareRegistry private var errorHandler: ErrorHandler? private var verbose: Bool; + public static func sanitize(path: String) -> String { + var characters = path.characters + guard characters.count > 0 && path != "/" else { + return "/" + } + let last = characters.endIndex + let separator = Character(UnicodeScalar(47)) + if characters[characters.index(before: last)] == separator { + characters.removeLast() + } + let first = characters.startIndex + if characters[first] != separator { + characters.insert(separator, at: first) + } + return String(characters) + } + public convenience init() { self.init(verbose: false) } @@ -45,7 +54,7 @@ open class RouterMiddleware { self.beforeAll = [Middleware]() self.afterAll = [Middleware]() self.children = [String: RouterMiddleware]() - self.registry = RoutesRegistry() + self.registry = RouteMiddlewareRegistry() self.verbose = verbose } @@ -74,77 +83,39 @@ open class RouterMiddleware { } @discardableResult - public func use(path: String, router: RouterMiddleware) -> Self { - self.children[RoutesRegistry.sanitize(path: path)] = router - return self - } - - @discardableResult - public func get(path: String, handler: @escaping MiddlewareHandler) -> Self { - return self.get(path: path, middleware: MiddlewareWrapper(handler: handler)) - } - - @discardableResult - public func get(path: String, middleware: Middleware) -> Self { - self.registry.add(method: .get, path: path, middleware: middleware) + public func use(path: String, child router: RouterMiddleware) -> Self { + self.children[RouterMiddleware.sanitize(path: path)] = router return self } - @discardableResult - public func post(path: String, handler: @escaping MiddlewareHandler) -> Self { - return self.post(path: path, middleware: MiddlewareWrapper(handler: handler)) + public func get(path: String) -> MiddlewareBinder { + return self.binder(method: .get, path: path) } - @discardableResult - public func post(path: String, middleware: Middleware) -> Self { - self.registry.add(method: .post, path: path, middleware: middleware) - return self + public func post(path: String) -> MiddlewareBinder { + return self.binder(method: .post, path: path) } - @discardableResult - public func put(path: String, handler: @escaping MiddlewareHandler) -> Self { - return self.put(path: path, middleware: MiddlewareWrapper(handler: handler)) + public func put(path: String) -> MiddlewareBinder { + return self.binder(method: .put, path: path) } - @discardableResult - public func put(path: String, middleware: Middleware) -> Self { - self.registry.add(method: .put, path: path, middleware: middleware) - return self + public func delete(path: String) -> MiddlewareBinder { + return self.binder(method: .delete, path: path) } - @discardableResult - public func delete(path: String, handler: @escaping MiddlewareHandler) -> Self { - return self.delete(path: path, middleware: MiddlewareWrapper(handler: handler)) + public func options(path: String) -> MiddlewareBinder { + return self.binder(method: .options, path: path) } - @discardableResult - public func delete(path: String, middleware: Middleware) -> Self { - self.registry.add(method: .delete, path: path, middleware: middleware) - return self + public func methods(_ methods: [HTTPMethod], path: String) -> MiddlewareBinder { + return CompositeMiddlewareBinder(children: methods.map { method -> MiddlewareBinder in + return self.binder(method: method, path: path) + }) } - @discardableResult - public func options(path: String, handler: @escaping MiddlewareHandler) -> Self { - return self.options(path: path, middleware: MiddlewareWrapper(handler: handler)) - } - - @discardableResult - public func options(path: String, middleware: Middleware) -> Self { - self.registry.add(method: .options, path: path, middleware: middleware) - return self - } - - @discardableResult - public func methods(_ methods: [HTTPMethod], path: String, handler: @escaping MiddlewareHandler) -> Self { - return self.methods(methods, path: path, middleware: MiddlewareWrapper(handler: handler)) - } - - @discardableResult - public func methods(_ methods: [HTTPMethod], path: String, middleware: Middleware) -> Self { - methods.forEach { (method) in - self.registry.add(method: method, path: path, middleware: middleware) - } - return self + public func binder(method: HTTPMethod, path: String) -> MiddlewareBinder { + return self.registry.findOrCreate(method: method, path: path) } fileprivate func getRoutes(path: String = "", beforeAll: [Middleware] = [], afterAll: [Middleware] = [], verbose: Bool = false, errorHandler: ErrorHandler? = nil) -> Routes { @@ -156,9 +127,9 @@ open class RouterMiddleware { let curErrorHandler = (self.errorHandler != nil) ? self.errorHandler : errorHandler let curVerbose = (verbose == true) ? verbose : self.verbose - self.registry.routes.forEach { (method: HTTPMethod, value: [String : [Middleware]]) in - value.forEach({ (key: String, value: [Middleware]) in - let middlewares = depthBeforeAll + value + depthAfterAll + self.registry.binders.forEach { (method: HTTPMethod, value: [String : RouteMiddlewareBinder]) in + value.forEach({ (key: String, value: RouteMiddlewareBinder) in + let middlewares = depthBeforeAll + value.middlewares + depthAfterAll if curVerbose { Log.info(message: "HTTP Server listen \(method.description) on \(path)\(key)") } @@ -199,46 +170,16 @@ open class RouterMiddleware { } } +/** + * Add RouterMiddleware supports for HTTPServer + */ public extension HTTPServer { + /** + * Register the router inside the HttpServer by adding all routes + * @param router The router midddleware to register + */ public func use(router: RouterMiddleware) { self.addRoutes(router.getRoutes()) } } - -fileprivate class RoutesRegistry { - - fileprivate private(set) var routes: [HTTPMethod: [String: [Middleware]]] - - public init() { - self.routes = [HTTPMethod: [String: [Middleware]]]() - } - - public func add(method: HTTPMethod, path: String, middleware: Middleware) { - let sanitizePath = RoutesRegistry.sanitize(path: path) - if self.routes[method] == nil { - self.routes[method] = [String: [Middleware]]() - } - if self.routes[method]![sanitizePath] == nil { - self.routes[method]![sanitizePath] = [Middleware]() - } - self.routes[method]![sanitizePath]!.append(middleware) - } - - static fileprivate func sanitize(path: String) -> String { - var characters = path.characters - guard characters.count > 0 && path != "/" else { - return "/" - } - let last = characters.endIndex - let separator = Character(UnicodeScalar(47)) - if characters[characters.index(before: last)] == separator { - characters.removeLast() - } - let first = characters.startIndex - if characters[first] != separator { - characters.insert(separator, at: first) - } - return String(characters) - } -}