Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

lazy middleware #1507

Merged
merged 27 commits into from
Feb 20, 2018
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
27 commits
Select commit Hold shift + click to select a range
4ff3ddf
refactor RoutingGroups
MaximBazarov Dec 24, 2017
5313050
renamed validate to beforeEach to better describe itself
MaximBazarov Dec 24, 2017
7fbb830
renamed Validator to Closure since it's no longer validate
MaximBazarov Dec 24, 2017
4a6804e
renamed methods for adding middleware to using
MaximBazarov Dec 24, 2017
bfc710e
Merge branch 'beta' into beta
tanner0101 Dec 28, 2017
0f45bdc
fixed naming, added tests
MaximBazarov Dec 30, 2017
43307ca
renamed using to use for function and in-place usage
MaximBazarov Dec 30, 2017
46f3e45
updated usage examples
MaximBazarov Dec 30, 2017
ee9c15b
use renamed to using back since it is the best
MaximBazarov Dec 30, 2017
b5c075b
Merge branch 'beta' into beta
tanner0101 Jan 23, 2018
8a34ca2
Merge branch 'v-beta' into beta
MaximBazarov Feb 14, 2018
5c39b41
updated router groups
MaximBazarov Feb 14, 2018
59f90af
fixed tests
MaximBazarov Feb 14, 2018
7ade5b8
removed unnecessary space
MaximBazarov Feb 14, 2018
d2f8062
fixed linux tests
MaximBazarov Feb 14, 2018
55e48b0
docs updated
MaximBazarov Feb 15, 2018
b3a953b
Merge branch 'beta' into beta
MaximBazarov Feb 18, 2018
d88047f
fixed inline documentation
MaximBazarov Feb 18, 2018
edfe3ed
Merge branch 'beta' of github.com:MaximBazarov/vapor into beta
MaximBazarov Feb 18, 2018
3088c56
fix inline docs
MaximBazarov Feb 18, 2018
72a0428
lazy middleware
tanner0101 Feb 19, 2018
914e142
Merge branch 'beta' into beta
tanner0101 Feb 20, 2018
79ee422
Merge branch 'lazy-middleware' into beta
tanner0101 Feb 20, 2018
e6abf70
Merge pull request #1388 from MaximBazarov/beta
tanner0101 Feb 20, 2018
39fb810
organize router extensions
tanner0101 Feb 20, 2018
e9db7a1
fix tests
tanner0101 Feb 20, 2018
1012d2d
cleanup
tanner0101 Feb 20, 2018
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 9 additions & 3 deletions Sources/Development/main.swift
Original file line number Diff line number Diff line change
Expand Up @@ -53,16 +53,15 @@ do {

var middlewareConfig = MiddlewareConfig()
middlewareConfig.use(ErrorMiddleware.self)
// middlewareConfig.use(DateMiddleware.self)
// middlewareConfig.use(SessionsMiddleware.self)
services.register(middlewareConfig)


let dir = DirectoryConfig(workDir: "/Users/tanner/dev/vapor/vapor/Sources/Development/")
services.register(dir)

let app = try Application(environment: .detect(), services: services)

let router = try app.make(Router.self)
let router = EngineRouter.default()

router.get("search") { req -> String in
return try req.query.get(String.self, at: ["query"])
Expand Down Expand Up @@ -359,6 +358,13 @@ do {
// }.blockingAwait()
// print(foo)

router.grouped(DateMiddleware.self).get("datetest") { req in
return HTTPStatus.ok
}

services.register(Router.self) { _ in return router }

let app = try Application(environment: .detect(), services: services)
try app.run()
} catch {
print("Top Level Error: \(error)")
Expand Down
6 changes: 2 additions & 4 deletions Sources/Vapor/Commands/ServeCommand.swift
Original file line number Diff line number Diff line change
Expand Up @@ -14,16 +14,14 @@ public struct ServeCommand: Command, Service {

/// The server to boot.
public let server: Server
public let responder: Responder

/// Create a new serve command.
public init(server: Server, responder: Responder) {
public init(server: Server) {
self.server = server
self.responder = responder
}

/// See Runnable.run
public func run(using context: CommandContext) throws {
try server.start(with: responder)
try server.start()
}
}
10 changes: 6 additions & 4 deletions Sources/Vapor/Engine/EngineServer.swift
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ public final class EngineServer: Server, Service {
}

/// Start the server. Server protocol requirement.
public func start(with responder: Responder) throws {
public func start() throws {
let tcpServer = try TCPServer(socket: TCPSocket(isNonBlocking: true))
// leaking, probably because of client capturing itself in closure
// tcpServer.willAccept = PeerValidator(maxConnectionsPerIP: config.maxConnectionsPerIP).willAccept
Expand All @@ -40,10 +40,12 @@ public final class EngineServer: Server, Service {
for i in 1...config.workerCount {
let eventLoop = try DefaultEventLoop(label: "codes.vapor.engine.server.worker.\(i)")
let subContainer = self.container.subContainer(on: eventLoop)
let responder = EngineResponder(container: subContainer, responder: responder)
let subResponder = try subContainer.make(Responder.self, for: EngineServer.self)
let responder = EngineResponder(container: subContainer, responder: subResponder)
let acceptStream = tcpServer.stream(on: eventLoop).map(to: TCPSocketStream.self) {
$0.socket.stream(on: eventLoop) { _, error in
$0.socket.stream(on: eventLoop) { sink, error in
logger.reportError(error, as: "Server Error")
sink.close()
}
}

Expand Down Expand Up @@ -72,7 +74,7 @@ public final class EngineServer: Server, Service {
console.output("http://" + config.hostname, style: .init(color: .cyan), newLine: false)
console.output(":" + config.port.description, style: .init(color: .cyan))

while true { RunLoop.main.run() }
container.eventLoop.runLoop()
}

// private func startPlain(with responder: Responder) throws {
Expand Down
2 changes: 1 addition & 1 deletion Sources/Vapor/Engine/Server.swift
Original file line number Diff line number Diff line change
Expand Up @@ -4,5 +4,5 @@ import HTTP
/// and subsequently responding to requests sent
/// to that address.
public protocol Server {
func start(with responder: Responder) throws
func start() throws
}
16 changes: 14 additions & 2 deletions Sources/Vapor/Middleware/DateMiddleware.swift
Original file line number Diff line number Diff line change
Expand Up @@ -26,11 +26,16 @@ fileprivate let NUMBERS = [
fileprivate var cachedTimeComponents: (key: time_t, components: COperatingSystem.tm)?

let secondsInDay = 60 * 60 * 24
let accuracy: Int = 1 // seconds

/// Adds the RFC 1123 date to the response.
public final class DateMiddleware: Middleware, Service {
var cachedTimestamp: (timestamp: String, createdAt: time_t)?

/// Creates a new `DateMiddleware`
public init() { }


/// See `Middleware.respond(to:)`
public func respond(to request: Request, chainingTo next: Responder) throws -> Future<Response> {
let promise = Promise<Response>()

Expand All @@ -43,9 +48,14 @@ public final class DateMiddleware: Middleware, Service {

return promise.future
}


/// Gets the current RFC 1123 date string.
fileprivate func getDate() -> String {
var date = COperatingSystem.time(nil)

if let (timestamp, createdAt) = cachedTimestamp, createdAt <= date + accuracy {
return timestamp
}

// generate a key used for caching.
// this key is a unique id for each day
Expand Down Expand Up @@ -91,6 +101,8 @@ public final class DateMiddleware: Middleware, Service {
rfc1123.append(":")
rfc1123.append(NUMBERS[seconds])
rfc1123.append(" GMT")

cachedTimestamp = (rfc1123, date)

return rfc1123
}
Expand Down
66 changes: 66 additions & 0 deletions Sources/Vapor/Routing/LazyMiddlewareRouteGroup.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
extension Router {
/// Creates a group with the provided middleware type.
///
/// This middleware will be lazily initialized using the container upon
/// the first request that invokes it.
///
/// [Learn More →](https://docs.vapor.codes/3.0/vapor/route-group/#middleware)
public func grouped<M>(_ middleware: M.Type) -> Router where M: Middleware {
return LazyMiddlewareRouteGroup(M.self, cascadingTo: self)
}

/// Creates a group with the provided middleware type.
///
/// This middleware will be lazily initialized using the container upon
/// the first request that invokes it.
///
/// [Learn More →](https://docs.vapor.codes/3.0/vapor/route-group/#middleware)
public func group<M>(_ middleware: M.Type, configure: (Router) throws -> ()) rethrows where M: Middleware {
try configure(LazyMiddlewareRouteGroup(M.self, cascadingTo: self))
}
}

/// Responder wrapper around middleware type.
/// Lazily initializes the middleware upon request.
fileprivate struct LazyMiddlewareResponder<M>: Responder where M: Middleware {
/// The responder to chain to.
var responder: Responder

/// Creates a new `LazyMiddlewareResponder`
init(_ type: M.Type, chainingTo responder: Responder) {
self.responder = responder
}

/// See `Responder.respond(to:)`
func respond(to req: Request) throws -> Future<Response> {
return try req.make(M.self, for: Request.self)
.makeResponder(chainedTo: responder)
.respond(to: req)
}
}

/// Lazy initialized route group
fileprivate final class LazyMiddlewareRouteGroup<M>: Router where M: Middleware {
/// All routes registered to this group
private(set) var routes: [Route<Responder>] = []

/// The parent router.
let `super`: Router

/// Creates a new group
init(_ type: M.Type, cascadingTo router: Router) {
self.super = router
}

/// See `Router.register(route:)`
func register(route: Route<Responder>) {
self.routes.append(route)
route.output = LazyMiddlewareResponder(M.self, chainingTo: route.output)
self.super.register(route: route)
}

/// See `Router.route(request:)`
func route(request: Request) -> Responder? {
return self.super.route(request: request)
}
}
83 changes: 83 additions & 0 deletions Sources/Vapor/Routing/MiddlewareFunctionRouteGroup.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,83 @@
extension Router {
/// Returns a group cascading to router with function attached
///
///
/// **Example:**
///
///
/// We can create some authorization closure to check whether user is authorized
/// ```
/// let userMustBeAuthorized = { req, next in
/// // User is the user model class which can parse request
/// // and returns User instance or nil
/// guard User(from: req) != nil else { throw AuthError.unauthorized }
/// return next.respond(to: request)
/// }
/// ```
///
/// Then create new group on router
/// ```
/// let users = router.group("user")
/// .grouped(userMustBeAuthorized)
/// ```
///
/// And then users *group* will apply this closure to every request to check whether user is unauthorized or not
/// [Learn More →](https://docs.vapor.codes/3.0/vapor/route-group/#path-components)
///
/// - Parameter respond: `(request: Request, next: Responder) throws -> Future<Response>`
/// - Returns: RouterGroup with closure attached
public func grouped(_ respond: @escaping (Request, Responder) throws -> Future<Response>) -> Router {
return grouped([MiddlewareFunction(respond)])
}


/// *Ataches RouteGroup cascading to router with function as a middleware attached*
/// and call configuaration function with new group provided
///
/// **Example:**
///
/// First of all we need some function which implements authorization logic.
/// Function must returns `Future<Response>` to not breaking chaining of middlewares
/// It might be static or instance function, or just closure, it doesn't matter
/// ```
/// static func userMustBeAuthorized(request: Request, next: Responder) throws -> Future<Response> {
/// // User is the user model class which can parse request
/// // and returns User instance or nil
/// guard User(from: request) != nil else { throw AuthError.unauthorized }
/// return next.respond(to: request)
/// }
/// ```
///
/// Then we pass function as a parameter
/// ```
/// router.group(using: userMustBeAuthorized) { group in
/// group.get("profile", use: userProfileHandler)
/// }
/// ```
///
/// And this *router* will apply this function to every request to check whether user is unauthorized or not
///
/// [Learn More →](https://docs.vapor.codes/3.0/vapor/route-group/#path-components)
/// - Parameters:
/// - respond: respond: `(request: Request, next: Responder) throws -> Future<Response>`
/// - configure: Group configuration function
public func group(_ respond: @escaping (Request, Responder) throws -> Future<Response>, configure: (Router) -> ()) {
group([MiddlewareFunction(respond)], configure: configure)
}
}

/// Wrapper to create Middleware from function
fileprivate class MiddlewareFunction: Middleware {
/// Internal request handler.
private let respond: (Request, Responder) throws -> Future<Response>

/// Creates a new `MiddlewareFunction`
init(_ function: @escaping (Request, Responder) throws -> Future<Response>) {
self.respond = function
}

/// See `Middleware.respond(to:chainingTo:)`
func respond(to request: Request, chainingTo next: Responder) throws -> Future<Response> {
return try self.respond(request,next)
}
}
Loading