Browse Source

Merge pull request #2570 from azeff/swift-vapor

Swift lang, Vapor framework.
Mike Smith 8 years ago
parent
commit
f3fc1dc2db
35 changed files with 943 additions and 0 deletions
  1. 1 0
      .travis.yml
  2. 24 0
      frameworks/Swift/README.md
  3. 67 0
      frameworks/Swift/vapor/.gitignore
  4. 2 0
      frameworks/Swift/vapor/Config/app.json
  5. 7 0
      frameworks/Swift/vapor/Config/clients.json
  6. 11 0
      frameworks/Swift/vapor/Config/crypto.json
  7. 14 0
      frameworks/Swift/vapor/Config/droplet.json
  8. 2 0
      frameworks/Swift/vapor/Config/production/app.json
  9. 7 0
      frameworks/Swift/vapor/Config/secrets/mongo.json
  10. 3 0
      frameworks/Swift/vapor/Config/secrets/mysql.json
  11. 3 0
      frameworks/Swift/vapor/Config/secrets/postgresql.json
  12. 7 0
      frameworks/Swift/vapor/Config/servers.json
  13. 24 0
      frameworks/Swift/vapor/Package.swift
  14. 44 0
      frameworks/Swift/vapor/README.md
  15. 10 0
      frameworks/Swift/vapor/Resources/Views/fortune.leaf
  16. 21 0
      frameworks/Swift/vapor/Sources/TfbCommon/Message.swift
  17. 21 0
      frameworks/Swift/vapor/Sources/TfbCommon/Request.swift
  18. 15 0
      frameworks/Swift/vapor/Sources/TfbCommon/ServerMiddleware.swift
  19. 10 0
      frameworks/Swift/vapor/Sources/TfbCommon/World.swift
  20. 58 0
      frameworks/Swift/vapor/Sources/vapor-tfb-mongodb/Fortune.swift
  21. 52 0
      frameworks/Swift/vapor/Sources/vapor-tfb-mongodb/World.swift
  22. 79 0
      frameworks/Swift/vapor/Sources/vapor-tfb-mongodb/main.swift
  23. 42 0
      frameworks/Swift/vapor/Sources/vapor-tfb-mysql/Fortune.swift
  24. 35 0
      frameworks/Swift/vapor/Sources/vapor-tfb-mysql/World.swift
  25. 80 0
      frameworks/Swift/vapor/Sources/vapor-tfb-mysql/main.swift
  26. 42 0
      frameworks/Swift/vapor/Sources/vapor-tfb-postgresql/Fortune.swift
  27. 35 0
      frameworks/Swift/vapor/Sources/vapor-tfb-postgresql/World.swift
  28. 79 0
      frameworks/Swift/vapor/Sources/vapor-tfb-postgresql/main.swift
  29. 12 0
      frameworks/Swift/vapor/app.json
  30. 77 0
      frameworks/Swift/vapor/benchmark_config.json
  31. 7 0
      frameworks/Swift/vapor/setup-mongodb.sh
  32. 7 0
      frameworks/Swift/vapor/setup-mysql.sh
  33. 7 0
      frameworks/Swift/vapor/setup-postgresql.sh
  34. 23 0
      toolset/setup/linux/languages/swift3.sh
  35. 15 0
      toolset/setup/linux/systools/cmake-3.5.sh

+ 1 - 0
.travis.yml

@@ -191,6 +191,7 @@ env:
     - "TESTDIR=Scala/s-server"
     - "TESTDIR=Scala/http4s"
     - "TESTDIR=Scala/finch"
+    - "TESTDIR=Swift/vapor"
     - "TESTDIR=Ur/urweb"
 
 before_script:

+ 24 - 0
frameworks/Swift/README.md

@@ -0,0 +1,24 @@
+# Swift frameworks
+
+The information below contains information specific to Python. For further guidance, review the [documentation](http://frameworkbenchmarks.readthedocs.org/en/latest/)
+
+## Infrastructure Software Versions
+
+[Swift 3](https://swift.org)
+
+## Adding a New Framework
+
+In order to declare that your framework requires Swift 3, you should have an `install.sh` that contains at least
+```
+#!/bin/bash
+
+fw_depends swift3
+
+```
+
+This installs the Swift 3.
+
+## Get Help
+
+### Swift Experts
+No experts listed, yet. If you're an expert, add yourself!

+ 67 - 0
frameworks/Swift/vapor/.gitignore

@@ -0,0 +1,67 @@
+# Xcode
+#
+# gitignore contributors: remember to update Global/Xcode.gitignore, Objective-C.gitignore & Swift.gitignore
+
+*.xcodeproj
+
+## Build generated
+build/
+DerivedData/
+
+## Various settings
+*.pbxuser
+!default.pbxuser
+*.mode1v3
+!default.mode1v3
+*.mode2v3
+!default.mode2v3
+*.perspectivev3
+!default.perspectivev3
+xcuserdata/
+
+## Other
+*.moved-aside
+*.xcuserstate
+
+## Obj-C/Swift specific
+*.hmap
+*.ipa
+*.dSYM.zip
+*.dSYM
+
+## Playgrounds
+timeline.xctimeline
+playground.xcworkspace
+
+# Swift Package Manager
+#
+# Add this line if you want to avoid checking in source code from Swift Package Manager dependencies.
+Packages/
+.build/
+
+# CocoaPods
+#
+# We recommend against adding the Pods directory to your .gitignore. However
+# you should judge for yourself, the pros and cons are mentioned at:
+# https://guides.cocoapods.org/using/using-cocoapods.html#should-i-check-the-pods-directory-into-source-control
+#
+# Pods/
+
+# Carthage
+#
+# Add this line if you want to avoid checking in source code from Carthage dependencies.
+# Carthage/Checkouts
+
+Carthage/Build
+
+# fastlane
+#
+# It is recommended to not store the screenshots in the git repo. Instead, use fastlane to re-generate the
+# screenshots whenever they are needed.
+# For more information about the recommended setup visit:
+# https://github.com/fastlane/fastlane/blob/master/fastlane/docs/Gitignore.md
+
+fastlane/report.xml
+fastlane/Preview.html
+fastlane/screenshots
+fastlane/test_output

+ 2 - 0
frameworks/Swift/vapor/Config/app.json

@@ -0,0 +1,2 @@
+{
+}

+ 7 - 0
frameworks/Swift/vapor/Config/clients.json

@@ -0,0 +1,7 @@
+{
+  "tls": {
+    "verifyHost": true,
+    "verifyCertificates": true,
+    "certificates": "defaults"
+  }
+}

+ 11 - 0
frameworks/Swift/vapor/Config/crypto.json

@@ -0,0 +1,11 @@
+{
+  "hash": {
+    "method": "sha256",
+    "key": "password"
+  },
+  "cipher": {
+    "method": "chacha20",
+    "key": "passwordpasswordpasswordpassword",
+    "iv": "password"
+  }
+}

+ 14 - 0
frameworks/Swift/vapor/Config/droplet.json

@@ -0,0 +1,14 @@
+{
+  "server": "engine",
+  "client": "engine",
+  "console": "terminal",
+  "log": "console",
+  "hash": "crypto",
+  "cipher": "crypto",
+  "middleware": {
+    "server": [
+      "date"
+    ],
+    "client": []
+  }
+}

+ 2 - 0
frameworks/Swift/vapor/Config/production/app.json

@@ -0,0 +1,2 @@
+{
+}

+ 7 - 0
frameworks/Swift/vapor/Config/secrets/mongo.json

@@ -0,0 +1,7 @@
+{
+  "user": "",
+  "password": "",
+  "database": "hello_world",
+  "port": "27017",
+  "host": "TFB-database"
+}

+ 3 - 0
frameworks/Swift/vapor/Config/secrets/mysql.json

@@ -0,0 +1,3 @@
+{
+  "url": "mysql://benchmarkdbuser:benchmarkdbpass@TFB-database:3306/hello_world"
+}

+ 3 - 0
frameworks/Swift/vapor/Config/secrets/postgresql.json

@@ -0,0 +1,3 @@
+{
+  "url": "psql://benchmarkdbuser:benchmarkdbpass@TFB-database:5432/hello_world"
+}

+ 7 - 0
frameworks/Swift/vapor/Config/servers.json

@@ -0,0 +1,7 @@
+{
+  "default": {
+    "port": "$PORT:8080",
+    "host": "0.0.0.0",
+    "securityLayer": "none"
+  }
+}

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

@@ -0,0 +1,24 @@
+import PackageDescription
+
+let package = Package(
+  name: "tfb",
+  targets: [
+    Target(name: "vapor-tfb-mysql", dependencies: ["TfbCommon"]),
+    Target(name: "vapor-tfb-postgresql", dependencies: ["TfbCommon"]),
+    Target(name: "vapor-tfb-mongodb", dependencies: ["TfbCommon"])
+  ],
+  dependencies: [
+    .Package(url: "https://github.com/vapor/vapor.git", majorVersion: 1, minor: 5),
+    .Package(url: "https://github.com/vapor/postgresql-provider", majorVersion:1, minor: 1),
+    .Package(url: "https://github.com/vapor/mysql-provider.git", majorVersion: 1, minor: 0),
+    .Package(url: "https://github.com/vapor/mongo-provider.git", majorVersion: 1, minor: 1)
+  ],
+  exclude: [
+    "Config",
+    "Database",
+    "Localization",
+    "Public",
+    "Resources",
+  ]
+)
+

+ 44 - 0
frameworks/Swift/vapor/README.md

@@ -0,0 +1,44 @@
+# [Vapor](https://vapor.codes/) Benchmark Test
+
+This is the Vapor portion of a [benchmarking tests suite](../../) comparing a variety of web development platforms.
+
+### Targets
+
+Three executable targets. Each listens on port 8080. All handle same URLs.  
+`vapor-tfb-mysql` is for MySQL  
+`vapor-tfb-postgresql` is for PostgreSQL  
+`vapor-tfb-mongodb` is for MongoDB
+
+### Dependencies
+
+Linked MySQL and PostgreSQL client libraries are required to build the app, please consult Vapor's documentation [for MySQL](https://github.com/vapor/mysql), [for PostgreSQL](https://github.com/vapor/postgresql).
+
+### Database
+
+MySQL  
+PostgreSQL  
+MongoDB
+
+## Versions
+[Swift 3.0.2](http://swift.org/)
+[Vapor 1.5](https://vapor.codes/)
+
+## Test URLs
+
+### JSON serialization test
+http://localhost:8080/json
+
+### Single database query test
+http://localhost:8080/db
+
+### Multiple database queries test
+http://localhost:8080/queries?queries=[1...500]
+
+### Fortunes test
+http://localhost:8080/fortunes
+
+### Database updates test
+http://localhost:8080/updates?queries=[1...500]
+
+### Plaintext test
+http://localhost:8080/plaintext

+ 10 - 0
frameworks/Swift/vapor/Resources/Views/fortune.leaf

@@ -0,0 +1,10 @@
+<!DOCTYPE html>
+<html>
+<head><title>Fortunes</title></head>
+<body>
+<table>
+<tr><th>id</th><th>message</th></tr>
+#loop(fortunes, "fortune") { <tr><td>#(fortune.id)</td><td>#(fortune.message)</td></tr> }
+</table>
+</body>
+</html>

+ 21 - 0
frameworks/Swift/vapor/Sources/TfbCommon/Message.swift

@@ -0,0 +1,21 @@
+import Node
+
+public struct Message {
+  public let message: String
+  
+  public init(_ message: String) {
+    self.message = message
+  }
+}
+
+extension Message: NodeConvertible {
+  public init(node: Node, in context: Context) throws {
+    message = try node.extract("message")
+  }
+  
+  public func makeNode(context: Context) throws -> Node {
+    return try Node(node: [
+      "message": message
+      ])
+  }
+}

+ 21 - 0
frameworks/Swift/vapor/Sources/TfbCommon/Request.swift

@@ -0,0 +1,21 @@
+import Vapor
+import HTTP
+
+public let validQueriesRange: ClosedRange<Int> = 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) -> Int {
+  let queriesParam = request.query?["queries"]?.int ?? 1
+  return clamp(queriesParam, to: validQueriesRange)
+}
+
+func clamp(_ value: Int, to: ClosedRange<Int>) -> Int {
+  return max(to.lowerBound, min(to.upperBound, value))
+}

+ 15 - 0
frameworks/Swift/vapor/Sources/TfbCommon/ServerMiddleware.swift

@@ -0,0 +1,15 @@
+import HTTP
+
+/// Middleware that adds `Server` HTTP header to response.
+public final class ServerMiddleware: Middleware {
+  
+  public init() { }
+  
+  public func respond(to request: Request, chainingTo next: Responder) throws -> Response {
+    let response = try next.respond(to: request)
+    
+    response.headers["Server"] = "Vapor"
+    
+    return response
+  }
+}

+ 10 - 0
frameworks/Swift/vapor/Sources/TfbCommon/World.swift

@@ -0,0 +1,10 @@
+import Random
+
+public struct WorldMeta {
+  private init() { }
+
+  public static let maxId: UInt32 = 10000
+  public static let randomId = { () -> UInt32 in UInt32(1) + CryptoRandom.uint32 % maxId }
+  public static let maxRandomNumber: Int32 = 10000
+  public static let randomRandomNumber = { () -> Int32 in Int32(1) + abs(CryptoRandom.int32) % maxRandomNumber }
+}

+ 58 - 0
frameworks/Swift/vapor/Sources/vapor-tfb-mongodb/Fortune.swift

@@ -0,0 +1,58 @@
+import Vapor
+
+final class Fortune: Model {
+
+  static let entity = "fortune"
+
+  var mongoId: Node?
+  var id: Node?
+  var message: String
+
+  static var idKey = "_id"
+
+  // For internal Vapor use
+  var exists: Bool = false
+
+  init(id: Int, message: String) {
+    self.id = Node(id)
+    self.message = message
+  }
+
+  init(node: Node, in context: Context) throws {
+    id = try node.extract("_id")
+    mongoId = try node.extract("id")
+    message = try node.extract("message")
+  }
+
+  func makeNode(context: Context) throws -> Node {
+    return try Node(node: [
+      "id": mongoId,
+      "_id": id,
+      "message": message
+    ])
+  }
+
+  func makeJSONNode() throws -> Node {
+    return try Node(node: [
+      "id": id?.int,
+      "message": message
+    ])
+  }
+
+  func makeJSON() throws -> JSON {
+    let node = try makeJSONNode()
+    return try JSON(node: node)
+  }
+
+  static func prepare(_ database: Database) throws {
+    try database.create("Fortune") { fortune in
+      fortune.id("id")
+      fortune.string("message")
+    }
+  }
+
+  static func revert(_ database: Database) throws {
+    try database.delete("Fortune")
+  }
+
+}

+ 52 - 0
frameworks/Swift/vapor/Sources/vapor-tfb-mongodb/World.swift

@@ -0,0 +1,52 @@
+import Vapor
+
+final class World: Model {
+  
+  static let entity = "world"
+  
+  var mongoId: Node?
+  var id: Node?
+  var randomNumber: Int32
+  
+  static var idKey = "_id"
+  
+  // For internal Vapor use
+  var exists: Bool = false
+  
+  init(node: Node, in context: Context) throws {
+    mongoId = try node.extract("id")
+    id = try node.extract("_id")
+    randomNumber = try node.extract("randomNumber")
+  }
+  
+  func makeNode(context: Context) throws -> Node {
+    return try Node(node: [
+      "id": mongoId,
+      "_id": id,
+      "randomNumber": randomNumber
+    ])
+  }
+  
+  func makeJSONNode() throws -> Node {
+    return try Node(node: [
+      "id": id?.int,
+      "randomNumber": randomNumber
+    ])
+  }
+  
+  func makeJSON() throws -> JSON {
+    let node = try makeJSONNode()
+    return try JSON(node: node)
+  }
+  
+  static func prepare(_ database: Database) throws {
+    try database.create("World") { world in
+      world.id("id")
+      world.int("randomNumber")
+    }
+  }
+  
+  static func revert(_ database: Database) throws {
+    try database.delete("World")
+  }
+}

+ 79 - 0
frameworks/Swift/vapor/Sources/vapor-tfb-mongodb/main.swift

@@ -0,0 +1,79 @@
+import Foundation
+import Vapor
+import JSON
+import HTTP
+import VaporMongo
+
+import TfbCommon
+
+let drop = Droplet()
+try drop.addProvider(VaporMongo.Provider.self)
+
+// All test types require `Server` and `Date` HTTP response headers.
+// Vapor has standard middleware that adds `Date` header.
+// We use custom middleware that adds `Server` header.
+drop.middleware.append(ServerMiddleware())
+
+// Normally we would add preparation for Fluent Models.
+//   `drop.preparations.append(World.self)` etc.
+// During preparation Fluent creates `fluent` table to track migrations.
+// But TFB environment does not grant user rights to create tables.
+// So we just configure our Models with correct database.
+World.database = drop.database
+Fortune.database = drop.database
+
+// Test type 1: JSON serialization
+drop.get("json") { req in
+  return try JSON(node: Message("Hello, World!"))
+}
+
+// Test type 2: Single database query
+drop.get("db") { _ in
+  let worldId = WorldMeta.randomId()
+  return try World.find(worldId)?.makeJSON() ?? JSON(node: .null)
+}
+
+// Test type 3: Multiple database queries
+drop.get("queries") { req in
+  let queries = queriesParam(for: req)
+  let ids = (1...queries).map({ _ in WorldMeta.randomId() })
+  let worlds = try ids.flatMap { try World.find($0)?.makeJSON() }
+  return JSON(worlds)
+}
+
+// Test type 4: Fortunes
+private let posixLocale = Locale(identifier: "en_US_POSIX")
+drop.get("fortunes") { _ in
+  var fortunes = try Fortune.all()
+  let additional = Fortune(id: 0, message: "Additional fortune added at request time.")
+  fortunes.insert(additional, at: 0)
+  fortunes.sort(by: { lhs, rhs -> Bool in
+    return lhs.message.compare(rhs.message, locale: posixLocale) == .orderedAscending
+  })
+  
+  let nodes = try fortunes.map { try $0.makeJSONNode() }
+  return try drop.view.make("fortune", ["fortunes": Node(nodes)])
+}
+
+// Test type 5: Database updates
+drop.get("updates") { req in
+  let queries = queriesParam(for: req)
+  let ids = (1...queries).map({ _ in WorldMeta.randomId() })
+  var worlds = try ids.flatMap { try World.find($0) }
+  worlds.forEach { $0.randomNumber = WorldMeta.randomRandomNumber() }
+  worlds = try worlds.flatMap { world in
+    var modifiedWorld = world
+    try modifiedWorld.save()
+    return modifiedWorld
+  }
+  let updatedWorlds = try worlds.flatMap { try $0.makeJSON() }
+  return JSON(updatedWorlds)
+}
+
+// Test type 6: Plaintext
+let helloWorldBuffer = "Hello, World!".utf8.array
+drop.get("plaintext") { req in
+  return Response(headers: ["Content-Type": "text/plain; charset=utf-8"], body: helloWorldBuffer)
+}
+
+drop.run()

+ 42 - 0
frameworks/Swift/vapor/Sources/vapor-tfb-mysql/Fortune.swift

@@ -0,0 +1,42 @@
+import Vapor
+
+final class Fortune: Model {
+
+  static let entity = "Fortune"
+
+  var id: Node?
+
+  var message: String
+
+  // For internal Vapor use
+  var exists: Bool = false
+
+  init(id: Int, message: String) {
+    self.id = Node(id)
+    self.message = message
+  }
+
+  init(node: Node, in context: Context) throws {
+    id = try node.extract("id")
+    message = try node.extract("message")
+  }
+
+  func makeNode(context: Context) throws -> Node {
+    return try Node(node: [
+      "id": id,
+      "message": message
+    ])
+  }
+
+  static func prepare(_ database: Database) throws {
+    try database.create("Fortune") { fortune in
+      fortune.id("id")
+      fortune.string("message")
+    }
+  }
+
+  static func revert(_ database: Database) throws {
+    try database.delete("Fortune")
+  }
+
+}

+ 35 - 0
frameworks/Swift/vapor/Sources/vapor-tfb-mysql/World.swift

@@ -0,0 +1,35 @@
+import Vapor
+
+final class World: Model {
+  
+  static let entity = "World"
+  
+  var id: Node?
+  var randomNumber: Int32
+  
+  // For internal Vapor use
+  var exists: Bool = false
+  
+  init(node: Node, in context: Context) throws {
+    id = try node.extract("id")
+    randomNumber = try node.extract("randomNumber")
+  }
+  
+  func makeNode(context: Context) throws -> Node {
+    return try Node(node: [
+      "id": id,
+      "randomNumber": randomNumber
+    ])
+  }
+  
+  static func prepare(_ database: Database) throws {
+    try database.create("World") { world in
+      world.id("id")
+      world.int("randomNumber")
+    }
+  }
+  
+  static func revert(_ database: Database) throws {
+    try database.delete("World")
+  }
+}

+ 80 - 0
frameworks/Swift/vapor/Sources/vapor-tfb-mysql/main.swift

@@ -0,0 +1,80 @@
+import Foundation
+import Vapor
+import JSON
+import HTTP
+import VaporMySQL
+
+import TfbCommon
+
+let drop = Droplet()
+try drop.addProvider(VaporMySQL.Provider.self)
+
+// All test types require `Server` and `Date` HTTP response headers.
+// Vapor has standard middleware that adds `Date` header.
+// We use custom middleware that adds `Server` header.
+drop.middleware.append(ServerMiddleware())
+
+// Normally we would add preparation for Fluent Models.
+//   `drop.preparations.append(World.self)` etc.
+// During preparation Fluent creates `fluent` table to track migrations.
+// But TFB environment does not grant user rights to create tables.
+// So we just configure our Models with correct database.
+World.database = drop.database
+Fortune.database = drop.database
+
+// Test type 1: JSON serialization
+drop.get("json") { req in
+  return try JSON(node: Message("Hello, World!"))
+}
+
+// Test type 2: Single database query
+drop.get("db") { _ in
+  let worldId = WorldMeta.randomId()
+  return try World.find(worldId)?.makeJSON() ?? JSON(node: .null)
+}
+
+// Test type 3: Multiple database queries
+drop.get("queries") { req in
+  let queries = queriesParam(for: req)
+  let ids = (1...queries).map({ _ in WorldMeta.randomId() })
+  let worlds = try ids.flatMap { try World.find($0)?.makeJSON() }
+  return JSON(worlds)
+}
+
+// Test type 4: Fortunes
+/// Locale for string comparison to workaround https://bugs.swift.org/browse/SR-530
+private let posixLocale = Locale(identifier: "en_US_POSIX")
+drop.get("fortunes") { _ in
+  var fortunes = try Fortune.all()
+  let additional = Fortune(id: 0, message: "Additional fortune added at request time.")
+  fortunes.insert(additional, at: 0)
+  fortunes.sort(by: { lhs, rhs -> Bool in
+    return lhs.message.compare(rhs.message, locale: posixLocale) == .orderedAscending
+  })
+  
+  let nodes = try fortunes.map { try $0.makeNode() }
+  return try drop.view.make("fortune", ["fortunes": Node(nodes)])
+}
+
+// Test type 5: Database updates
+drop.get("updates") { req in
+  let queries = queriesParam(for: req)
+  let ids = (1...queries).map({ _ in WorldMeta.randomId() })
+  var worlds = try ids.flatMap { try World.find($0) }
+  worlds.forEach { $0.randomNumber = WorldMeta.randomRandomNumber() }
+  worlds = try worlds.flatMap { world in
+    var modifiedWorld = world
+    try modifiedWorld.save()
+    return modifiedWorld
+  }
+  let updatedWorlds = try worlds.flatMap { try $0.makeJSON() }
+  return JSON(updatedWorlds)
+}
+
+// Test type 6: Plaintext
+let helloWorldBuffer = "Hello, World!".utf8.array
+drop.get("plaintext") { req in
+  return Response(headers: ["Content-Type": "text/plain; charset=utf-8"], body: helloWorldBuffer)
+}
+
+drop.run()

+ 42 - 0
frameworks/Swift/vapor/Sources/vapor-tfb-postgresql/Fortune.swift

@@ -0,0 +1,42 @@
+import Vapor
+
+final class Fortune: Model {
+
+  static let entity = "Fortune"
+
+  var id: Node?
+
+  var message: String
+
+  // For internal Vapor use
+  var exists: Bool = false
+
+  init(id: Int, message: String) {
+    self.id = Node(id)
+    self.message = message
+  }
+
+  init(node: Node, in context: Context) throws {
+    id = try node.extract("id")
+    message = try node.extract("message")
+  }
+
+  func makeNode(context: Context) throws -> Node {
+    return try Node(node: [
+      "id": id,
+      "message": message
+    ])
+  }
+
+  static func prepare(_ database: Database) throws {
+    try database.create("Fortune") { fortune in
+      fortune.id("id")
+      fortune.string("message")
+    }
+  }
+
+  static func revert(_ database: Database) throws {
+    try database.delete("Fortune")
+  }
+
+}

+ 35 - 0
frameworks/Swift/vapor/Sources/vapor-tfb-postgresql/World.swift

@@ -0,0 +1,35 @@
+import Vapor
+
+final class World: Model {
+  
+  static let entity = "World"
+  
+  var id: Node?
+  var randomNumber: Int32
+  
+  // For internal Vapor use
+  var exists: Bool = false
+  
+  init(node: Node, in context: Context) throws {
+    id = try node.extract("id")
+    randomNumber = try node.extract("randomnumber")
+  }
+  
+  func makeNode(context: Context) throws -> Node {
+    return try Node(node: [
+      "id": id,
+      "randomNumber": randomNumber
+    ])
+  }
+  
+  static func prepare(_ database: Database) throws {
+    try database.create("World") { world in
+      world.id("id")
+      world.int("randomNumber")
+    }
+  }
+  
+  static func revert(_ database: Database) throws {
+    try database.delete("World")
+  }
+}

+ 79 - 0
frameworks/Swift/vapor/Sources/vapor-tfb-postgresql/main.swift

@@ -0,0 +1,79 @@
+import Foundation
+import Vapor
+import JSON
+import HTTP
+import VaporPostgreSQL
+
+import TfbCommon
+
+let drop = Droplet()
+try drop.addProvider(VaporPostgreSQL.Provider.self)
+
+// All test types require `Server` and `Date` HTTP response headers.
+// Vapor has standard middleware that adds `Date` header.
+// We use custom middleware that adds `Server` header.
+drop.middleware.append(ServerMiddleware())
+
+// Normally we would add preparation for Fluent Models.
+//   `drop.preparations.append(World.self)` etc.
+// During preparation Fluent creates `fluent` table to track migrations.
+// But TFB environment does not grant user rights to create tables.
+// So we just configure our Models with correct database.
+World.database = drop.database
+Fortune.database = drop.database
+
+// Test type 1: JSON serialization
+drop.get("json") { req in
+  return try JSON(node: Message("Hello, World!"))
+}
+
+// Test type 2: Single database query
+drop.get("db") { _ in
+  let worldId = WorldMeta.randomId()
+  return try World.find(worldId)?.makeJSON() ?? JSON(node: .null)
+}
+
+// Test type 3: Multiple database queries
+drop.get("queries") { req in
+  let queries = queriesParam(for: req)
+  let ids = (1...queries).map({ _ in WorldMeta.randomId() })
+  let worlds = try ids.flatMap { try World.find($0)?.makeJSON() }
+  return JSON(worlds)
+}
+
+// Test type 4: Fortunes
+private let posixLocale = Locale(identifier: "en_US_POSIX")
+drop.get("fortunes") { _ in
+  var fortunes = try Fortune.all()
+  let additional = Fortune(id: 0, message: "Additional fortune added at request time.")
+  fortunes.insert(additional, at: 0)
+  fortunes.sort(by: { lhs, rhs -> Bool in
+    return lhs.message.compare(rhs.message, locale: posixLocale) == .orderedAscending
+  })
+  
+  let nodes = try fortunes.map { try $0.makeNode() }
+  return try drop.view.make("fortune", ["fortunes": Node(nodes)])
+}
+
+// Test type 5: Database updates
+drop.get("updates") { req in
+  let queries = queriesParam(for: req)
+  let ids = (1...queries).map({ _ in WorldMeta.randomId() })
+  var worlds = try ids.flatMap { try World.find($0) }
+  worlds.forEach { $0.randomNumber = WorldMeta.randomRandomNumber() }
+  worlds = try worlds.flatMap { world in
+    var modifiedWorld = world
+    try modifiedWorld.save()
+    return modifiedWorld
+  }
+  let updatedWorlds = try worlds.flatMap { try $0.makeJSON() }
+  return JSON(updatedWorlds)
+}
+
+// Test type 6: Plaintext
+let helloWorldBuffer = "Hello, World!".utf8.array
+drop.get("plaintext") { req in
+  return Response(headers: ["Content-Type": "text/plain; charset=utf-8"], body: helloWorldBuffer)
+}
+
+drop.run()

+ 12 - 0
frameworks/Swift/vapor/app.json

@@ -0,0 +1,12 @@
+{
+    "name": "VaporApp",
+    "scripts": {},
+    "env": {},
+    "formation": {},
+    "addons": [],
+    "buildpacks": [
+        {
+            "url": "https://github.com/vapor/heroku-buildpack"
+        }
+    ]
+}

+ 77 - 0
frameworks/Swift/vapor/benchmark_config.json

@@ -0,0 +1,77 @@
+{
+  "framework": "vapor",
+  "tests": [{
+    "default": {
+      "setup_file": "setup-mysql",
+      "json_url": "/json",
+      "db_url": "/db",
+      "query_url": "/queries?queries=",
+      "fortune_url": "/fortunes",
+      "update_url": "/updates?queries=",
+      "plaintext_url": "/plaintext",
+      "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": ""
+    },
+    "postgresql": {
+      "setup_file": "setup-postgresql",
+      "json_url": "/json",
+      "db_url": "/db",
+      "query_url": "/queries?queries=",
+      "fortune_url": "/fortunes",
+      "update_url": "/updates?queries=",
+      "plaintext_url": "/plaintext",
+      "port": 8080,
+      "approach": "Realistic",
+      "classification": "Fullstack",
+      "database": "Postgres",
+      "framework": "Vapor",
+      "language": "Swift",
+      "flavor": "None",
+      "orm": "Full",
+      "platform": "None",
+      "webserver": "None",
+      "os": "Linux",
+      "database_os": "Linux",
+      "display_name": "Vapor",
+      "notes": "",
+      "versus": ""
+    },
+    "mongodb": {
+      "setup_file": "setup-mongodb",
+      "json_url": "/json",
+      "db_url": "/db",
+      "query_url": "/queries?queries=",
+      "fortune_url": "/fortunes",
+      "update_url": "/updates?queries=",
+      "plaintext_url": "/plaintext",
+      "port": 8080,
+      "approach": "Realistic",
+      "classification": "Fullstack",
+      "database": "MongoDB",
+      "framework": "Vapor",
+      "language": "Swift",
+      "flavor": "None",
+      "orm": "Full",
+      "platform": "None",
+      "webserver": "None",
+      "os": "Linux",
+      "database_os": "Linux",
+      "display_name": "Vapor",
+      "notes": "",
+      "versus": ""
+    }
+  }]
+}

+ 7 - 0
frameworks/Swift/vapor/setup-mongodb.sh

@@ -0,0 +1,7 @@
+#!/bin/bash
+
+fw_depends swift3 mongodb mysql postgresql
+
+swift build -Xswiftc -DNOJSON -c release
+
+.build/release/vapor-tfb-mongodb --env=production &

+ 7 - 0
frameworks/Swift/vapor/setup-mysql.sh

@@ -0,0 +1,7 @@
+#!/bin/bash
+
+fw_depends swift3 mongodb mysql postgresql
+
+swift build -Xswiftc -DNOJSON -c release
+
+.build/release/vapor-tfb-mysql --env=production &

+ 7 - 0
frameworks/Swift/vapor/setup-postgresql.sh

@@ -0,0 +1,7 @@
+#!/bin/bash
+
+fw_depends swift3 mongodb mysql postgresql
+
+swift build -Xswiftc -DNOJSON -c release
+
+.build/release/vapor-tfb-postgresql --env=production &

+ 23 - 0
toolset/setup/linux/languages/swift3.sh

@@ -0,0 +1,23 @@
+#!/bin/bash
+
+fw_installed swift3 && return 0
+
+fw_depends clang-3.9
+
+sudo update-alternatives --install /usr/bin/clang clang /usr/bin/clang-3.9 100
+sudo update-alternatives --install /usr/bin/clang++ clang++ /usr/bin/clang++-3.9 100
+
+fw_depends cmake-3.5
+
+SWIFT_SNAPSHOT="swift-3.0.2-RELEASE"
+SWIFT_SNAPSHOT_LOWERCASE="swift-3.0.2-release"
+UBUNTU_VERSION="ubuntu14.04"
+UBUNTU_VERSION_NO_DOTS="ubuntu1404"
+
+fw_get -O https://swift.org/builds/$SWIFT_SNAPSHOT_LOWERCASE/$UBUNTU_VERSION_NO_DOTS/$SWIFT_SNAPSHOT/$SWIFT_SNAPSHOT-$UBUNTU_VERSION.tar.gz
+fw_untar $SWIFT_SNAPSHOT-$UBUNTU_VERSION.tar.gz
+mv $SWIFT_SNAPSHOT-$UBUNTU_VERSION swift
+
+echo -e "export PATH=${IROOT}/swift/usr/bin:\$PATH" >> $IROOT/swift3.installed
+
+source $IROOT/swift3.installed

+ 15 - 0
toolset/setup/linux/systools/cmake-3.5.sh

@@ -0,0 +1,15 @@
+#!/bin/bash
+
+fw_installed cmake-3.5 && return 0
+
+fw_get -O http://www.cmake.org/files/v3.5/cmake-3.5.2.tar.gz
+fw_untar cmake-3.5.2.tar.gz
+cd cmake-3.5.2
+./configure
+make
+sudo make install
+sudo update-alternatives --install /usr/bin/cmake cmake /usr/local/bin/cmake 1 --force
+
+echo -e "" >> $IROOT/cmake-3.5.installed
+
+source $IROOT/cmake-3.5.installed