Browse Source

Improve Swift/Vapor Implementation (#5415)

* vapor 4

* add db + query + server header

* use int32 (INTEGER)

* rm init method

* passing local tests

* update to latest vapor 4 beta + gitignore fixes

* simplify folder structure

* track Package.resolved

* fix cyclic xcode issue

* update max connections count
Tanner 5 năm trước cách đây
mục cha
commit
b49b34dc9f

+ 2 - 0
.gitignore

@@ -111,3 +111,5 @@ frameworks/PHP/ubiquity/.buildpath
 # swift
 # swift
 Package.resolved
 Package.resolved
 .swiftpm
 .swiftpm
+DerivedData
+.build

+ 0 - 17
frameworks/Swift/.gitignore

@@ -1,17 +0,0 @@
-
-# Created by https://www.gitignore.io/api/vapor
-
-### Vapor ###
-Config/secrets
-
-### Vapor Patch ###
-Packages
-.build
-xcuserdata
-*.xcodeproj
-DerivedData/
-.DS_Store
-
-# End of https://www.gitignore.io/api/vapor
-
-.idea/

+ 3 - 2
frameworks/Swift/README.md

@@ -1,10 +1,11 @@
 # Swift frameworks
 # Swift frameworks
 
 
-The information below contains information specific to Python. For further guidance, review the [documentation](https://frameworkbenchmarks.readthedocs.org/en/latest/)
+For further guidance, review the [documentation](https://frameworkbenchmarks.readthedocs.org/en/latest/)
 
 
 ## Infrastructure Software Versions
 ## Infrastructure Software Versions
 
 
 [Swift](https://swift.org)
 [Swift](https://swift.org)
 
 
 ### Swift Experts
 ### Swift Experts
-No experts listed, yet. If you're an expert, add yourself!
+
+[tanner0101](https://github.com/tanner0101)

+ 2 - 0
frameworks/Swift/vapor/.dockerignore

@@ -0,0 +1,2 @@
+**/.swiftpm
+**/.build

+ 0 - 178
frameworks/Swift/vapor/Package.resolved

@@ -1,178 +0,0 @@
-{
-  "object": {
-    "pins": [
-      {
-        "package": "Console",
-        "repositoryURL": "https://github.com/vapor/console.git",
-        "state": {
-          "branch": null,
-          "revision": "5b9796d39f201b3dd06800437abd9d774a455e57",
-          "version": "3.0.2"
-        }
-      },
-      {
-        "package": "Core",
-        "repositoryURL": "https://github.com/vapor/core.git",
-        "state": {
-          "branch": null,
-          "revision": "eb876a758733166a4fb20f3f0a17b480c5ea563e",
-          "version": "3.4.3"
-        }
-      },
-      {
-        "package": "Crypto",
-        "repositoryURL": "https://github.com/vapor/crypto.git",
-        "state": {
-          "branch": null,
-          "revision": "4b85405430df1892ee3aa1554bdb477e96cf46ad",
-          "version": "3.2.0"
-        }
-      },
-      {
-        "package": "DatabaseKit",
-        "repositoryURL": "https://github.com/vapor/database-kit.git",
-        "state": {
-          "branch": null,
-          "revision": "3a17dbbe9be5f8c37703e4b7982c1332ad6b00c4",
-          "version": "1.3.1"
-        }
-      },
-      {
-        "package": "HTTP",
-        "repositoryURL": "https://github.com/vapor/http.git",
-        "state": {
-          "branch": null,
-          "revision": "9e3eff9dfa4df7fc282bf27f801c72b3ffbfd984",
-          "version": "3.1.4"
-        }
-      },
-      {
-        "package": "Multipart",
-        "repositoryURL": "https://github.com/vapor/multipart.git",
-        "state": {
-          "branch": null,
-          "revision": "e57007c23a52b68e44ebdfc70cbe882a7c4f1ec3",
-          "version": "3.0.2"
-        }
-      },
-      {
-        "package": "PostgreSQL",
-        "repositoryURL": "https://github.com/vapor/postgresql.git",
-        "state": {
-          "branch": null,
-          "revision": "c8b01a35efae6737e8d896440f5eec9b219571d9",
-          "version": "1.1.0"
-        }
-      },
-      {
-        "package": "Routing",
-        "repositoryURL": "https://github.com/vapor/routing.git",
-        "state": {
-          "branch": null,
-          "revision": "3219e328491b0853b8554c5a694add344d2c6cfb",
-          "version": "3.0.1"
-        }
-      },
-      {
-        "package": "Service",
-        "repositoryURL": "https://github.com/vapor/service.git",
-        "state": {
-          "branch": null,
-          "revision": "281a70b69783891900be31a9e70051b6fe19e146",
-          "version": "1.0.0"
-        }
-      },
-      {
-        "package": "SQL",
-        "repositoryURL": "https://github.com/vapor/sql.git",
-        "state": {
-          "branch": null,
-          "revision": "a35cf1dced4ddd32bb2dc8b6e765aea7bcf8d6e0",
-          "version": "2.1.0"
-        }
-      },
-      {
-        "package": "swift-nio",
-        "repositoryURL": "https://github.com/apple/swift-nio.git",
-        "state": {
-          "branch": null,
-          "revision": "5d8148c8b45dfb449276557f22120694567dd1d2",
-          "version": "1.9.5"
-        }
-      },
-      {
-        "package": "swift-nio-ssl",
-        "repositoryURL": "https://github.com/apple/swift-nio-ssl.git",
-        "state": {
-          "branch": null,
-          "revision": "8380fa29a2af784b067d8ee01c956626ca29f172",
-          "version": "1.3.1"
-        }
-      },
-      {
-        "package": "swift-nio-ssl-support",
-        "repositoryURL": "https://github.com/apple/swift-nio-ssl-support.git",
-        "state": {
-          "branch": null,
-          "revision": "c02eec4e0e6d351cd092938cf44195a8e669f555",
-          "version": "1.0.0"
-        }
-      },
-      {
-        "package": "swift-nio-zlib-support",
-        "repositoryURL": "https://github.com/apple/swift-nio-zlib-support.git",
-        "state": {
-          "branch": null,
-          "revision": "37760e9a52030bb9011972c5213c3350fa9d41fd",
-          "version": "1.0.0"
-        }
-      },
-      {
-        "package": "TemplateKit",
-        "repositoryURL": "https://github.com/vapor/template-kit.git",
-        "state": {
-          "branch": null,
-          "revision": "db35b1c35aabd0f5db3abca0cfda7becfe9c43e2",
-          "version": "1.1.0"
-        }
-      },
-      {
-        "package": "URLEncodedForm",
-        "repositoryURL": "https://github.com/vapor/url-encoded-form.git",
-        "state": {
-          "branch": null,
-          "revision": "932024f363ee5ff59059cf7d67194a1c271d3d0c",
-          "version": "1.0.5"
-        }
-      },
-      {
-        "package": "Validation",
-        "repositoryURL": "https://github.com/vapor/validation.git",
-        "state": {
-          "branch": null,
-          "revision": "156f8adeac3440e868da3757777884efbc6ec0cc",
-          "version": "2.1.0"
-        }
-      },
-      {
-        "package": "Vapor",
-        "repositoryURL": "https://github.com/vapor/vapor.git",
-        "state": {
-          "branch": null,
-          "revision": "157d3b15336caa882662cc75024dd04b2e225246",
-          "version": "3.1.0"
-        }
-      },
-      {
-        "package": "WebSocket",
-        "repositoryURL": "https://github.com/vapor/websocket.git",
-        "state": {
-          "branch": null,
-          "revision": "149af03348f60ac8b84defdf277112d62fd8c704",
-          "version": "1.0.2"
-        }
-      }
-    ]
-  },
-  "version": 1
-}

+ 0 - 15
frameworks/Swift/vapor/Package.swift

@@ -1,15 +0,0 @@
-// swift-tools-version:4.2
-import PackageDescription
-
-let package = Package(
-    name: "tefb",
-    dependencies: [
-        .package(url: "https://github.com/vapor/vapor.git", from: "3.1.0"),
-        .package(url: "https://github.com/vapor/postgresql.git", from: "1.0.2"),
-    ],
-    targets: [
-        .target(name: "App", dependencies: ["PostgreSQL", "Vapor"]),
-        .target(name: "Run", dependencies: ["App"])
-    ]
-)
-

+ 7 - 2
frameworks/Swift/vapor/README.md

@@ -11,19 +11,24 @@ One executable target. Listens on port 8080.
 PostgreSQL
 PostgreSQL
 
 
 ## Versions
 ## Versions
-[Swift 4.2](https://swift.org/)
-[Vapor 3.1.0](https://vapor.codes/)
+
+[Swift 5.1](https://swift.org/)
+[Vapor 4](https://vapor.codes/)
 
 
 ## Test URLs: `vapor`
 ## Test URLs: `vapor`
 
 
 ### Plaintext test
 ### Plaintext test
+
 http://localhost:8080/plaintext
 http://localhost:8080/plaintext
 
 
 #### JSON serialization test
 #### JSON serialization test
+
 http://localhost:8080/json
 http://localhost:8080/json
 
 
 ### Single database query test
 ### Single database query test
+
 http://localhost:8080/db
 http://localhost:8080/db
 
 
 ### Multiple database queries test
 ### Multiple database queries test
+
 http://localhost:8080/queries/[1...500]
 http://localhost:8080/queries/[1...500]

+ 0 - 16
frameworks/Swift/vapor/Sources/App/Controllers/JSONController.swift

@@ -1,16 +0,0 @@
-//
-//  JSONController.swift
-//  App
-//
-//  Created by Gopal Sharma on 7/29/18.
-//
-
-import Vapor
-
-final class JSONController {
-
-    func get(_ req: Request) throws -> Message {
-        return Message(message: "Hello, world!")
-    }
-
-}

+ 0 - 16
frameworks/Swift/vapor/Sources/App/Controllers/PlainTextController.swift

@@ -1,16 +0,0 @@
-//
-//  PlainTextController.swift
-//  App
-//
-//  Created by Gopal Sharma on 7/29/18.
-//
-
-import Vapor
-
-final class PlainTextController {
-
-    func get(_ req: Request) throws -> String {
-        return "Hello, world!"
-    }
-
-}

+ 0 - 65
frameworks/Swift/vapor/Sources/App/Controllers/WorldController.swift

@@ -1,65 +0,0 @@
-import Logging
-import PostgreSQL
-import Vapor
-
-struct BadRequestError : AbortError {
-    var status: HTTPResponseStatus {
-        return .badRequest
-    }
-
-    var reason: String {
-        return "Bad request"
-    }
-
-    var identifier: String {
-        return "badRequest"
-    }
-
-}
-
-final class WorldController {
-
-    func get(_ req: Request) throws -> Future<World> {
-        return req.withPooledConnection(to: .psql) { (conn: PostgreSQLDatabase.Connection) in
-            return conn.select().all().from(World.self).where(\World.id == Int.random(in: 1...10_000)).all(decoding: World.self)
-        }.map { (rows: [World]) in
-            if let first = rows.first {
-                return first
-            }
-            throw NotFound(rootCause: nil)
-        }
-    }
-
-    func queries(_ req: Request) throws -> Future<[World]> {
-        let numQueries: Int
-        do {
-            let queries = try req.query.get(Int.self, at: "queries")
-            if queries < 1 {
-                numQueries = 1
-            } else if queries > 500 {
-                numQueries = 500
-            } else {
-                numQueries = queries
-            }
-        } catch {
-            numQueries = 1
-        }
-        var futures: [Future<World>] = []
-        for _ in 0..<numQueries {
-            futures.append(
-                req.withPooledConnection(to: .psql) { (conn: PostgreSQLDatabase.Connection) in
-                    return conn.select().all().from(World.self).where(\World.id == Int.random(in: 1...10_000)).all(decoding: World.self)
-                }.map { (rows: [World]) in
-                    if let first = rows.first {
-                        return first
-                    }
-                    throw NotFound(rootCause: nil)
-                }
-            )
-        }
-        return Future<[World]>.reduce(into: [], futures, eventLoop: req.eventLoop) { (worlds, world) in
-            return worlds.append(world)
-        }
-    }
-
-}

+ 0 - 5
frameworks/Swift/vapor/Sources/App/Models/Message.swift

@@ -1,5 +0,0 @@
-import Vapor
-
-struct Message : Content {
-    let message: String
-}

+ 0 - 13
frameworks/Swift/vapor/Sources/App/Models/World.swift

@@ -1,13 +0,0 @@
-import PostgreSQL
-import Vapor
-
-struct World: Codable, Content, SQLTable {
-    static let sqlTableIdentifierString = "World"
-    var id: Int?
-    var randomnumber: Int
-
-    init(id: Int? = nil, randomNumber: Int) {
-        self.id = id
-        self.randomnumber = randomNumber
-    }
-}

+ 0 - 12
frameworks/Swift/vapor/Sources/App/app.swift

@@ -1,12 +0,0 @@
-import Vapor
-
-/// Creates an instance of Application. This is called from main.swift in the run target.
-public func app(_ env: Environment) throws -> Application {
-    var config = Config.default()
-    var env = env
-    var services = Services.default()
-    try configure(&config, &env, &services)
-    let app = try Application(config: config, environment: env, services: services)
-    try boot(app)
-    return app
-}

+ 0 - 6
frameworks/Swift/vapor/Sources/App/boot.swift

@@ -1,6 +0,0 @@
-import Vapor
-
-/// Called after your application has initialized.
-public func boot(_ app: Application) throws {
-    // your code here
-}

+ 0 - 39
frameworks/Swift/vapor/Sources/App/configure.swift

@@ -1,39 +0,0 @@
-import PostgreSQL
-import Vapor
-
-class ServerHeaderMiddleWare : Middleware, Service {
-    func respond(to request: Request, chainingTo next: Responder) throws -> EventLoopFuture<Response> {
-        return try next.respond(to: request).map { response in
-            response.http.headers.add(name: HTTPHeaderName.server, value: "Vapor")
-            return response
-        }
-    }
-}
-
-public func configure(_ config: inout Config, _ env: inout Environment, _ services: inout Services) throws {
-    let router = EngineRouter.default()
-    try routes(router)
-    services.register(router, as: Router.self)
-    services.register(ServerHeaderMiddleWare.self) { _ in
-        return ServerHeaderMiddleWare()
-    }
-
-    var middlewares = MiddlewareConfig()
-    middlewares.use(ErrorMiddleware.self)
-    middlewares.use(ServerHeaderMiddleWare.self)
-    services.register(middlewares)
-
-    try services.register(PostgreSQLProvider())
-
-    let postgresql = PostgreSQLDatabase(
-        config: PostgreSQLDatabaseConfig(
-            hostname: "tfb-database",
-            username: "benchmarkdbuser",
-            database: "hello_world",
-            password: "benchmarkdbpass"
-    ))
-
-    var databases = DatabasesConfig()
-    databases.add(database: postgresql, as: .psql)
-    services.register(databases)
-}

+ 0 - 15
frameworks/Swift/vapor/Sources/App/routes.swift

@@ -1,15 +0,0 @@
-import Vapor
-
-/// Register your application's routes here.
-public func routes(_ router: Router) throws {
-
-    let jsonController = JSONController()
-    router.get("json", use: jsonController.get)
-
-    let plainTextController = PlainTextController()
-    router.get("plaintext", use: plainTextController.get)
-
-    let worldController = WorldController()
-    router.get("db", use: worldController.get)
-    router.get("queries", use: worldController.queries)
-}

+ 0 - 3
frameworks/Swift/vapor/Sources/Run/main.swift

@@ -1,3 +0,0 @@
-import App
-
-try app(.detect()).run()

+ 24 - 0
frameworks/Swift/vapor/app/Package.swift

@@ -0,0 +1,24 @@
+// swift-tools-version:5.1
+import PackageDescription
+
+let package = Package(
+    name: "app",
+    platforms: [
+        .macOS(.v10_15)
+    ],
+    products: [
+        .executable(name: "app", targets: ["App"])
+    ],
+    dependencies: [
+        .package(url: "https://github.com/vapor/vapor.git", from: "4.0.0-beta"),
+        .package(url: "https://github.com/vapor/fluent.git", from: "4.0.0-beta"),
+        .package(url: "https://github.com/vapor/fluent-postgres-driver.git", from: "2.0.0-beta"),
+    ],
+    targets: [
+        .target(name: "App", dependencies: [
+            "Fluent",
+            "FluentPostgresDriver",
+            "Vapor"
+        ], path: "Sources")
+    ]
+)

+ 10 - 0
frameworks/Swift/vapor/app/Sources/ServerMiddleware.swift

@@ -0,0 +1,10 @@
+import Vapor
+
+struct ServerMiddleware: Middleware {
+    func respond(to request: Request, chainingTo next: Responder) -> EventLoopFuture<Response> {
+        next.respond(to: request).map { response in
+            response.headers.add(name: .server, value: "Vapor")
+            return response
+        }
+    }
+}

+ 25 - 0
frameworks/Swift/vapor/app/Sources/Utilities.swift

@@ -0,0 +1,25 @@
+import Vapor
+
+extension Int {
+    func bounded(to range: ClosedRange<Int>) -> Int {
+        switch self {
+        case ...range.lowerBound:
+            return range.lowerBound
+        case range.upperBound...:
+            return range.upperBound
+        default:
+            return self
+        }
+    }
+}
+
+extension System {
+    // tfb-server (aka, citrine) uses 28 hyper-threaded cores
+    // postgresql.conf specifies max_connections = 2000
+    //
+    // 2000 / (28 * 2) = 35.7 (theoretical max)
+    //
+    // https://github.com/TechEmpower/FrameworkBenchmarks/wiki/Project-Information-Environment#citrine-self-hosted
+    // https://github.com/TechEmpower/FrameworkBenchmarks/blob/master/toolset/databases/postgres/postgresql.conf#L64
+    static var maxConnectionsPerEventLoop: Int { 32 }
+}

+ 14 - 0
frameworks/Swift/vapor/app/Sources/World.swift

@@ -0,0 +1,14 @@
+import Fluent
+import Vapor
+
+final class World: Model, Content {
+    static let schema = "World"
+    
+    @ID(key: "id")
+    var id: Int32?
+    
+    @Field(key: "randomnumber")
+    var randomnumber: Int
+    
+    init() { }
+}

+ 46 - 0
frameworks/Swift/vapor/app/Sources/main.swift

@@ -0,0 +1,46 @@
+import Fluent
+import FluentPostgresDriver
+import Vapor
+
+var env = try Environment.detect()
+try LoggingSystem.bootstrap(from: &env)
+
+let app = Application(env)
+defer { app.shutdown() }
+
+app.middleware.use(ServerMiddleware())
+
+app.logger.notice("💧 VAPOR")
+app.logger.notice("System.coreCount: \(System.coreCount)")
+app.logger.notice("System.maxConnectionsPerEventLoop: \(System.maxConnectionsPerEventLoop)")
+
+app.databases.use(.postgres(
+    hostname: "tfb-database",
+    username: "benchmarkdbuser",
+    password: "benchmarkdbpass",
+    database: "hello_world",
+    maxConnectionsPerEventLoop: System.maxConnectionsPerEventLoop
+), as: .psql)
+
+app.get("plaintext") { req in
+    "Hello, world!"
+}
+
+app.get("json") { req in
+    ["message": "Hello, world!"]
+}
+
+app.get("db") { req in
+    World.find(.random(in: 1 ... 10_000), on: req.db)
+        .unwrap(or: Abort(.notFound))
+}
+
+app.get("queries") { req -> EventLoopFuture<[World]> in
+    let queries = (req.query["queries"] ?? 1).bounded(to: 1...500)
+    return (0 ..< queries).map { _ -> EventLoopFuture<World> in
+        World.find(.random(in: 1 ... 10_000), on: req.db)
+            .unwrap(or: Abort(.notFound))
+    }.flatten(on: req.eventLoop)
+}
+
+try app.run()

+ 1 - 1
frameworks/Swift/vapor/benchmark_config.json

@@ -14,7 +14,7 @@
         "framework": "Vapor",
         "framework": "Vapor",
         "language": "Swift",
         "language": "Swift",
         "flavor": "None",
         "flavor": "None",
-        "orm": "Micro",
+        "orm": "Full",
         "platform": "None",
         "platform": "None",
         "webserver": "None",
         "webserver": "None",
         "os": "Linux",
         "os": "Linux",

+ 25 - 5
frameworks/Swift/vapor/vapor.dockerfile

@@ -1,7 +1,27 @@
-FROM swift:4.2
+# ================================
+# Build image
+# ================================
+FROM vapor/swift:5.1 as build
+WORKDIR /build
 
 
-ADD ./ /vapor
-WORKDIR /vapor
-RUN swift build -c release
+# Copy entire repo into container
+COPY ./app .
 
 
-CMD .build/release/Run -e production -b 0.0.0.0:8080
+# Compile with optimizations
+RUN swift build \
+	--enable-test-discovery \
+	-c release
+
+# ================================
+# Run image
+# ================================
+FROM vapor/ubuntu:18.04
+WORKDIR /run
+
+# Copy build artifacts
+COPY --from=build /build/.build/release /run
+
+# Copy Swift runtime libraries
+COPY --from=build /usr/lib/swift/ /usr/lib/swift/
+
+ENTRYPOINT ["./app", "serve", "--env", "production", "--hostname", "0.0.0.0", "--port", "8080"]