Taking Swift to the Server
let me = Person(name: "Jens Ravens", company: "nerdgeschoss")
@JensRavens
GitHub: JensRavens
jensravens.com
nerdgeschoss.de
A short introduction to
http, tcp and ip.
Browser
http://jensravens.com
Browser
http://jensravens.com
DNS Server
Browser
http://jensravens.com
DNS Server
;; ANSWER SECTION:
jensravens.com. 3600 INA 37.120.178.83
Browser
http://jensravens.com
DNS Server
;; ANSWER SECTION:
jensravens.com. 3600 INA 37.120.178.83
37.120.178.83
Browser
http://jensravens.com
DNS Server
37.120.178.83
Browser
http://jensravens.com
DNS Server
37.120.178.83
GET / HTTP/1.1
Host: jensravens.com
User-Agent: curl/7.43.0
Accept: */*
Browser
http://jensravens.com
DNS Server
37.120.178.83
Server
GET / HTTP/1.1
Host: jensravens.com
User-Agent: curl/7.43.0
Accept: */*
Browser
http://jensravens.com
DNS Server
37.120.178.83
Server
Load Balancer
GET / HTTP/1.1
Host: jensravens.com
User-Agent: curl/7.43.0
Accept: */*
Browser
http://jensravens.com
DNS Server
37.120.178.83
Server
Load Balancer
App App
GET / HTTP/1.1
Host: jensravens.com
User-Agent: curl/7.43.0
Accept: */*
Browser
DNS Server Server
Load Balancer
App App
HTTP/1.1 200 OK
Content-Type: text/html
Content-Length: 7897
Connection: Keep-Alive
<!DOCTYPE html>
Browser
DNS Server Server
Load Balancer
App App
Hey, that’s just some
plain text over tcp!
Get a Request,
give a Response
public protocol RequestType {
var context: [String:AnyObject] { get set }
var method: HTTPMethod { get }
var path: String { get }
var params: [String:String] { get }
var headers: [String:String] { get }
var format: Format { get }
var body: Streamable? { get }
}
public protocol ResponseType {
var headers: [String: HeaderType] { get }
var code: StatusCode { get }
var content: Streamable { get }
}
public protocol RequestType {
var context: [String:AnyObject] { get set }
var method: HTTPMethod { get }
var path: String { get }
var params: [String:String] { get }
var headers: [String:String] { get }
var format: Format { get }
var body: Streamable? { get }
}
public protocol ResponseType {
var headers: [String: HeaderType] { get }
var code: StatusCode { get }
var content: Streamable { get }
}
public protocol RequestType {
var context: [String:AnyObject] { get set }
var method: HTTPMethod { get }
var path: String { get }
var params: [String:String] { get }
var headers: [String:String] { get }
var format: Format { get }
var body: Streamable? { get }
}
public protocol Streamable {
var stream: Void -> NSData? { get }
}
public typealias AppType = RequestType throws
-> ResponseType
public typealias AppType = RequestType throws
-> ResponseType
let myApp: AppType = { request in
return Response(code: 200, headers: [:],
content: "Hello World")
}
Where to go next:
The Router
public final class Router {
public func route(method: HTTPMethod, path: String,
app: Void -> AppType)
public func get(path: String, app: Void -> AppType)
public func app(request: RequestType) throws
-> ResponseType
}
public final class Router {
public func route(method: HTTPMethod, path: String,
app: Void -> AppType)
public func get(path: String, app: Void -> AppType)
public func app(request: RequestType) throws
-> ResponseType
}
let myApp: AppType = { request in
return Response(code: 200, headers: [:],
content: "Hello World")
}
public final class Router {
public func route(method: HTTPMethod, path: String,
app: Void -> AppType)
public func get(path: String, app: Void -> AppType)
public func app(request: RequestType) throws
-> ResponseType
}
let myApp: AppType = { request in
return Response(code: 200, headers: [:],
content: "Hello World")
}
let router = Router()
router.get("home") { myApp }
let routedApp = router.app
Making things pretty -
Rendering Views
public protocol ViewType {
func render() throws -> Streamable
var contentType: FormatType { get }
}
public protocol ViewType {
func render() throws -> Streamable
var contentType: FormatType { get }
}
public struct JSONView: ViewType {
public var contentType: FormatType { return Format.JSON }
let contents: AnyObject
public init(_ contents: AnyObject) {
self.contents = contents
}
public func render() throws -> Streamable {
return try NSJSONSerialization.dataWithJSONObject(
contents, options: .PrettyPrinted)
}
}
public protocol ControllerType {}
extension ControllerType {
func redirect(path: PathConvertible,
statusCode: StatusCode = 302) -> ResponseType
func render(view view: ViewType,
statusCode: StatusCode = 200) throws -> ResponseType
func render(json json: AnyObject,
statusCode: StatusCode = 200) throws -> ResponseType
func render(html html: String,
statusCode: StatusCode = 200) throws -> ResponseType
}
struct UsersController: ControllerType {
func index(request: RequestType) throws -> ResponseType {
let users: [User] = …
switch request.format {
case .JSON:
return try render(json: users.map { $0.json })
default:
return try render(view: MustacheView(name: "template",
context: users))
}
}
}
struct UsersController: ControllerType {
func index(request: RequestType) throws -> ResponseType {
let users: [User] = …
switch request.format {
case .JSON:
return try render(json: users.map { $0.json })
default:
return try render(view: MustacheView(name: "template",
context: users))
}
}
}
let router = Router()
router.get(“/users") { UsersController().index }
let routedApp = router.app
Extending the Stack -
Add some Middleware
public typealias MiddlewareType = AppType -> AppType
public func + (lhs: MiddlewareType, rhs: AppType) -> AppType {
return lhs(rhs)
}
public typealias MiddlewareType = AppType -> AppType
public func + (lhs: MiddlewareType, rhs: AppType) -> AppType {
return lhs(rhs)
}
let awesomeMiddleware: MiddlewareType = { app in
return { request in
var response = try app(request)
response.content = "Overwritten by middleware"
return response
}
}
public typealias MiddlewareType = AppType -> AppType
public func + (lhs: MiddlewareType, rhs: AppType) -> AppType {
return lhs(rhs)
}
let awesomeMiddleware: MiddlewareType = { app in
return { request in
var response = try app(request)
response.content = "Overwritten by middleware"
return response
}
}
let router = Router()
router.get(“/users")
{ awesomeMiddleware + UsersController().index }
let routedApp = router.app
class CookieStore {
struct Cookie {
public var name: String
public var value: String
public var validUntil: NSDate?
}
var cookies = [String:Cookie]()
subscript(key: String) -> String?
static func middleware(app: AppType) -> AppType
}
class CookieStore {
struct Cookie {
public var name: String
public var value: String
public var validUntil: NSDate?
}
var cookies = [String:Cookie]()
subscript(key: String) -> String?
static func middleware(app: AppType) -> AppType
}
let router = Router()
router.get(“/users")
{ CookieStore().middleware + UsersController().index }
let routedApp = router.app
class CookieStore {
struct Cookie {
public var name: String
public var value: String
public var validUntil: NSDate?
}
var cookies = [String:Cookie]()
subscript(key: String) -> String?
static func middleware(app: AppType) -> AppType
}
let router = Router()
router.get(“/users")
{ CookieStore().middleware + UsersController().index }
let routedApp = router.app
extension RequestType {
public var cookies: CookieStore!
}
func login(request: RequestType) throws -> ResponseType {
let secret = request.params["secret"] ?? ""
request.cookies["secret"] = secret
return try render(text: "Login successfull")
}
func secretStuff(request: RequestType) throws -> ResponseType {
if request.cookies["secret"] == "superSecret" {
return try render(text: "Here's the secret page")
} else {
return try render(text: "Permission denied!",
status: 403)
}
}
func login(request: RequestType) throws -> ResponseType {
let secret = request.params["secret"] ?? ""
request.cookies["secret"] = secret
return try render(text: "Login successfull")
}
func secretStuff(request: RequestType) throws -> ResponseType {
if request.cookies["secret"] == "superSecret" {
return try render(text: "Here's the secret page")
} else {
return try render(text: "Permission denied!",
status: 403)
}
}
let router = Router()
router.post(“/login”)
{ CookieStore().middleware + login }
router.get(“/my-secret-page“)
{ CookieStore().middleware + secretStuff }
let routedApp = router.app
Middleware
Cookies
Sessions
Current User
Format Detection
Custom Error Pages
HTTP Body Parser
Static File Server
Analytics
Mounting your App
Nest Server Interface
https://github.com/nestproject/Nest
import Curassow
import Inquiline
serve { request in
return Response(.Ok, contentType: "text/plain",
body: "Hello World")
}
Epoch
https://github.com/Zewo/Epoch
struct ServerResponder: ResponderType {
func respond(request: Request) -> Response {
// do something based on the Request
return Response(status: .OK)
}
}
let responder = ServerResponder()
let server = Server(port: 8080, responder: responder)
server.start()
Swag
https://github.com/swagproject/swag
import Foundation
import Curassow
import SwagNest
final class NextApp: App {
override init() {
super.init()
register(FileServer(root: publicDir).serve)
register(BodyParser.middleware)
register(CookieStore.middleware)
router.get(“/users") { UsersController.middleware +
UsersController().index }
router.get("/users/:id") { UsersController().show }
}
}
serve(NextApp().nest)
Thank you.
@JensRavens
github.com/swagproject/swag

Server Side Swift with Swag

  • 1.
    Taking Swift tothe Server
  • 2.
    let me =Person(name: "Jens Ravens", company: "nerdgeschoss") @JensRavens GitHub: JensRavens jensravens.com nerdgeschoss.de
  • 3.
    A short introductionto http, tcp and ip.
  • 4.
  • 5.
  • 6.
    Browser http://jensravens.com DNS Server ;; ANSWERSECTION: jensravens.com. 3600 INA 37.120.178.83
  • 7.
    Browser http://jensravens.com DNS Server ;; ANSWERSECTION: jensravens.com. 3600 INA 37.120.178.83 37.120.178.83
  • 8.
  • 9.
    Browser http://jensravens.com DNS Server 37.120.178.83 GET /HTTP/1.1 Host: jensravens.com User-Agent: curl/7.43.0 Accept: */*
  • 10.
    Browser http://jensravens.com DNS Server 37.120.178.83 Server GET /HTTP/1.1 Host: jensravens.com User-Agent: curl/7.43.0 Accept: */*
  • 11.
    Browser http://jensravens.com DNS Server 37.120.178.83 Server Load Balancer GET/ HTTP/1.1 Host: jensravens.com User-Agent: curl/7.43.0 Accept: */*
  • 12.
    Browser http://jensravens.com DNS Server 37.120.178.83 Server Load Balancer AppApp GET / HTTP/1.1 Host: jensravens.com User-Agent: curl/7.43.0 Accept: */*
  • 13.
  • 14.
    HTTP/1.1 200 OK Content-Type:text/html Content-Length: 7897 Connection: Keep-Alive <!DOCTYPE html> Browser DNS Server Server Load Balancer App App
  • 15.
    Hey, that’s justsome plain text over tcp!
  • 16.
  • 17.
    public protocol RequestType{ var context: [String:AnyObject] { get set } var method: HTTPMethod { get } var path: String { get } var params: [String:String] { get } var headers: [String:String] { get } var format: Format { get } var body: Streamable? { get } }
  • 18.
    public protocol ResponseType{ var headers: [String: HeaderType] { get } var code: StatusCode { get } var content: Streamable { get } } public protocol RequestType { var context: [String:AnyObject] { get set } var method: HTTPMethod { get } var path: String { get } var params: [String:String] { get } var headers: [String:String] { get } var format: Format { get } var body: Streamable? { get } }
  • 19.
    public protocol ResponseType{ var headers: [String: HeaderType] { get } var code: StatusCode { get } var content: Streamable { get } } public protocol RequestType { var context: [String:AnyObject] { get set } var method: HTTPMethod { get } var path: String { get } var params: [String:String] { get } var headers: [String:String] { get } var format: Format { get } var body: Streamable? { get } } public protocol Streamable { var stream: Void -> NSData? { get } }
  • 20.
    public typealias AppType= RequestType throws -> ResponseType
  • 21.
    public typealias AppType= RequestType throws -> ResponseType let myApp: AppType = { request in return Response(code: 200, headers: [:], content: "Hello World") }
  • 22.
    Where to gonext: The Router
  • 23.
    public final classRouter { public func route(method: HTTPMethod, path: String, app: Void -> AppType) public func get(path: String, app: Void -> AppType) public func app(request: RequestType) throws -> ResponseType }
  • 24.
    public final classRouter { public func route(method: HTTPMethod, path: String, app: Void -> AppType) public func get(path: String, app: Void -> AppType) public func app(request: RequestType) throws -> ResponseType } let myApp: AppType = { request in return Response(code: 200, headers: [:], content: "Hello World") }
  • 25.
    public final classRouter { public func route(method: HTTPMethod, path: String, app: Void -> AppType) public func get(path: String, app: Void -> AppType) public func app(request: RequestType) throws -> ResponseType } let myApp: AppType = { request in return Response(code: 200, headers: [:], content: "Hello World") } let router = Router() router.get("home") { myApp } let routedApp = router.app
  • 26.
    Making things pretty- Rendering Views
  • 27.
    public protocol ViewType{ func render() throws -> Streamable var contentType: FormatType { get } }
  • 28.
    public protocol ViewType{ func render() throws -> Streamable var contentType: FormatType { get } } public struct JSONView: ViewType { public var contentType: FormatType { return Format.JSON } let contents: AnyObject public init(_ contents: AnyObject) { self.contents = contents } public func render() throws -> Streamable { return try NSJSONSerialization.dataWithJSONObject( contents, options: .PrettyPrinted) } }
  • 29.
    public protocol ControllerType{} extension ControllerType { func redirect(path: PathConvertible, statusCode: StatusCode = 302) -> ResponseType func render(view view: ViewType, statusCode: StatusCode = 200) throws -> ResponseType func render(json json: AnyObject, statusCode: StatusCode = 200) throws -> ResponseType func render(html html: String, statusCode: StatusCode = 200) throws -> ResponseType }
  • 30.
    struct UsersController: ControllerType{ func index(request: RequestType) throws -> ResponseType { let users: [User] = … switch request.format { case .JSON: return try render(json: users.map { $0.json }) default: return try render(view: MustacheView(name: "template", context: users)) } } }
  • 31.
    struct UsersController: ControllerType{ func index(request: RequestType) throws -> ResponseType { let users: [User] = … switch request.format { case .JSON: return try render(json: users.map { $0.json }) default: return try render(view: MustacheView(name: "template", context: users)) } } } let router = Router() router.get(“/users") { UsersController().index } let routedApp = router.app
  • 32.
    Extending the Stack- Add some Middleware
  • 33.
    public typealias MiddlewareType= AppType -> AppType public func + (lhs: MiddlewareType, rhs: AppType) -> AppType { return lhs(rhs) }
  • 34.
    public typealias MiddlewareType= AppType -> AppType public func + (lhs: MiddlewareType, rhs: AppType) -> AppType { return lhs(rhs) } let awesomeMiddleware: MiddlewareType = { app in return { request in var response = try app(request) response.content = "Overwritten by middleware" return response } }
  • 35.
    public typealias MiddlewareType= AppType -> AppType public func + (lhs: MiddlewareType, rhs: AppType) -> AppType { return lhs(rhs) } let awesomeMiddleware: MiddlewareType = { app in return { request in var response = try app(request) response.content = "Overwritten by middleware" return response } } let router = Router() router.get(“/users") { awesomeMiddleware + UsersController().index } let routedApp = router.app
  • 36.
    class CookieStore { structCookie { public var name: String public var value: String public var validUntil: NSDate? } var cookies = [String:Cookie]() subscript(key: String) -> String? static func middleware(app: AppType) -> AppType }
  • 37.
    class CookieStore { structCookie { public var name: String public var value: String public var validUntil: NSDate? } var cookies = [String:Cookie]() subscript(key: String) -> String? static func middleware(app: AppType) -> AppType } let router = Router() router.get(“/users") { CookieStore().middleware + UsersController().index } let routedApp = router.app
  • 38.
    class CookieStore { structCookie { public var name: String public var value: String public var validUntil: NSDate? } var cookies = [String:Cookie]() subscript(key: String) -> String? static func middleware(app: AppType) -> AppType } let router = Router() router.get(“/users") { CookieStore().middleware + UsersController().index } let routedApp = router.app extension RequestType { public var cookies: CookieStore! }
  • 39.
    func login(request: RequestType)throws -> ResponseType { let secret = request.params["secret"] ?? "" request.cookies["secret"] = secret return try render(text: "Login successfull") } func secretStuff(request: RequestType) throws -> ResponseType { if request.cookies["secret"] == "superSecret" { return try render(text: "Here's the secret page") } else { return try render(text: "Permission denied!", status: 403) } }
  • 40.
    func login(request: RequestType)throws -> ResponseType { let secret = request.params["secret"] ?? "" request.cookies["secret"] = secret return try render(text: "Login successfull") } func secretStuff(request: RequestType) throws -> ResponseType { if request.cookies["secret"] == "superSecret" { return try render(text: "Here's the secret page") } else { return try render(text: "Permission denied!", status: 403) } } let router = Router() router.post(“/login”) { CookieStore().middleware + login } router.get(“/my-secret-page“) { CookieStore().middleware + secretStuff } let routedApp = router.app
  • 41.
    Middleware Cookies Sessions Current User Format Detection CustomError Pages HTTP Body Parser Static File Server Analytics
  • 42.
  • 43.
    Nest Server Interface https://github.com/nestproject/Nest importCurassow import Inquiline serve { request in return Response(.Ok, contentType: "text/plain", body: "Hello World") }
  • 44.
    Epoch https://github.com/Zewo/Epoch struct ServerResponder: ResponderType{ func respond(request: Request) -> Response { // do something based on the Request return Response(status: .OK) } } let responder = ServerResponder() let server = Server(port: 8080, responder: responder) server.start()
  • 45.
    Swag https://github.com/swagproject/swag import Foundation import Curassow importSwagNest final class NextApp: App { override init() { super.init() register(FileServer(root: publicDir).serve) register(BodyParser.middleware) register(CookieStore.middleware) router.get(“/users") { UsersController.middleware + UsersController().index } router.get("/users/:id") { UsersController().show } } } serve(NextApp().nest)
  • 46.