$30 off During Our Annual Pro Sale. View Details »

Up to Slimane 0.2 and the Server Side Swift St...

Yuki Takei
April 22, 2016

Up to Slimane 0.2 and the Server Side Swift Standards

2016 April 21 Tokyo Server Side Swift Meetup

Yuki Takei

April 22, 2016
Tweet

More Decks by Yuki Takei

Other Decks in Technology

Transcript

  1. Up to Slimane 0.2 and the Server Side Swift Standards

    2016 April 21 Tokyo Server Side Swift Meetup Yuki Takei
  2. WHO AM I? Yuki Takei (noppoMan) Freelance software engineer/ Radio

    Personality Recent Works ɾLead Developer at iDEAKITT inc. ɾBackend/Data Analysis at SmartDrive inc. Programming Language: Node.js, Python, Ruby and Swift I have a radio program that named MikeTOKYO!!!
  3. WHAT IS SLIMANE? Slimane is… ▸ Express inspired Lightweight Web

    Framework in Swift ▸ Completely asynchronous (libuv) ▸ Faster ▸ Modular ▸ Multiprocess Supports ▸ Message passing between processes ▸ swift3 supports (swift 2.2? no longer working haha) ▸ Adopting Open-Swift
  4. WHAT IS SLIMANE? Base technologies in Slimane SUV ᵎ ɾA

    Swift Wrapper for libuv ɾAsynchronous Stream, FileSystem, Process, Timer, Idle, Cluster/Worker Skelton ᵎ ɾStream based tiny Asynchronous HTTP Server ɾHTTP 1.1 Supports. ɾAdopts S4, C7 https://github.com/noppoMan/Skelton https://github.com/noppoMan/Suv.git
  5. let app = Slimane() app.use { req, res, next print(req.uri.path)

    next(.Next) } app.get { req, res in res.write(“Hello!") } try! app.listen() Slimane 0.1x Slimane 0.2x let app = Slimane() app.use { req, res, next print(req.path) next(.Chain(req, res)) } app.get { req, responder in responder { Response(body: "Hello!") } } try! app.listen()
  6. OPEN-SWIFT ▸ A Standard Interface Libraries for Swift ▸ Open-Swift

    Covers from data/networking layer to web application layer (Nest is just Web Server Gateway Interface) ▸ Started from Zewo guys ▸ No Foundation ▸ Anyway Growing up day by day (Hard to follow…(*´Дʆ)) OPEN SWIFT IS
  7. OPEN SWIFT OFFERS ▸ Data ▸ Stream<Read, Write>, Drain, Close

    and Error ▸ Connection ▸ Time: timespec/mach_timebase wrapper ▸ URI: URI Schema struct ▸ And also has their Asynchronous apis C7 Core standards for Swift
  8. OPEN SWIFT OFFERS ▸ Request/Response ▸ Header, Body, Version, Status

    and Method ▸ Middleware ▸ Responder ▸ Server/Client ▸ And also has their Asynchronous apis S4 HTTP standards for Swift
  9. FRAMEWORKS THAT ARE ADOPTING/CONSIDERING OPEN-SWIFT ▸ Zewo/Zewo (Adopting) ▸ qutheory/vapor

    (Adopting) ▸ necolt/Swifton (Considering) ▸ noppoMan/Slimane (60% Adopting)
  10. Integrate interfaces from bottom layer to application layer and then

    can make reusable modules. So it’s an Ecosystem for Swift.
  11. AsyncResponder is a Protocol for making the Respondable Objects in

    S4. ɾIn S4, All of the routes have to confirm it ɾEven Middlewares
  12. Routing let app = Slimane() // Handy type routing app.get("/articles/:id")

    { req, responder in responder { Response( headers: ["content-type": "text/plain"], body: "Article id is \(req.params["id"]!)" ) } } Slimane has 2 ways for routing 1. Handy Routing
  13. Routing struct FooResponder: AsyncResponder { func respond(request: Request, result: (Void

    throws -> Response) -> Void) { Redis.command(con, command: .PING) { result in if case .Success(let rep) = result { result { Responder(body: rep) } } } } } app.get("/foo", FooResponder()) 2. Routing with S4. AsyncResponder Slimane has 2 ways for routing
  14. AsyncMiddleware is a Protocol for making the Middleware ɾIn S4,

    All of the middlewares have to confirm it ɾ[AsyncMiddleware] List has chain method to run the each middlewares in order.
  15. Middleware Slimane has 3 types registration ways for the middleware

    // Handy is also standard middleware for Slimane app.use { req, res, next in print("[\(Time())] \(req.path ?? "/")") next(.Chain(req, res)) } 1. Handy Middleware
  16. Middleware Slimane has 3 types registration ways for the middleware

    2. MiddlewareType // MiddlewareType is standard type to make middleware for Slimane struct AccessLogMiddleware: MiddlewareType { func respond(req: Request, res: Response, next: MiddlewareChain) { print("[\(Time())] \(req.path ?? "/")") next(.Chain(req, res)) } } app.use(AccessLogMiddleware())
  17. Middleware Slimane has 3 types registration ways for the middleware

    3. S4.AsyncMiddleware (Third party’s AsyncMiddleware) // Confirming S4.AsyncMiddleware directly struct AccessLogMiddleware: AsyncMiddleware { func respond(to request: Request, chainingTo next: AsyncResponder, result: (Void throws -> Response) -> Void) { next.respond(to: request, result: { do { let response = try $0() print("[\(Time())] \(req.path ?? "/")") result { response } } catch { result { throw error } } }) } } app.use(AccessLogMiddleware())
  18. extension Slimane { public struct Static: MiddlewareType { let root:

    String public init(root: String){ self.root = root } public func respond(req: Request, res: Response, next: MiddlewareChain) { guard let path = req.path , ext = path.split(by: “.").last else { return next(.Chain(req, res)) } guard mediaType = mediaTypeForFileExtension(ext) else { return next(.Chain(req, res)) } FS.readFile(root + path) { switch($0) { case .Success(let buffer): var res = res res.contentType = mediaType res.body = .buffer(buffer.data) next(.Chain(req, res)) case .Error(_): next(.Error(Error.ResourceNotFound("\(path) is not found"))) } } } } } Intercept Response at the middleware
  19. extension Slimane { public struct Static: MiddlewareType { let root:

    String public init(root: String){ self.root = root } public func respond(req: Request, res: Response, next: MiddlewareChain) { guard let path = req.path , ext = path.split(by: “.").last else { return next(.Chain(req, res)) } guard mediaType = mediaTypeForFileExtension(ext) else { return next(.Chain(req, res)) } FS.readFile(root + path) { switch($0) { case .Success(let buffer): var res = res res.contentType = mediaType res.body = .buffer(buffer.data) next(.Chain(req, res)) case .Error(_): next(.Error(Error.ResourceNotFound("\(path) is not found"))) } } } } } Intercept Response at the middleware Pass the buffer to the response.body and call chain next, then the response is intercepted and respond soon.
  20. Session import SessionMiddleware import SessionRedisStore let app = Slimane() //

    Initialize Redis store let redisStore = try! RedisStore(host: "127.0.0.1", port: 6379) // SessionConfig let sesConf = SessionConfig( store: redisStore, secret: "my-secret-value", expires: 180, HTTPOnly: true ) // Enable to use session in Slimane app.use(SessionMiddleware(conf: sesConf)) app.get("/") { req, responder // set data into the session req.session![“foo”] = "bar" req.session!.id // show session id responder { Response() } } Register SessionMiddleware into the middleware chains then session is enabled
  21. Slimane.Static Register Slimane.Static into the middleware chains then that enables

    to serve static files let app = Slimane() // Enable to use session in Slimane app.use(Slimane.Static(root: ”/public”))
  22. BodyParser Register BodyParser into the middleware chains to parse body

    data. import BodyParser let app = Slimane() app.use(BodyParser()) // application/x-www-form-urlencoded app.post("/form_data") { req, responder in let foo = formData![“foo”] as! String responder { Response(body: "\(foo) is posted") } } // application/json app.post("/json") { req, responder in let foo = req.json![“foo"] as! String responder { Response(body: "\(foo) is posted") } } * application/x-www-form-urlencoded * application/json
  23. Response with Template Engine Respond to complex text with the

    template engine let app = Slimane() // html render with MustacheViewEngine app.get("/render") { req, responder in responder { let templateData = ["name": "Slimane", "date": "\(Time())"] let mustacheEngine = MustacheViewEngine(templateData: templateData) return Response(custome: Render(engine: mustacheEngine, path: "index")) } } <!DOCTYPE html> <html lang="ja"> <head> <meta charset="utf-8"> </head> <body> <p>Welcome to {{ name }} !</p> <p>Today is {{ date }}</p> </body> </html> index.mustache app.swift
  24. You can make custom view engine with… Renderable Protocol *

    Actually Reactify is not an existence module. just my imagine. import Reactify public struct ReactViewEngine: Renderable { public var fileExtension = "jsx" public let templateData: [String: AnyObject] public init(templateData: [String: AnyObject]){ self.templateData = templateData } // Should implement render method public func render(path: String, result: (Void throws -> Data) -> Void) { let event = Reactify(path: "\(path).\(fileExtension)", templateData: templateData) event.on(.End) { result { return $0 } } event.on(.Error) { result { throw $0 } } } }
  25. Cluster Use Cluster.fork to run Slimane app on the each

    cores. // Check current process is master? if Cluster.isMaster { // Launch the worker processes the number of cores for _ in 0..<OS.cpuCount { try! Cluster.fork(silent: false) } try! Slimane().listen() } else { // on workers let app = Slimane() app.use("/") { req, responder in responder { Response(body: "Hello! I'm \(Process.pid)") } } try! app.listen() }
  26. IPC Between the Master and the Workers Can send/receive messages

    each other let worker = try! Cluster.fork(silent: false) // Send message to the worker worker.send(.Message("message from master")) // Receive event from the worker worker.on { event in if case .Message(let str) = event { print(str) } else if case .Online = event { print("Worker: \(worker.id) is online") } else if case .Exit(let status) = event { print("Worker: \(worker.id) is dead. status: \(status)") } } // Receive event from the master Process.on { event in if case .Message(let str) = event { print(str) } } // Send message to the master Process.send(.Message("Hey!")) On the Master Process On the Worker Process
  27. Let it crash Crash recovery/Let it crash with observing the

    worker processes // Worker observer func observeWorker(worker: inout Worker){ worker.send(.Message("message from master")) worker.on { event in if case .Message(let str) = event { if str == "restart please!!" { worker.process.kill(SIGTERM) // Refork a worker worker = try! Cluster.fork(silent: false) observeWorker(&worker) } } else if case .Exit(let status) = event { print("Worker: \(worker.id) is dead. status: \(status)") // Refork a worker worker = try! Cluster.fork(silent: false) observeWorker(&worker) } } } // Start fork and observe var worker = try! Cluster.fork(silent: false) observeWorker(&worker)
  28. Process.qwork Process.qwork is wrapper method for the uv_queue_work in libuv.

    It enables you to execute the synchronous i/o in the separated pooled thread, and the notification will be emitted to the main loop when the task is finished. var rows: [User] = [] var err: ErrorProtocol? = nil let onThread = { let nameParam: String = "some one" let ids: [QueryParameter] = [1, 2, 3, 4, 5, 6] let optional:Int? = nil let params: (Int, Int?, String, QueryArray) = ( 50, optional, nameParam, QueryArray(ids) ) do { rows: [User] = try conn.query( "SELECT id,name,created_at,age FROM users WHERE (age > ? OR age is ?) OR name = ? OR id IN (?)", build(params) ) } catch { err = error } } let onFinish = { if let error = err { print(error) return } print(rows) } Process.qwork(onThread: onThread, onFinish: onFinish)
  29. Time 0 1.5 3 4.5 6 SLIMANE NGINX EXPRESS REVEL

    VAPOR ɾ Command: ab -n 10000 -c 100 ɾServer: EC2 t2.medium(vCPU: 2, Mem: 4) ɾSwift-Version: swift-DEVELOPMENT-SNAPSHOT-2016-03-24-a-ubuntu14.04 Performance testing I also tried to build Zewo, Swifton and Kitura but that are failed to build…
  30. NEXT STEP ▸ Promise API ▸ Streaming Response ▸ Deal

    with Mysql via asynchronous networking ▸ Command line interface ▸ More Test Codes ▸ Affinity for the Web Front End ▸ Community