Swift on the Server State of the Union Chris Bailey @Chris__Bailey
Swift on the ServerState of the Union
Chris Bailey@Chris__Bailey
30-10-201707-1 1-201607-1 1-2016
Swift 3.0
Swift 3.0
Swift 3.0
Swift 3.0
Swift 3.0
Swift 3.0
IBM Cloud
IBM Cloud
DRINKS
SOAKSCOVE
RS
Server API Workgroup
0.1.0: Draft HTTP Server API
import Foundationimport HTTP
func hello(request: HTTPRequest, response: HTTPResponseWriter ) -> HTTPBodyProcessing { response.writeHeader(status: .ok) response.writeBody("Hello, World!") response.done() return .discardBody}
let server = HTTPServer()try! server.start(port: 8080, handler: hello)
RunLoop.current.run()
0.1.0: Draft HTTP Server API
import Foundationimport HTTP
func hello(request: HTTPRequest, response: HTTPResponseWriter ) -> HTTPBodyProcessing { response.writeHeader(status: .ok) response.writeBody("Hello, World!") response.done() return .discardBody}
let server = HTTPServer()try! server.start(port: 8080, handler: hello)
RunLoop.current.run()
0.1.x: Draft HTTP Server API
0.2.x: Draft TLS support for HTTPS
import Foundationimport HTTP
func hello(request: HTTPRequest, response: HTTPResponseWriter ) -> HTTPBodyProcessing { response.writeHeader(status: .ok) response.writeBody("Hello, World!") response.done() return .discardBody}
let server = HTTPServer()try! server.start(port: 8080, handler: hello)
RunLoop.current.run()
0.1.x: Draft HTTP Server API
0.2.x: Draft TLS support for HTTPS
We need to talk About Foundation
0
20
40
60
80
100 Foundation: • ~1,700 APIs
0
20
40
60
80
100 Foundation: • ~1,700 APIs
Used on GitHub in: • 39.7% of all Obj-C files that import
0
20
40
60
80
100 Foundation: • ~1,700 APIs
Used on GitHub in: • 39.7% of all Obj-C files that import
• 90.7% of all Swift files that import
0
20
40
60
80
100
Open Source
0
20
40
60
80
100
Open Source
Swift 3.0
Sho Ikeda@ikesyo
Ian Partridge @ianpartridge
Pushkar N Kulkarni @pushkarnk
Bartek Chlebek @bubski
David Dunn @ddunn2
John Holdsworth @johnno1962
Philippe Hausler @phausler
Alex Blewitt @alblue
Simon Evans @spevans
Sergey Minakov @naithar
0
20
40
60
80
100
Open Source
Swift 3.0
Sho Ikeda@ikesyo
Ian Partridge @ianpartridge
Pushkar N Kulkarni @pushkarnk
Bartek Chlebek @bubski
David Dunn @ddunn2
John Holdsworth @johnno1962
Philippe Hausler @phausler
Alex Blewitt @alblue
Simon Evans @spevans
Sergey Minakov @naithar
0
20
40
60
80
100
Open Source
Swift 3.0
Sho Ikeda@ikesyo
Ian Partridge @ianpartridge
Pushkar N Kulkarni @pushkarnk
Bartek Chlebek @bubski
David Dunn @ddunn2
John Holdsworth @johnno1962
Philippe Hausler @phausler
Alex Blewitt @alblue
Simon Evans @spevans
Sergey Minakov @naithar
0
20
40
60
80
100
Open Source
Swift 3.0
Ian Partridge @ianpartridge
Pushkar N Kulkarni @pushkarnk
Bartek Chlebek @bubski
David Dunn @ddunn2
John Holdsworth @johnno1962
Philippe Hausler @phausler
Alex Blewitt @alblue
Simon Evans @spevans
Sergey Minakov @naithar
0
20
40
60
80
100
Sho Ikeda@ikesyo
Open Source
Swift 3.0
Ian Partridge @ianpartridge
Pushkar N Kulkarni @pushkarnk
Bartek Chlebek @bubski
David Dunn @ddunn2
John Holdsworth @johnno1962
Philippe Hausler @phausler
Alex Blewitt @alblue
Sergey Minakov @naithar
0
20
40
60
80
100
Sho Ikeda@ikesyo
Simon Evans @spevans
Open Source
Swift 3.0
Ian Partridge @ianpartridge
Pushkar N Kulkarni @pushkarnk
David Dunn @ddunn2
John Holdsworth @johnno1962
Philippe Hausler @phausler
Alex Blewitt @alblue
Sergey Minakov @naithar
0
20
40
60
80
100
Sho Ikeda@ikesyo
Simon Evans @spevans
Bartek Chlebek @bubski
Open Source
Swift 3.0
Ian Partridge @ianpartridge
Pushkar N Kulkarni @pushkarnk
David Dunn @ddunn2
John Holdsworth @johnno1962
Philippe Hausler @phausler
Alex Blewitt @alblue
0
20
40
60
80
100
Sho Ikeda@ikesyo
Simon Evans @spevans
Bartek Chlebek @bubski
Sergey Minakov @naithar
Open Source
Swift 3.0
Ian Partridge @ianpartridge
Pushkar N Kulkarni @pushkarnk
David Dunn @ddunn2
John Holdsworth @johnno1962
Philippe Hausler @phausler
Alex Blewitt @alblue
0
20
40
60
80
100
Sho Ikeda@ikesyo
Simon Evans @spevans
Bartek Chlebek @bubski
Sergey Minakov @naithar
Mamatha Busi @mamabusi
Nethra Ravindran @nethraravindran
Open Source
Swift 3.0
Sho Ikeda@ikesyo
Ian Partridge @ianpartridge
Pushkar N Kulkarni @pushkarnk
Bartek Chlebek @bubski
David Dunn @ddunn2
John Holdsworth @johnno1962
Philippe Hausler @phausler
Alex Blewitt @alblue
Simon Evans @spevans
Sergey Minakov @naithar
0
20
40
60
80
100
Mamatha Busi @mamabusi
Nethra Ravindran @nethraravindran
Open Source
Swift 3.0
Today
Commercial Support for Swift on Linux
from
Commercial Support for Swift on Linux
from
Commercial Support for Swift on Linux
IBM Cloud
from
Commercial Support for Kitura Ecosystem
from
Commercial Support for Kitura Ecosystem
from
IBM Cloud
07-1 1-2016
“Software interop is hard” —Rocket Scientists
Deploy Deploy
Deploy DeployGenerate
{ "name": "", "photo": "", "dateOfBirth": ""}
{ "name": "", "photo": "", "dateOfBirth": { "year": "month": "day": }}
struct Profile { var name: String var photo: Data var dateOfBirth: Date}
Swift
var profile: [String : Any]
Swift
Codable
let json = JSON(data: data) guard json["name"].exists() else { response.status(.unprocessableEntity) response.send(json: JSON([ "error": "Missing reqired property `name`" ])) next() } guard let name = json["name"].string else { response.status(.unprocessableEntity) response.send(json: JSON([ "error": "Type mismatch, `name` expected to be a String" ])) next() } guard json["photo"].exists() else { response.status(.unprocessableEntity) response.send(json: JSON([ "error": "Missing reqired property photo`" ])) next() } guard let photoString = json[“photo"].string else { response.status(.unprocessableEntity) response.send(json: JSON([ "error": "Type mismatch, `photo` expected to be a String" ])) next() } guard let photo = Data(base64Encoded: photoString) else { response.status(.unprocessableEntity) response.send(json: JSON([ "error": "Type mismatch, `photo` expected to be Base64 encoded data" ])) next() } let profile = Profile(name: name, photo: photo)
var profile: Profile do { var data = Data() _ = try request.read(into: &data) profile = try JSONDecoder().decode(Profile.Type, from: data) } catch let error { response.status(.unprocessableEntity) response.send(try JSONEncoder().encode(error)) next() }
var profile: Profile do { profile = try request.read(as: Profile.self) } catch let error { response.status(.unprocessableEntity) response.send(try JSONEncoder().encode(error)) next() }
var profile: Profile do { profile = try request.read(as: Profile.self) } catch let error { response.status(.unprocessableEntity) response.send(error) next() }
router.post,(“/profile", handler: storeProfile)
func storeProfile(request: RouterRequest, response: RouterResponse, next: @escaping () -> Void) { guard let contentType = request.headers["Content-Type"], contentType.hasPrefix("application/json") else { response.status(.unsupportedMediaType) response.send(error) return next() }
var profile: Profile do { profile = try request.read(as: Profile.self) } catch let error { response.status(.unprocessableEntity) response.send(error) next() } ... }
router.post,(“/profile", handler: storeProfile)
func storeProfile(request: RouterRequest, response: RouterResponse, next: @escaping () -> Void) { guard let contentType = request.headers["Content-Type"], contentType.hasPrefix("application/json") else { response.status(.unsupportedMediaType) response.send(error) return next() }
var profile: Profile do { profile = try request.read(as: Profile.self) } catch let error { response.status(.unprocessableEntity) response.send(error) next() } ... }
Web Request
router.post,(“/profile", handler: storeProfile)
func storeProfile(request: RouterRequest, response: RouterResponse, next: @escaping () -> Void) { guard let contentType = request.headers["Content-Type"], contentType.hasPrefix("application/json") else { response.status(.unsupportedMediaType) response.send(error) return next() }
var profile: Profile do { profile = try request.read(as: Profile.self) } catch let error { response.status(.unprocessableEntity) response.send(error) next() } ... }
Web RequestWeb Response
router.post,(“/profile", handler: storeProfile)
func storeProfile(request: RouterRequest, response: RouterResponse, next: @escaping () -> Void) { guard let contentType = request.headers["Content-Type"], contentType.hasPrefix("application/json") else { response.status(.unsupportedMediaType) response.send(error) return next() }
var profile: Profile do { profile = try request.read(as: Profile.self) } catch let error { response.status(.unprocessableEntity) response.send(error) next() } ... }
Web RequestWeb Response
Next ??
router.post,(“/profile", handler: storeProfile)
func storeProfile(request: RouterRequest, response: RouterResponse, next: @escaping () -> Void) { guard let contentType = request.headers["Content-Type"], contentType.hasPrefix("application/json") else { response.status(.unsupportedMediaType) response.send(error) return next() }
var profile: Profile do { profile = try request.read(as: Profile.self) } catch let error { response.status(.unprocessableEntity) response.send(error) next() } ... }
Web RequestWeb Response
Next ??
Validate and Convert parameters
3/10
func store(profile: Profile, respondWith: @escaping (Profile?, Error?) -> Void) -> Void {
... }
func store(profile: Profile, respondWith: @escaping (Profile?, Error?) -> Void) -> Void {
... }
Swift Type
func store(profile: Profile, respondWith: @escaping (Profile?, Error?) -> Void) -> Void {
... }
Swift Type
Completion Handler
Codable Routing
func store(profile: Profile, respondWith: @escaping (Profile?, Error?) -> Void) -> Void {
... }
func store(profile: Profile, respondWith: @escaping (Profile?, Error?) -> Void) -> Void {
... }
Swift Type (Codable)
func store(profile: Profile, respondWith: @escaping (Profile?, Error?) -> Void) -> Void {
... }
Swift Type (Codable)
Swift Type (RequestError)
func store(profile: Profile, respondWith: @escaping (Profile?, Error?) -> Void) -> Void {
... }
func get(profile id: Int, respondWith: @escaping (Profile?, Error?) -> Void) -> Void {
... }
Swift Type (Codable)
Swift Type (RequestError)
func store(profile: Profile, respondWith: @escaping (Profile?, Error?) -> Void) -> Void {
... }
func get(profile id: Int, respondWith: @escaping (Profile?, Error?) -> Void) -> Void {
... }
Swift Type (Codable)
Swift Type (Identifier)
Swift Type (RequestError)
func store(profile: Profile, respondWith: @escaping (Profile?, Error?) -> Void) -> Void {
... }
func get(profile id: Int, respondWith: @escaping (Profile?, Error?) -> Void) -> Void {
... }
func delete(profile id: Int, respondWith: @escaping (Error?) -> Void) -> Void {
... }
Swift Type (Codable)
Swift Type (Identifier)
Swift Type (RequestError)
func store(profile: Profile, respondWith: @escaping (Profile?, Error?) -> Void) -> Void {
... }
func get(profile id: Int, respondWith: @escaping (Profile?, Error?) -> Void) -> Void {
... }
func delete(profile id: Int, respondWith: @escaping (Error?) -> Void) -> Void {
... }
router.post("/profile", handler: store)router.get("/profile", handler: get)router.delete("/profile", handler: delete)
Swift Type (Codable)
Swift Type (Identifier)
Swift Type (RequestError)
Deploy Deploy
Deploy Deploy
Client/Server Contract
KituraKit
guard let backend = KituraKit(baseURL: "http://localhost:8080") else { print("Error creating KituraKit client") return}
guard let backend = KituraKit(baseURL: "http://localhost:8080") else { print("Error creating KituraKit client") return}
backend.post("/profile", data: profile) { (profile: Profile?, error: RequestError?) in ...
}
guard let backend = KituraKit(baseURL: "http://localhost:8080") else { print("Error creating KituraKit client") return}
backend.post("/profile", data: profile) { (profile: Profile?, error: RequestError?) in ...
} Swift Type (Codable)
guard let backend = KituraKit(baseURL: "http://localhost:8080") else { print("Error creating KituraKit client") return}
backend.post("/profile", data: profile) { (profile: Profile?, error: RequestError?) in ...
} Swift Type (Codable)
Swift Type (RequestError)
Kitura 2.0
The Future
Optimized Transports
Optimized Transports Implicit Security
Optimized Transports Implicit Security
User Domains
API Protocols
Optimized Transports Implicit Security
User Domains
public struct ToDoItem : Codable { public var user: String public var title: String public var order: Int public var completed: Bool}
Client/Server Contract
Client Server
public struct ToDoItem : Codable { public var user: String public var title: String public var order: Int public var completed: Bool}
public protocol todoStoreAPI: Store, Get, Update, Index, Delete { typealias In, Out = ToDoItem typealias Id = Int}
Client/Server Contract
Client Server
public struct ToDoItem : Codable { public var user: String public var title: String public var order: Int public var completed: Bool}
public protocol todoStoreAPI: Store, Get, Update, Index, Delete { typealias In, Out = ToDoItem typealias Id = Int}
class ToDoBackend: APIController, ToDoAPI { func store(_ item: ToDoItem, completion: …) { } func get(_ id: Int, completion: …) { } func update(_ id: Int, _ item: ToDoItem completion: …) { } func index(completion: …) { } func delete(_ id: Int, completion: …) { } }
router.registerAPI(api: ToDo())
Client/Server Contract
Client Server
public struct ToDoItem : Codable { public var user: String public var title: String public var order: Int public var completed: Bool}
public protocol todoStoreAPI: Store, Get, Update, Index, Delete { typealias In, Out = ToDoItem typealias Id = Int}
class ToDoBackend: APIController, ToDoAPI { func store(_ item: ToDoItem, completion: …) { } func get(_ id: Int, completion: …) { } func update(_ id: Int, _ item: ToDoItem completion: …) { } func index(completion: …) { } func delete(_ id: Int, completion: …) { } }
router.registerAPI(api: ToDo())
Client/Server Contract
Client Server
User provided implementation
public struct ToDoItem : Codable { public var user: String public var title: String public var order: Int public var completed: Bool}
public protocol todoStoreAPI: Store, Get, Update, Index, Delete { typealias In, Out = ToDoItem typealias Id = Int}
class ToDoBackend: APIController, ToDoAPI {
}
class ToDoBackend: APIController, ToDoAPI { func store(_ item: ToDoItem, completion: …) { } func get(_ id: Int, completion: …) { } func update(_ id: Int, _ item: ToDoItem completion: …) { } func index(completion: …) { } func delete(_ id: Int, completion: …) { } }
router.registerAPI(api: ToDo())
Client/Server Contract
Client Server
User provided implementation
public struct ToDoItem : Codable { public var user: String public var title: String public var order: Int public var completed: Bool}
public protocol todoStoreAPI: Store, Get, Update, Index, Delete { typealias In, Out = ToDoItem typealias Id = Int}
class ToDoBackend: APIController, ToDoAPI {
}
class ToDoBackend: APIController, ToDoAPI { func store(_ item: ToDoItem, completion: …) { } func get(_ id: Int, completion: …) { } func update(_ id: Int, _ item: ToDoItem completion: …) { } func index(completion: …) { } func delete(_ id: Int, completion: …) { } }
router.registerAPI(api: ToDo())
Client/Server Contract
KituraKit provides implementation
Client Server
User provided implementation
public struct ToDoItem : Codable { public var user: String public var title: String public var order: Int public var completed: Bool}
public protocol todoStoreAPI: Store, Get, Update, Index, Delete { typealias In, Out = ToDoItem typealias Id = Int}
class ToDoBackend: APIController, ToDoAPI {
}
let backend = ToDoBackend()
backend.store(data: item) { id?, created?, error? in print(id!)}backend.get(id: id) { todo, err in print(todo!.title)}
class ToDoBackend: APIController, ToDoAPI { func store(_ item: ToDoItem, completion: …) { } func get(_ id: Int, completion: …) { } func update(_ id: Int, _ item: ToDoItem completion: …) { } func index(completion: …) { } func delete(_ id: Int, completion: …) { } }
router.registerAPI(api: ToDo())
Client/Server Contract
KituraKit provides implementation
Client Server
User provided implementation
Swift on the ServerThe Future, available Today