From d2bd0e8d1d299753cd5a48cfdc703bdcf1ae0e77 Mon Sep 17 00:00:00 2001 From: bre7 Date: Thu, 10 May 2018 18:42:45 -0300 Subject: [PATCH] Init --- frameworks/Swift/vapor/Package.resolved | 45 +++++++ frameworks/Swift/vapor/Package.swift | 3 + .../Swift/vapor/Resources/Views/fortune.leaf | 10 ++ .../vapor-tfb-mysql/Models/Fortune.swift | 15 +++ .../vapor-tfb-mysql/Models/Message.swift | 9 ++ .../vapor-tfb-mysql/Models/World.swift | 15 +++ .../vapor-tfb-mysql/ParameterUtils.swift | 26 ++++ .../Sources/vapor-tfb-mysql/WorldMeta.swift | 29 +++++ .../vapor/Sources/vapor-tfb-mysql/main.swift | 122 ++++++++++++++++++ frameworks/Swift/vapor/benchmark_config.json | 21 +++ frameworks/Swift/vapor/vapor-mysql.dockerfile | 8 ++ 11 files changed, 303 insertions(+) create mode 100644 frameworks/Swift/vapor/Resources/Views/fortune.leaf create mode 100644 frameworks/Swift/vapor/Sources/vapor-tfb-mysql/Models/Fortune.swift create mode 100644 frameworks/Swift/vapor/Sources/vapor-tfb-mysql/Models/Message.swift create mode 100644 frameworks/Swift/vapor/Sources/vapor-tfb-mysql/Models/World.swift create mode 100644 frameworks/Swift/vapor/Sources/vapor-tfb-mysql/ParameterUtils.swift create mode 100644 frameworks/Swift/vapor/Sources/vapor-tfb-mysql/WorldMeta.swift create mode 100644 frameworks/Swift/vapor/Sources/vapor-tfb-mysql/main.swift create mode 100644 frameworks/Swift/vapor/vapor-mysql.dockerfile diff --git a/frameworks/Swift/vapor/Package.resolved b/frameworks/Swift/vapor/Package.resolved index a6e676b873d..ede714a04e4 100644 --- a/frameworks/Swift/vapor/Package.resolved +++ b/frameworks/Swift/vapor/Package.resolved @@ -37,6 +37,24 @@ "version": "1.0.1" } }, + { + "package": "Fluent", + "repositoryURL": "https://github.com/vapor/fluent.git", + "state": { + "branch": null, + "revision": "8f22ef2dfeb278179a2d6e91769a5c1ceccf31ef", + "version": "3.0.0-rc.2.4.1" + } + }, + { + "package": "FluentMySQL", + "repositoryURL": "https://github.com/vapor/fluent-mysql.git", + "state": { + "branch": null, + "revision": "19956a04b1983f20879d9c770a49d7948c42b270", + "version": "3.0.0-rc.2.4" + } + }, { "package": "HTTP", "repositoryURL": "https://github.com/vapor/http.git", @@ -46,6 +64,15 @@ "version": "3.0.5" } }, + { + "package": "Leaf", + "repositoryURL": "https://github.com/vapor/leaf.git", + "state": { + "branch": null, + "revision": "a71cb0cd29ec88ee72baa00668c05e327114f27c", + "version": "3.0.0-rc.2.2" + } + }, { "package": "Multipart", "repositoryURL": "https://github.com/vapor/multipart.git", @@ -55,6 +82,15 @@ "version": "3.0.1" } }, + { + "package": "MySQL", + "repositoryURL": "https://github.com/vapor/mysql.git", + "state": { + "branch": null, + "revision": "cda473257572c8e02706d0c65fd145765c5a414d", + "version": "3.0.0-rc.2.2" + } + }, { "package": "Routing", "repositoryURL": "https://github.com/vapor/routing.git", @@ -73,6 +109,15 @@ "version": "1.0.0" } }, + { + "package": "SQL", + "repositoryURL": "https://github.com/vapor/sql.git", + "state": { + "branch": null, + "revision": "1bb94271d8645b6d6983721080ad642aa35d749c", + "version": "1.0.0" + } + }, { "package": "swift-nio", "repositoryURL": "https://github.com/apple/swift-nio.git", diff --git a/frameworks/Swift/vapor/Package.swift b/frameworks/Swift/vapor/Package.swift index 42871ee11ef..2fa7b5a6a43 100644 --- a/frameworks/Swift/vapor/Package.swift +++ b/frameworks/Swift/vapor/Package.swift @@ -5,8 +5,11 @@ let package = Package( name: "tfb", dependencies: [ .package(url: "https://github.com/vapor/vapor.git", from: "3.0.0"), + .package(url: "https://github.com/vapor/fluent-mysql.git", from: "3.0.0-rc"), + .package(url: "https://github.com/vapor/leaf.git", from: "3.0.0-rc.1") ], targets: [ .target(name: "vapor-tfb", dependencies: ["Vapor"], exclude: ["Resources"]), + .target(name: "vapor-tfb-mysql", dependencies: ["FluentMySQL", "Leaf", "Vapor"]), ] ) diff --git a/frameworks/Swift/vapor/Resources/Views/fortune.leaf b/frameworks/Swift/vapor/Resources/Views/fortune.leaf new file mode 100644 index 00000000000..861552c0437 --- /dev/null +++ b/frameworks/Swift/vapor/Resources/Views/fortune.leaf @@ -0,0 +1,10 @@ + + +Fortunes + + + +#for(fortune in fortunes) { } +
idmessage
#(fortune.id)#(fortune.message)
+ + diff --git a/frameworks/Swift/vapor/Sources/vapor-tfb-mysql/Models/Fortune.swift b/frameworks/Swift/vapor/Sources/vapor-tfb-mysql/Models/Fortune.swift new file mode 100644 index 00000000000..35b81f2bf87 --- /dev/null +++ b/frameworks/Swift/vapor/Sources/vapor-tfb-mysql/Models/Fortune.swift @@ -0,0 +1,15 @@ +import Vapor +import FluentMySQL + +final class Fortune: MySQLModel, Migration, Content { + static let entity = "fortune" + + var id: Int? + var message: String + + init(id: Int?, message: String) { + self.id = id + self.message = message + } + +} diff --git a/frameworks/Swift/vapor/Sources/vapor-tfb-mysql/Models/Message.swift b/frameworks/Swift/vapor/Sources/vapor-tfb-mysql/Models/Message.swift new file mode 100644 index 00000000000..f1dd8e25c6d --- /dev/null +++ b/frameworks/Swift/vapor/Sources/vapor-tfb-mysql/Models/Message.swift @@ -0,0 +1,9 @@ +import Vapor + +struct Message: Content { + let message: String? + + init(message: String? = nil) { + self.message = message + } +} diff --git a/frameworks/Swift/vapor/Sources/vapor-tfb-mysql/Models/World.swift b/frameworks/Swift/vapor/Sources/vapor-tfb-mysql/Models/World.swift new file mode 100644 index 00000000000..7897d6bc0fa --- /dev/null +++ b/frameworks/Swift/vapor/Sources/vapor-tfb-mysql/Models/World.swift @@ -0,0 +1,15 @@ +import Vapor +import FluentMySQL + +final class World: MySQLModel, Migration, Content { + static let entity = "world" + + var id: Int? + var randomNumber: Int + + /// Creates a new World + init(id: Int?, randomNumber: Int) { + self.id = id + self.randomNumber = randomNumber + } +} diff --git a/frameworks/Swift/vapor/Sources/vapor-tfb-mysql/ParameterUtils.swift b/frameworks/Swift/vapor/Sources/vapor-tfb-mysql/ParameterUtils.swift new file mode 100644 index 00000000000..d489e09fbee --- /dev/null +++ b/frameworks/Swift/vapor/Sources/vapor-tfb-mysql/ParameterUtils.swift @@ -0,0 +1,26 @@ +import Vapor + +public let validQueriesRange: ClosedRange = 1...500 + +/// Tests 3 and 5 are parameterized with `queries`. +/// +/// The queries parameter must be bounded to between 1 and 500. +/// If the parameter is missing, is not an integer, or is an integer less than 1, +/// the value should be interpreted as 1; if greater than 500, the value should be interpreted as 500. +/// +/// - Parameter request: HTTP request +/// - Returns: queries +public func queriesParam(for request: Request) throws -> Int { + // TODO: throw instead of using ! + let rangeMax = try request.parameters.next(String.self) + .removingPercentEncoding? // convert url-encoded chars + .components(separatedBy: "...") + .last? // ignore lower bound, only retain last part which contains upper bound + .dropLast() // remove ] + let paramNormalized = rangeMax.flatMap(String.init).flatMap(Int.init) ?? validQueriesRange.upperBound + return clamp(paramNormalized, to: validQueriesRange) +} + +func clamp(_ value: Int, to: ClosedRange) -> Int { + return max(to.lowerBound, min(to.upperBound, value)) +} diff --git a/frameworks/Swift/vapor/Sources/vapor-tfb-mysql/WorldMeta.swift b/frameworks/Swift/vapor/Sources/vapor-tfb-mysql/WorldMeta.swift new file mode 100644 index 00000000000..2964ad92e8a --- /dev/null +++ b/frameworks/Swift/vapor/Sources/vapor-tfb-mysql/WorldMeta.swift @@ -0,0 +1,29 @@ +import Vapor +#if os(Linux) +import Glibc +#endif + +private extension Int { + static func random(_ max: T) -> Int where T: BinaryInteger { + #if os(Linux) + return Int(SwiftGlibc.random() % (Int(max) + 1)) + #else + return Int(arc4random_uniform(UInt32(max))) + #endif + } +} + +public struct WorldMeta { + private init() { } + + public static let maxId: UInt32 = 10_000 + + public static func randomId() -> Int { + return Int.random(maxId) + } + + public static let maxRandomNumber: Int32 = 10_000 + public static func randomRandomNumber() -> Int { + return Int.random(maxRandomNumber) + } +} diff --git a/frameworks/Swift/vapor/Sources/vapor-tfb-mysql/main.swift b/frameworks/Swift/vapor/Sources/vapor-tfb-mysql/main.swift new file mode 100644 index 00000000000..6f5c003840b --- /dev/null +++ b/frameworks/Swift/vapor/Sources/vapor-tfb-mysql/main.swift @@ -0,0 +1,122 @@ +import FluentMySQL +import Leaf +import Vapor + +// Services +var services = Services.default() + +// Router +let router = EngineRouter.default() + +// Config +var config = Config.default() + +// Routes + +var jsonRes = HTTPResponse(status: .ok, headers: ["Server": "Vapor"]) +let jsonEncoder = JSONEncoder() + +// Test type 2: Single database query +router.get("db") { req -> Future in + let worldId = WorldMeta.randomId() + return try World.find(worldId, on: req) + .map(to: Response.self) { world in + let res = Response(http: jsonRes, using: req.sharedContainer) + + if let world = world { + try res.content.encode(json: world, using: jsonEncoder) + } else { + try res.content.encode(json: Message(), using: jsonEncoder) + } + + return res + } +} + +// Test type 3: Multiple database queries +router.get("queries", String.parameter) { req -> Future in + let queries = try queriesParam(for: req) + let ids = (1...queries).lazy.map({ _ in WorldMeta.randomId() }) + + return try ids.map { id in + return try World.find(id, on: req) + } + .flatten(on: req) + .map { result in + return result.compactMap({ $0 }) + }.map { worlds in + let res = Response(http: jsonRes, using: req.sharedContainer) + try res.content.encode(json: worlds, using: jsonEncoder) + return res + } +} + +// Test type 4: Fortunes +router.get("fortunes") { req -> Future in + let posixLocale = Locale(identifier: "en_US_POSIX") + + return Fortune.query(on: req).all() + .map { fortunes -> [Fortune] in + var newFortunes = fortunes + let additional = Fortune(id: 0, message: "Additional fortune added at request time.") + newFortunes.insert(additional, at: 0) + newFortunes.sort(by: { lhs, rhs -> Bool in + return lhs.message.compare(rhs.message, locale: posixLocale) == .orderedAscending + }) + return newFortunes + } + .flatMap { fortunes in + // FIXME: Add headers + return try req.view().render("fortune", ["fortunes": fortunes]) + } +} + +// Test type 5: Database updates +router.get("updates", String.parameter) { req -> Future in + let queries = try queriesParam(for: req) + let ids = (1...queries).lazy.map({ _ in WorldMeta.randomId() }) + + return try ids.map { id in + return try World.find(id, on: req) + } + .flatten(on: req) + .map { result -> [World] in + let worlds = result.compactMap({ $0 }) + worlds.forEach { $0.randomNumber = WorldMeta.randomRandomNumber() } + return worlds + } + .flatMap { worlds -> Future<[World]> in + return worlds + .map { $0.save(on: req) } + .flatten(on: req) + }.flatMap { _ in + return World.query(on: req).all() + }.map { worlds in + let res = Response(http: jsonRes, using: req.sharedContainer) + try res.content.encode(json: worlds, using: jsonEncoder) + return res + } +} + +services.register(router, as: Router.self) + +try services.register(LeafProvider()) +config.prefer(LeafRenderer.self, for: ViewRenderer.self) +try services.register(FluentMySQLProvider()) +/// Register custom MySQL Config +let mySQLUrl = URL(string: "mysql://benchmarkdbuser:benchmarkdbpass@tfb-database:5432/hello_world")! +let mysqlConfig = MySQLDatabaseConfig(hostname: mySQLUrl.host!, port: mySQLUrl.port!, username: mySQLUrl.user!, password: mySQLUrl.password, database: "hello_world") +services.register(mysqlConfig) + +/// Configure migrations +services.register(MigrationConfig()) +/// Allow requests as DatabaseConnectible +Fortune.defaultDatabase = .mysql +World.defaultDatabase = .mysql + +// Middlewares (remove unused ErrorMiddleware) +var middlewares = MiddlewareConfig() +services.register(middlewares) + +let app = try Application(config: config, environment: .detect(), services: services) +try app.run() diff --git a/frameworks/Swift/vapor/benchmark_config.json b/frameworks/Swift/vapor/benchmark_config.json index b68a405a582..28f7ffe0cb8 100644 --- a/frameworks/Swift/vapor/benchmark_config.json +++ b/frameworks/Swift/vapor/benchmark_config.json @@ -19,6 +19,27 @@ "display_name": "Vapor", "notes": "", "versus": "" + }, + "mysql": { + "db_url": "/db", + "query_url": "/queries/", + "fortune_url": "/fortunes", + "update_url": "/updates/", + "port": 8080, + "approach": "Realistic", + "classification": "Fullstack", + "database": "MySQL", + "framework": "Vapor", + "language": "Swift", + "flavor": "None", + "orm": "Full", + "platform": "None", + "webserver": "None", + "os": "Linux", + "database_os": "Linux", + "display_name": "Vapor", + "notes": "", + "versus": "" } }] } diff --git a/frameworks/Swift/vapor/vapor-mysql.dockerfile b/frameworks/Swift/vapor/vapor-mysql.dockerfile new file mode 100644 index 00000000000..d32c75ce30c --- /dev/null +++ b/frameworks/Swift/vapor/vapor-mysql.dockerfile @@ -0,0 +1,8 @@ +FROM swift:4.1 + +ADD ./ /vapor +WORKDIR /vapor +RUN apt update -yqq && apt install -yqq cmysql libmysqlclient-dev +RUN swift build -c release + +CMD .build/release/vapor-tfb-mysql -e production -b 0.0.0.0:8080 \ No newline at end of file