46
Taking Swift to the Server

Server Side Swift with Swag

Embed Size (px)

Citation preview

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 IN A 37.120.178.83

Browser

http://jensravens.com

DNS Server

;; ANSWER SECTION: jensravens.com. 3600 IN A 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.83GET / 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> <html lang="en"> ...

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

MiddlewareCookies

Sessions

Current User

Format DetectionCustom 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.

@JensRavensgithub.com/swagproject/swag