Upgrade to Pro — share decks privately, control downloads, hide ads and more …

Up to Slimane 0.2 and the Server Side Swift Standards

Up to Slimane 0.2 and the Server Side Swift Standards

2016 April 21 Tokyo Server Side Swift Meetup

9ebab3d4f66a62a859ad238e7e97597f?s=128

Yuki Takei

April 22, 2016
Tweet

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?

  4. None
  5. None
  6. None
  7. None
  8. 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
  9. 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
  10. Slimane 0.2x has significant changes from 0.1x

  11. 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()
  12. What are the changes?

  13. Adopted Open-Swift

  14. None
  15. 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
  16. 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
  17. 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
  18. FRAMEWORKS THAT ARE ADOPTING/CONSIDERING OPEN-SWIFT ▸ Zewo/Zewo (Adopting) ▸ qutheory/vapor

    (Adopting) ▸ necolt/Swifton (Considering) ▸ noppoMan/Slimane (60% Adopting)
  19. Why Open-Swift?

  20. Integrate interfaces from bottom layer to application layer and then

    can make reusable modules. So it’s an Ecosystem for Swift.
  21. Let’ see the new expressive!

  22. Routing with AsyncResponder

  23. AsyncResponder is a Protocol for making the Respondable Objects in

    S4. ɾIn S4, All of the routes have to confirm it ɾEven Middlewares
  24. 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
  25. 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
  26. Middleware with AsyncMiddleware

  27. 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.
  28. 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
  29. 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())
  30. 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())
  31. 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
  32. 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.
  33. Official Middlewares

  34. 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
  35. 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”))
  36. 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
  37. Response With Template Engine

  38. 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
  39. 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 } } } }
  40. Cluster Mode

  41. 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() }
  42. 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
  43. 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)
  44. DataBase(RDBMS) Supports

  45. Recently, Slimane doesn’t have any apis for accessing RDBMS…

  46. I found an amazing Mysql-Client in Swift

  47. https://github.com/novi/mysql-swift

  48. Ahh, But the network i/o is blocking..

  49. Remember, Slimane built with libuv No Blocking I/O!

  50. Process.qwork

  51. 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)
  52. For now it is the best compromise way to deal

    with Synchronous I/O
  53. Performance

  54. 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…
  55. 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
  56. Thank you!