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

Add SendableRoute type #3100

Open
wants to merge 27 commits into
base: main
Choose a base branch
from
Open
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
326e450
Add SendableRoute type
0xTim Nov 6, 2023
f4a87e0
Add reference type dictionary
0xTim Nov 6, 2023
4285798
Migrate Responder over to Sendable route
0xTim Nov 6, 2023
a0cb01e
Migrate non-async code
0xTim Nov 6, 2023
ac431c9
Migrate async routes
0xTim Nov 6, 2023
e3e6cf2
Remove a deprecation warning
0xTim Nov 6, 2023
4aa82d9
Updates from latest RoutingKit release
0xTim Nov 6, 2023
6b02f69
Copy user info between route types
0xTim Nov 16, 2023
93e8b2d
Make responder stack async
0xTim Nov 30, 2023
5b67917
Force async route where we can
0xTim Nov 30, 2023
fa21a0c
Add clarification
0xTim Nov 30, 2023
8fc49ef
Make DefaultResponder Async
0xTim Nov 30, 2023
731165b
Set the async boudary as high as we can
0xTim Nov 30, 2023
913e62d
Fix some warnings and tests
0xTim Nov 30, 2023
e0b0945
Start making internal middlewares async
0xTim Dec 19, 2023
7f7de98
Add AsyncSessionsMiddleware
0xTim Dec 19, 2023
4012dee
Add tests for async sessions
0xTim Dec 19, 2023
a97d54b
Fix commented out code
0xTim Dec 19, 2023
df2edfd
Fix another warning
0xTim Dec 19, 2023
82eb32f
Bump SwiftNIO version as we use a new API
0xTim Dec 19, 2023
5424913
Remove old middleware as we push everyone to AsyncMiddleware
0xTim Dec 19, 2023
3c05c8a
Fix some Multipart Sendable warnings
0xTim Dec 19, 2023
b5a2923
Fix some warnings in tests
0xTim Dec 19, 2023
b77b312
Remove more test warnings
0xTim Dec 19, 2023
15e3d56
Merge AsyncTests into the main tests
0xTim Dec 19, 2023
cd1d4c1
Fix a load of test warnings
0xTim Dec 19, 2023
bf729c9
Update a test
0xTim Jan 12, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
6 changes: 1 addition & 5 deletions Package.swift
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@ let package = Package(

// Event-driven network application framework for high performance protocol servers & clients, non-blocking.
.package(url: "https://github.com/apple/swift-nio.git", from: "2.62.0"),

// Bindings to OpenSSL-compatible libraries for TLS support in SwiftNIO
.package(url: "https://github.com/apple/swift-nio-ssl.git", from: "2.8.0"),

Expand Down Expand Up @@ -120,9 +120,5 @@ let package = Package(
.copy("Utilities/expired.crt"),
.copy("Utilities/expired.key"),
]),
.testTarget(name: "AsyncTests", dependencies: [
.product(name: "NIOTestUtils", package: "swift-nio"),
.target(name: "XCTVapor"),
]),
]
)
13 changes: 1 addition & 12 deletions Package@swift-5.9.swift
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ let package = Package(

// Event-driven network application framework for high performance protocol servers & clients, non-blocking.
.package(url: "https://github.com/apple/swift-nio.git", from: "2.62.0"),

// Bindings to OpenSSL-compatible libraries for TLS support in SwiftNIO
.package(url: "https://github.com/apple/swift-nio-ssl.git", from: "2.8.0"),

Expand Down Expand Up @@ -133,16 +133,5 @@ let package = Package(
.enableExperimentalFeature("StrictConcurrency=complete"),
]
),
.testTarget(
name: "AsyncTests",
dependencies: [
.product(name: "NIOTestUtils", package: "swift-nio"),
.target(name: "XCTVapor"),
],
swiftSettings: [
.enableUpcomingFeature("BareSlashRegexLiterals"),
.enableExperimentalFeature("StrictConcurrency=complete"),
]
),
]
)
18 changes: 3 additions & 15 deletions Sources/Development/routes.swift
Original file line number Diff line number Diff line change
Expand Up @@ -132,7 +132,7 @@ public func routes(_ app: Application) throws {
}

let sessions = app.grouped("sessions")
.grouped(app.sessions.middleware)
.grouped(app.asyncSessions.middleware)
sessions.get("set", ":value") { req -> HTTPStatus in
req.session.data["name"] = req.parameters.get("value")
return .ok
Expand Down Expand Up @@ -273,8 +273,8 @@ public func routes(_ app: Application) throws {
}
asyncRoutes.get("opaque", use: opaqueRouteTester)

// Make sure jumping between multiple different types of middleware works
asyncRoutes.grouped(TestAsyncMiddleware(number: 2), TestMiddleware(number: 3), TestAsyncMiddleware(number: 4), TestMiddleware(number: 5)).get("middleware") { req async throws -> String in
// Make sure no warnings with async middleware
asyncRoutes.grouped(TestAsyncMiddleware(number: 2), TestAsyncMiddleware(number: 4), TestAsyncMiddleware(number: 5)).get("middleware") { req async throws -> String in
return "OK"
}

Expand Down Expand Up @@ -341,15 +341,3 @@ struct TestAsyncMiddleware: AsyncMiddleware {
return response
}
}

struct TestMiddleware: Middleware {
let number: Int

func respond(to request: Request, chainingTo next: Responder) -> EventLoopFuture<Response> {
request.logger.debug("In non-async middleware - \(number)")
return next.respond(to: request).map { response in
request.logger.debug("In non-async middleware way out - \(self.number)")
return response
}
}
}
2 changes: 2 additions & 0 deletions Sources/Vapor/Application.swift
Original file line number Diff line number Diff line change
Expand Up @@ -142,6 +142,8 @@ public final class Application: Sendable {
self.passwords.use(.bcrypt)
self.sessions.initialize()
self.sessions.use(.memory)
self.asyncSessions.initialize()
self.asyncSessions.use(.memory)
self.responder.initialize()
self.responder.use(.default)
self.servers.initialize()
Expand Down
10 changes: 5 additions & 5 deletions Sources/Vapor/Authentication/GuardMiddleware.swift
Original file line number Diff line number Diff line change
Expand Up @@ -19,14 +19,14 @@ extension Authenticatable {
/// - throwing: `Error` to throw if the type is not authed.
public static func guardMiddleware(
throwing error: Error = Abort(.unauthorized, reason: "\(Self.self) not authenticated.")
) -> Middleware {
) -> AsyncMiddleware {
return GuardAuthenticationMiddleware<Self>(throwing: error)
}
}



private final class GuardAuthenticationMiddleware<A>: Middleware
private final class GuardAuthenticationMiddleware<A>: AsyncMiddleware
where A: Authenticatable
{
/// Error to throw when guard fails.
Expand All @@ -41,10 +41,10 @@ private final class GuardAuthenticationMiddleware<A>: Middleware
self.error = error
}

public func respond(to request: Request, chainingTo next: Responder) -> EventLoopFuture<Response> {
func respond(to request: Request, chainingTo next: AsyncResponder) async throws -> Response {
guard request.auth.has(A.self) else {
return request.eventLoop.makeFailedFuture(self.error)
throw self.error
}
return next.respond(to: request)
return try await next.respond(to: request)
}
}
13 changes: 6 additions & 7 deletions Sources/Vapor/Authentication/RedirectMiddleware.swift
Original file line number Diff line number Diff line change
Expand Up @@ -5,21 +5,21 @@ extension Authenticatable {
///
/// - parameters:
/// - path: The path to redirect to if the request is not authenticated
public static func redirectMiddleware(path: String) -> Middleware {
public static func redirectMiddleware(path: String) -> AsyncMiddleware {
self.redirectMiddleware(makePath: { _ in path })
}

/// Basic middleware to redirect unauthenticated requests to the supplied path
///
/// - parameters:
/// - makePath: The closure that returns the redirect path based on the given `Request` object
@preconcurrency public static func redirectMiddleware(makePath: @Sendable @escaping (Request) -> String) -> Middleware {
@preconcurrency public static func redirectMiddleware(makePath: @Sendable @escaping (Request) -> String) -> AsyncMiddleware {
RedirectMiddleware<Self>(Self.self, makePath: makePath)
}
}


private final class RedirectMiddleware<A>: Middleware
private final class RedirectMiddleware<A>: AsyncMiddleware
where A: Authenticatable
{
let makePath: @Sendable (Request) -> String
Expand All @@ -28,12 +28,11 @@ private final class RedirectMiddleware<A>: Middleware
self.makePath = makePath
}

func respond(to request: Request, chainingTo next: Responder) -> EventLoopFuture<Response> {
func respond(to request: Request, chainingTo next: AsyncResponder) async throws -> Response {
if request.auth.has(A.self) {
return next.respond(to: request)
return try await next.respond(to: request)
}

let redirect = request.redirect(to: self.makePath(request))
return request.eventLoop.makeSucceededFuture(redirect)
return request.redirect(to: self.makePath(request))
}
}
4 changes: 2 additions & 2 deletions Sources/Vapor/Commands/RoutesCommand.swift
Original file line number Diff line number Diff line change
Expand Up @@ -27,9 +27,9 @@ public final class RoutesCommand: Command {

public func run(using context: CommandContext, signature: Signature) throws {
let routes = context.application.routes
let includeDescription = !routes.all.filter { $0.userInfo["description"] != nil }.isEmpty
let includeDescription = !routes.sendableAll.filter { $0.userInfo["description"] != nil }.isEmpty
let pathSeparator = "/".consoleText()
context.console.outputASCIITable(routes.all.map { route -> [ConsoleText] in
context.console.outputASCIITable(routes.sendableAll.map { route -> [ConsoleText] in
var column = [route.method.string.consoleText()]
if route.path.isEmpty {
column.append(pathSeparator)
Expand Down
46 changes: 46 additions & 0 deletions Sources/Vapor/Concurrency/AsyncMiddleware.swift
Original file line number Diff line number Diff line change
Expand Up @@ -27,3 +27,49 @@ extension AsyncMiddleware {
return promise.futureResult
}
}

extension Array where Element == AsyncMiddleware {
/// Wraps an `AsyncResponder` in an array of `AsyncMiddleware` creating a new `AsyncResponder`.
/// - note: The array of middleware must be `[AsyncMiddleware]` not `[M] where M: AsyncMiddleware`.
public func makeAsyncResponder(chainingTo responder: AsyncResponder) -> AsyncResponder {
var responder = responder
for middleware in reversed() {
responder = middleware.makeAsyncResponder(chainingTo: responder)
}
return responder
}
}

public extension AsyncMiddleware {
/// Wraps a `Responder` in a single `Middleware` creating a new `Responder`.
func makeAsyncResponder(chainingTo responder: AsyncResponder) -> AsyncResponder {
return AsyncHTTPMiddlewareResponder(middleware: self, responder: responder)
}
}

private struct AsyncHTTPMiddlewareResponder: AsyncResponder {
var middleware: AsyncMiddleware
var responder: AsyncResponder

init(middleware: AsyncMiddleware, responder: AsyncResponder) {
self.middleware = middleware
self.responder = responder
}

/// Chains an incoming request to another `AsyncResponder` on the router.
/// - parameters:
/// - request: The incoming `Request`.
/// - returns: An asynchronous `Response`.
func respond(to request: Request) async throws -> Response {
return try await self.middleware.respond(to: request, chainingTo: self.responder)
}
}

struct AsyncMiddlewareWrapper: AsyncMiddleware {

let middleware: Middleware

func respond(to request: Request, chainingTo next: AsyncResponder) async throws -> Response {
try await middleware.respond(to: request, chainingTo: next).get()
}
}
12 changes: 12 additions & 0 deletions Sources/Vapor/Concurrency/Responder+Concurrency.swift
Original file line number Diff line number Diff line change
Expand Up @@ -13,3 +13,15 @@ extension AsyncResponder {
return promise.futureResult
}
}

struct AsyncResponderWrapper: AsyncResponder {
let responder: Responder

init(_ responder: Responder) {
self.responder = responder
}

func respond(to request: Request) async throws -> Response {
try await self.responder.respond(to: request).get()
}
}