This commit is contained in:
Stephen Celis 2022-05-04 13:15:35 -04:00
parent f86552cde7
commit 324ef421e2
11 changed files with 835 additions and 12 deletions

View File

@ -0,0 +1,67 @@
<?xml version="1.0" encoding="UTF-8"?>
<Scheme
LastUpgradeVersion = "1330"
version = "1.3">
<BuildAction
parallelizeBuildables = "YES"
buildImplicitDependencies = "YES">
<BuildActionEntries>
<BuildActionEntry
buildForTesting = "YES"
buildForRunning = "YES"
buildForProfiling = "YES"
buildForArchiving = "YES"
buildForAnalyzing = "YES">
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "VaporRouting"
BuildableName = "VaporRouting"
BlueprintName = "VaporRouting"
ReferencedContainer = "container:">
</BuildableReference>
</BuildActionEntry>
</BuildActionEntries>
</BuildAction>
<TestAction
buildConfiguration = "Debug"
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
shouldUseLaunchSchemeArgsEnv = "YES">
<Testables>
</Testables>
</TestAction>
<LaunchAction
buildConfiguration = "Debug"
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
launchStyle = "0"
useCustomWorkingDirectory = "NO"
ignoresPersistentStateOnLaunch = "NO"
debugDocumentVersioning = "YES"
debugServiceExtension = "internal"
allowLocationSimulation = "YES">
</LaunchAction>
<ProfileAction
buildConfiguration = "Release"
shouldUseLaunchSchemeArgsEnv = "YES"
savedToolIdentifier = ""
useCustomWorkingDirectory = "NO"
debugDocumentVersioning = "YES">
<MacroExpansion>
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "VaporRouting"
BuildableName = "VaporRouting"
BlueprintName = "VaporRouting"
ReferencedContainer = "container:">
</BuildableReference>
</MacroExpansion>
</ProfileAction>
<AnalyzeAction
buildConfiguration = "Debug">
</AnalyzeAction>
<ArchiveAction
buildConfiguration = "Release"
revealArchiveInOrganizer = "YES">
</ArchiveAction>
</Scheme>

View File

@ -0,0 +1,114 @@
<?xml version="1.0" encoding="UTF-8"?>
<Scheme
LastUpgradeVersion = "1330"
version = "1.3">
<BuildAction
parallelizeBuildables = "YES"
buildImplicitDependencies = "YES">
<BuildActionEntries>
<BuildActionEntry
buildForTesting = "YES"
buildForRunning = "YES"
buildForProfiling = "YES"
buildForArchiving = "YES"
buildForAnalyzing = "YES">
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "VaporRouting"
BuildableName = "VaporRouting"
BlueprintName = "VaporRouting"
ReferencedContainer = "container:">
</BuildableReference>
</BuildActionEntry>
<BuildActionEntry
buildForTesting = "YES"
buildForRunning = "YES"
buildForProfiling = "YES"
buildForArchiving = "YES"
buildForAnalyzing = "YES">
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "vapor-routing-benchmark"
BuildableName = "vapor-routing-benchmark"
BlueprintName = "vapor-routing-benchmark"
ReferencedContainer = "container:">
</BuildableReference>
</BuildActionEntry>
<BuildActionEntry
buildForTesting = "YES"
buildForRunning = "YES"
buildForProfiling = "NO"
buildForArchiving = "NO"
buildForAnalyzing = "YES">
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "VaporRoutingTests"
BuildableName = "VaporRoutingTests"
BlueprintName = "VaporRoutingTests"
ReferencedContainer = "container:">
</BuildableReference>
</BuildActionEntry>
</BuildActionEntries>
</BuildAction>
<TestAction
buildConfiguration = "Debug"
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
shouldUseLaunchSchemeArgsEnv = "YES">
<Testables>
<TestableReference
skipped = "NO">
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "VaporRoutingTests"
BuildableName = "VaporRoutingTests"
BlueprintName = "VaporRoutingTests"
ReferencedContainer = "container:">
</BuildableReference>
</TestableReference>
</Testables>
</TestAction>
<LaunchAction
buildConfiguration = "Debug"
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
launchStyle = "0"
useCustomWorkingDirectory = "NO"
ignoresPersistentStateOnLaunch = "NO"
debugDocumentVersioning = "YES"
debugServiceExtension = "internal"
allowLocationSimulation = "YES">
<MacroExpansion>
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "vapor-routing-benchmark"
BuildableName = "vapor-routing-benchmark"
BlueprintName = "vapor-routing-benchmark"
ReferencedContainer = "container:">
</BuildableReference>
</MacroExpansion>
</LaunchAction>
<ProfileAction
buildConfiguration = "Release"
shouldUseLaunchSchemeArgsEnv = "YES"
savedToolIdentifier = ""
useCustomWorkingDirectory = "NO"
debugDocumentVersioning = "YES">
<MacroExpansion>
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "vapor-routing-benchmark"
BuildableName = "vapor-routing-benchmark"
BlueprintName = "vapor-routing-benchmark"
ReferencedContainer = "container:">
</BuildableReference>
</MacroExpansion>
</ProfileAction>
<AnalyzeAction
buildConfiguration = "Debug">
</AnalyzeAction>
<ArchiveAction
buildConfiguration = "Release"
revealArchiveInOrganizer = "YES">
</ArchiveAction>
</Scheme>

View File

@ -0,0 +1,109 @@
<?xml version="1.0" encoding="UTF-8"?>
<Scheme
LastUpgradeVersion = "1330"
version = "1.3">
<BuildAction
parallelizeBuildables = "YES"
buildImplicitDependencies = "YES">
<BuildActionEntries>
<BuildActionEntry
buildForTesting = "YES"
buildForRunning = "YES"
buildForProfiling = "YES"
buildForArchiving = "YES"
buildForAnalyzing = "YES">
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "vapor-routing-benchmark"
BuildableName = "vapor-routing-benchmark"
BlueprintName = "vapor-routing-benchmark"
ReferencedContainer = "container:">
</BuildableReference>
</BuildActionEntry>
<BuildActionEntry
buildForTesting = "YES"
buildForRunning = "YES"
buildForProfiling = "NO"
buildForArchiving = "NO"
buildForAnalyzing = "YES">
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "VaporRoutingTests"
BuildableName = "VaporRoutingTests"
BlueprintName = "VaporRoutingTests"
ReferencedContainer = "container:">
</BuildableReference>
</BuildActionEntry>
</BuildActionEntries>
</BuildAction>
<TestAction
buildConfiguration = "Debug"
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
shouldUseLaunchSchemeArgsEnv = "YES">
<Testables>
<TestableReference
skipped = "NO">
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "VaporRoutingTests"
BuildableName = "VaporRoutingTests"
BlueprintName = "VaporRoutingTests"
ReferencedContainer = "container:">
</BuildableReference>
</TestableReference>
</Testables>
</TestAction>
<LaunchAction
buildConfiguration = "Release"
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
launchStyle = "0"
useCustomWorkingDirectory = "YES"
customWorkingDirectory = "/tmp"
ignoresPersistentStateOnLaunch = "NO"
debugDocumentVersioning = "YES"
debugServiceExtension = "internal"
allowLocationSimulation = "YES">
<BuildableProductRunnable
runnableDebuggingMode = "0">
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "vapor-routing-benchmark"
BuildableName = "vapor-routing-benchmark"
BlueprintName = "vapor-routing-benchmark"
ReferencedContainer = "container:">
</BuildableReference>
</BuildableProductRunnable>
<CommandLineArguments>
<CommandLineArgument
argument = "--allow-debug-build"
isEnabled = "YES">
</CommandLineArgument>
</CommandLineArguments>
</LaunchAction>
<ProfileAction
buildConfiguration = "Release"
shouldUseLaunchSchemeArgsEnv = "YES"
savedToolIdentifier = ""
useCustomWorkingDirectory = "NO"
debugDocumentVersioning = "YES">
<BuildableProductRunnable
runnableDebuggingMode = "0">
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "vapor-routing-benchmark"
BuildableName = "vapor-routing-benchmark"
BlueprintName = "vapor-routing-benchmark"
ReferencedContainer = "container:">
</BuildableReference>
</BuildableProductRunnable>
</ProfileAction>
<AnalyzeAction
buildConfiguration = "Debug">
</AnalyzeAction>
<ArchiveAction
buildConfiguration = "Release"
revealArchiveInOrganizer = "YES">
</ArchiveAction>
</Scheme>

View File

@ -0,0 +1,91 @@
<?xml version="1.0" encoding="UTF-8"?>
<Scheme
LastUpgradeVersion = "1330"
version = "1.3">
<BuildAction
parallelizeBuildables = "YES"
buildImplicitDependencies = "YES">
<BuildActionEntries>
<BuildActionEntry
buildForTesting = "YES"
buildForRunning = "YES"
buildForProfiling = "YES"
buildForArchiving = "YES"
buildForAnalyzing = "YES">
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "VaporRouting"
BuildableName = "VaporRouting"
BlueprintName = "VaporRouting"
ReferencedContainer = "container:">
</BuildableReference>
</BuildActionEntry>
<BuildActionEntry
buildForTesting = "YES"
buildForRunning = "YES"
buildForProfiling = "NO"
buildForArchiving = "NO"
buildForAnalyzing = "YES">
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "VaporRoutingTests"
BuildableName = "VaporRoutingTests"
BlueprintName = "VaporRoutingTests"
ReferencedContainer = "container:">
</BuildableReference>
</BuildActionEntry>
</BuildActionEntries>
</BuildAction>
<TestAction
buildConfiguration = "Debug"
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
shouldUseLaunchSchemeArgsEnv = "YES">
<Testables>
<TestableReference
skipped = "NO">
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "VaporRoutingTests"
BuildableName = "VaporRoutingTests"
BlueprintName = "VaporRoutingTests"
ReferencedContainer = "container:">
</BuildableReference>
</TestableReference>
</Testables>
</TestAction>
<LaunchAction
buildConfiguration = "Debug"
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
launchStyle = "0"
useCustomWorkingDirectory = "NO"
ignoresPersistentStateOnLaunch = "NO"
debugDocumentVersioning = "YES"
debugServiceExtension = "internal"
allowLocationSimulation = "YES">
</LaunchAction>
<ProfileAction
buildConfiguration = "Release"
shouldUseLaunchSchemeArgsEnv = "YES"
savedToolIdentifier = ""
useCustomWorkingDirectory = "NO"
debugDocumentVersioning = "YES">
<MacroExpansion>
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "VaporRouting"
BuildableName = "VaporRouting"
BlueprintName = "VaporRouting"
ReferencedContainer = "container:">
</BuildableReference>
</MacroExpansion>
</ProfileAction>
<AnalyzeAction
buildConfiguration = "Debug">
</AnalyzeAction>
<ArchiveAction
buildConfiguration = "Release"
revealArchiveInOrganizer = "YES">
</ArchiveAction>
</Scheme>

View File

@ -46,6 +46,15 @@
"version": "4.3.1"
}
},
{
"package": "swift-argument-parser",
"repositoryURL": "https://github.com/apple/swift-argument-parser",
"state": {
"branch": null,
"revision": "6b2aa2748a7881eebb9f84fb10c01293e15b52ca",
"version": "0.5.0"
}
},
{
"package": "swift-backtrace",
"repositoryURL": "https://github.com/swift-server/swift-backtrace.git",
@ -55,6 +64,15 @@
"version": "1.3.1"
}
},
{
"package": "Benchmark",
"repositoryURL": "https://github.com/google/swift-benchmark",
"state": {
"branch": null,
"revision": "a0564bf88df5f94eec81348a2f089494c6b28d80",
"version": "0.1.1"
}
},
{
"package": "swift-case-paths",
"repositoryURL": "https://github.com/pointfreeco/swift-case-paths",
@ -149,9 +167,9 @@
"package": "URLRouting",
"repositoryURL": "https://github.com/pointfreeco/swift-url-routing",
"state": {
"branch": null,
"revision": "53f56e552b3932d5c11252cb6669a059e9e9e69a",
"version": "0.1.0"
"branch": "main",
"revision": "97b2826fb5f1300b7b618c300569ed0725ce831a",
"version": null
}
},
{

View File

@ -15,7 +15,8 @@ let package = Package(
],
dependencies: [
.package(url: "https://github.com/vapor/vapor.git", from: "4.0.0"),
.package(url: "https://github.com/pointfreeco/swift-url-routing", from: "0.1.0"),
.package(url: "https://github.com/pointfreeco/swift-url-routing", branch: "main"), // from: "0.1.0"),
.package(name: "Benchmark", url: "https://github.com/google/swift-benchmark", from: "0.1.1"),
],
targets: [
.target(
@ -32,5 +33,13 @@ let package = Package(
.product(name: "XCTVapor", package: "vapor"),
]
),
.executableTarget(
name: "vapor-routing-benchmark",
dependencies: [
"VaporRouting",
.product(name: "Benchmark", package: "Benchmark"),
.product(name: "Vapor", package: "vapor"),
]
)
]
)

View File

@ -16,9 +16,9 @@ extension URLRequestData {
let components = URLComponents(url: url, resolvingAgainstBaseURL: false)
else { return nil }
let body: [UInt8]?
let body: Data?
if var buffer = request.body.data,
let bytes = buffer.readBytes(length: buffer.readableBytes)
let bytes = buffer.readData(length: buffer.readableBytes)
{
body = bytes
} else {
@ -27,12 +27,12 @@ extension URLRequestData {
self.init(
method: request.method.string,
scheme: request.url.scheme,
scheme: components.scheme,
user: request.headers.basicAuthorization?.username,
password: request.headers.basicAuthorization?.password,
host: request.url.host,
port: request.url.port,
path: request.url.path,
host: components.host,
port: components.port,
path: components.path,
query: components.queryItems?.reduce(into: [:]) { query, item in
query[item.name, default: []].append(item.value)
} ?? [:],
@ -45,7 +45,7 @@ extension URLRequestData {
},
uniquingKeysWith: { $0 + $1 }
),
body: body.map { Data($0) }
body: body
)
}
}

View File

@ -11,12 +11,54 @@ extension Application {
public func mount<R: Parser>(
_ router: R,
use closure: @escaping (Request, R.Output) async throws -> AsyncResponseEncodable
) where R.Input == URLRequestData {
self.middleware.use(AsyncRoutingMiddleware(router: router, respond: closure))
}
@_disfavoredOverload
public func _mount<R: Parser>(
_ router: R,
use closure: @escaping (Request, R.Output) -> EventLoopFuture<ResponseEncodable>
) where R.Input == URLRequestData {
self.middleware.use(RoutingMiddleware(router: router, respond: closure))
}
}
private struct RoutingMiddleware<Router: Parser>: AsyncMiddleware
private struct RoutingMiddleware<Router: Parser>: Middleware
where Router.Input == URLRequestData {
let router: Router
let respond: (Request, Router.Output) -> EventLoopFuture<ResponseEncodable>
public func respond(
to request: Request,
chainingTo next: Responder
) -> EventLoopFuture<Response> {
guard let requestData = URLRequestData(request: request)
else { return next.respond(to: request) }
let route: Router.Output
do {
route = try self.router.parse(requestData)
return self.respond(request, route)
.flatMap { $0.encodeResponse(for: request) }
} catch let routingError {
return next.respond(to: request)
.flatMapError { error in
request.logger.info("\(routingError)")
guard request.application.environment == .development
else { return request.eventLoop.makeFailedFuture(error) }
return request.eventLoop.makeSucceededFuture(
Response(status: .notFound, body: .init(string: "Routing \(routingError)"))
)
}
}
}
}
private struct AsyncRoutingMiddleware<Router: Parser>: AsyncMiddleware
where Router.Input == URLRequestData {
let router: Router
let respond: (Request, Router.Output) async throws -> AsyncResponseEncodable

View File

@ -0,0 +1,46 @@
import Benchmark
extension BenchmarkSuite {
func benchmark(
_ name: String,
run: @escaping () throws -> Void,
setUp: @escaping () -> Void = {},
tearDown: @escaping () -> Void = {}
) {
self.register(
benchmark: Benchmarking(name: name, run: run, setUp: setUp, tearDown: tearDown)
)
}
}
struct Benchmarking: AnyBenchmark {
let name: String
let settings: [BenchmarkSetting] = []
private let _run: () throws -> Void
private let _setUp: () -> Void
private let _tearDown: () -> Void
init(
name: String,
run: @escaping () throws -> Void,
setUp: @escaping () -> Void = {},
tearDown: @escaping () -> Void = {}
) {
self.name = name
self._run = run
self._setUp = setUp
self._tearDown = tearDown
}
func setUp() {
self._setUp()
}
func run(_ state: inout BenchmarkState) throws {
try self._run()
}
func tearDown() {
self._tearDown()
}
}

View File

@ -0,0 +1,319 @@
import Benchmark
import URLRouting
import Vapor
import VaporRouting
let routingSuite = BenchmarkSuite(name: "Routing", settings: MaxIterations(1_000)) { suite in
LoggingSystem.bootstrap { _ in SwiftLogNoOpLogHandler() }
let eventLoop = EmbeddedEventLoop()
var app: Application!
var req: Request!
var res: Response!
struct NoopMiddleware: Middleware {
func respond(to request: Request, chainingTo next: Responder) -> EventLoopFuture<Response> {
request.eventLoop.makeSucceededFuture(.init())
}
}
suite.benchmark("Vapor.baseline") {
res = try app.responder.respond(to: req).wait()
} setUp: {
app = .init()
app.middleware.use(NoopMiddleware())
req = .init(application: app, method: .GET, url: .init(string: "/"), on: eventLoop)
} tearDown: {
app.shutdown()
}
suite.benchmark("Vapor.home (no-op)") {
res = try app.responder.respond(to: req).wait()
} setUp: {
app = .init()
try! routes(app)
req = .init(application: app, method: .GET, url: .init(string: "/"), on: eventLoop)
} tearDown: {
precondition(res.status == .ok)
app.shutdown()
}
suite.benchmark("VaporRouting.home") {
res = try app.responder.respond(to: req).wait()
} setUp: {
app = .init()
app._mount(siteRouter, use: siteHandler)
req = .init(application: app, method: .GET, url: .init(string: "/"), on: eventLoop)
} tearDown: {
precondition(res.status == .ok)
app.shutdown()
}
suite.benchmark("Vapor.createUser") {
res = try app.responder.respond(to: req).wait()
} setUp: {
app = .init()
try! routes(app)
req = .init(
application: app,
method: .POST,
url: .init(string: "/users"),
headers: ["content-type": "application/json"],
collectedBody: .init(string: #"{"name":"Blob","bio":"Blobbed around the world!"}"#),
on: eventLoop
)
} tearDown: {
precondition(res.status == .ok)
app.shutdown()
}
suite.benchmark("VaporRouting.createUser") {
res = try app.responder.respond(to: req).wait()
} setUp: {
app = .init()
app._mount(siteRouter, use: siteHandler)
req = .init(
application: app,
method: .POST,
url: .init(string: "/users"),
collectedBody: .init(string: #"{"name":"Blob","bio":"Blobbed around the world!"}"#),
on: eventLoop
)
} tearDown: {
precondition(res.status == .ok)
app.shutdown()
}
suite.benchmark("Vapor.fetchUser") {
res = try app.responder.respond(to: req).wait()
} setUp: {
app = .init()
try! routes(app)
req = .init(application: app, method: .GET, url: .init(string: "/users/42"), on: eventLoop)
} tearDown: {
precondition(res.status == .ok)
app.shutdown()
}
suite.benchmark("VaporRouting.fetchUser") {
res = try app.responder.respond(to: req).wait()
} setUp: {
app = .init()
app._mount(siteRouter, use: siteHandler)
req = .init(application: app, method: .GET, url: .init(string: "/users/42"), on: eventLoop)
} tearDown: {
precondition(res.status == .ok)
app.shutdown()
}
suite.benchmark("Vapor.bookSearch") {
res = try app.responder.respond(to: req).wait()
} setUp: {
app = .init()
try! routes(app)
req = .init(
application: app,
method: .GET,
url: .init(string: "/users/42/books/search"),
on: eventLoop
)
} tearDown: {
precondition(res.status == .ok)
app.shutdown()
}
suite.benchmark("VaporRouting.bookSearch") {
res = try app.responder.respond(to: req).wait()
} setUp: {
app = .init()
app._mount(siteRouter, use: siteHandler)
req = .init(
application: app,
method: .GET,
url: .init(string: "/users/42/books/search"),
on: eventLoop
)
} tearDown: {
precondition(res.status == .ok)
app.shutdown()
}
suite.benchmark("Vapor.bookSearchQuery") {
res = try app.responder.respond(to: req).wait()
} setUp: {
app = .init()
try! routes(app)
req = .init(
application: app,
method: .GET,
url: .init(string: "/users/42/books/search?direction=desc&sort=category&count=100"),
on: eventLoop
)
} tearDown: {
precondition(res.status == .ok)
app.shutdown()
}
suite.benchmark("VaporRouting.bookSearchQuery") {
res = try app.responder.respond(to: req).wait()
} setUp: {
app = .init()
app._mount(siteRouter, use: siteHandler)
req = .init(
application: app,
method: .GET,
url: .init(string: "/users/42/books/search?direction=desc&sort=category&count=100"),
on: eventLoop
)
} tearDown: {
precondition(res.status == .ok)
app.shutdown()
}
suite.benchmark("Vapor.fetchUserBook") {
res = try app.responder.respond(to: req).wait()
} setUp: {
app = .init()
try! routes(app)
req = .init(
application: app,
method: .GET,
url: .init(string: "/users/42/books/deadbeef-dead-beef-dead-beefdeadbeef"),
on: eventLoop
)
} tearDown: {
precondition(res.status == .ok)
app.shutdown()
}
suite.benchmark("VaporRouting.fetchUserBook") {
res = try app.responder.respond(to: req).wait()
} setUp: {
app = .init()
app._mount(siteRouter, use: siteHandler)
req = .init(
application: app,
method: .GET,
url: .init(string: "/users/42/books/deadbeef-dead-beef-dead-beefdeadbeef"),
on: eventLoop
)
} tearDown: {
precondition(res.status == .ok)
app.shutdown()
}
}
struct CreateUser: Codable {
let bio: String
let name: String
}
struct SearchOptions: Decodable {
var sort: Sort = .title
var direction: Direction = .asc
var count = 10
enum Direction: String, CaseIterable, Decodable {
case asc, desc
}
enum Sort: String, CaseIterable, Decodable {
case title, category
}
}
@inline(never)
public func blackHole<T>(_ x: T) {}
func routes(_ app: Application) throws {
app.get { req -> Response in
return .init()
}
app.post("users") { req -> Response in
blackHole(try req.content.decode(CreateUser.self))
return .init()
}
app.get("users", ":userId") { req -> Response in
blackHole(try req.parameters.require("userId", as: Int.self))
return .init()
}
app.get("users", ":userId", "books", "search") { req -> Response in
blackHole(try req.parameters.require("userId", as: Int.self))
blackHole((try? req.query.get(SearchOptions.Sort.self, at: "sort")) ?? .title)
blackHole((try? req.query.get(SearchOptions.Direction.self, at: "sort")) ?? .asc)
blackHole((try? req.query.get(Int.self, at: "count")) ?? 10)
return .init()
}
app.get("users", ":userId", "books", ":bookId") { req -> Response in
blackHole(try req.parameters.require("userId", as: Int.self))
blackHole(try req.parameters.require("bookId", as: UUID.self))
return .init()
}
}
enum BookRoute {
case fetch
}
enum BooksRoute {
case book(UUID, BookRoute = .fetch)
case search(SearchOptions = .init())
}
enum UserRoute {
case books(BooksRoute = .search())
case fetch
}
enum UsersRoute {
case create(CreateUser)
case user(Int, UserRoute = .fetch)
}
enum SiteRoute {
case home
case users(UsersRoute)
}
let bookRouter = OneOf {
Route(.case(BookRoute.fetch))
}
let booksRouter = OneOf {
Route(.case(BooksRoute.search)) {
Path { From(.utf8) { "search".utf8 } }
Parse(.memberwise(SearchOptions.init)) {
Query {
Field("sort", .string.representing(SearchOptions.Sort.self), default: .title)
Field("direction", .string.representing(SearchOptions.Direction.self), default: .asc)
Field("count", default: 10) { Digits() }
}
}
}
Route(.case(BooksRoute.book)) {
Path { UUID.parser() }
bookRouter
}
}
let userRouter = OneOf {
Route(.case(UserRoute.fetch))
Route(.case(UserRoute.books)) {
Path { From(.utf8) { "books".utf8 } }
booksRouter
}
}
let usersRouter = OneOf {
Route(.case(UsersRoute.create)) {
Method.post
Body(.json(CreateUser.self))
}
Route(.case(UsersRoute.user)) {
Path { Digits() }
userRouter
}
}
let siteRouter = OneOf {
Route(.case(SiteRoute.home))
Route(.case(SiteRoute.users)) {
Path { From(.utf8) { "users".utf8 } }
usersRouter
}
}
func siteHandler(request: Request, route: SiteRoute) -> EventLoopFuture<ResponseEncodable> {
request.eventLoop.makeSucceededFuture(Response())
}

View File

@ -0,0 +1,8 @@
import Benchmark
Benchmark.main(
[
defaultBenchmarkSuite,
routingSuite,
]
)