Server Side? Swift

Post on 08-Jan-2017

89 views 0 download

Transcript of Server Side? Swift

 2016/09/10 H-LANE #hackt_hHACKER TACKLE

Server Side? Swift

About Me

• 田中 孝明 (Takaaki Tanaka)

• クラスメソッド株式会社

• iOS アプリケーションエンジニア

• @kongmingtrap • iOS Developer (Swift / Objective-C)

• GyazSquare / GitHub

Me and Fukuoka

ちょうど1年前まで住んでいました

Summary of Swift

SwiftSwift is a powerful and intuitive

programming language for macOS, iOS, watchOS and tvOS. Writing Swift code is interactive and fun, the syntax is concise

yet expressive, and Swift includes modern features developers love. Swift

code is safe by design, yet also produces software that runs lightning-fast.

History of Swift• 0.x (2014/06)• 1.0 (2014/09)• 1.1 (2014/10)• 1.2 (2015/02)• 2.0 (2015/06)• 2.1 (2015/10)• 2.2 (2016/03)• 3.0 (2016/09)

History of Swift• 0.x (2014/06)• 1.0 (2014/09)• 1.1 (2014/10)• 1.2 (2015/02)• 2.0 (2015/06)• 2.1 (2015/10)• 2.2 (2016/03)• 3.0 (2016/09)

黎明期

History of Swift• 0.x (2014/06)• 1.0 (2014/09)• 1.1 (2014/10)• 1.2 (2015/02)• 2.0 (2015/06)• 2.1 (2015/10)• 2.2 (2016/03)• 3.0 (2016/09)

成長期

History of Swift• 0.x (2014/06)• 1.0 (2014/09)• 1.1 (2014/10)• 1.2 (2015/02)• 2.0 (2015/06)• 2.1 (2015/10)• 2.2 (2016/03)• 3.0 (2016/09)

全盛期

[swift-evolution] Looking back on Swift 3 and ahead to Swift 4

https://lists.swift.org/pipermail/swift-evolution/Week-of-Mon-20160725/025676.html

ABI安定化など、Swift 3.0で実装予定だった機能が見送られ、Swift 4.0まで持ち越される。。。

Swift is Open Source

https://developer.apple.com/swift/blog/?id=34

Going Server-side with Swift Open Source

https://developer.apple.com/videos/play/wwdc2016/415/

WWDC2016

Architecture

https://developer.apple.com/videos/play/wwdc2016/415/

Architecture

https://developer.apple.com/videos/play/wwdc2016/415/

Agenda

• Server Side Swift frameworks• Environment construct• Make• Deploy

Server Side Swift frameworks

https://github.com/PerfectlySoft/Perfect

Perfect

Perfect• Swift における’Rails’ の立ち位置を目指す • 豊富なDB接続ライブラリ • Mustacheテンプレート • FastCGI + apache2 or Nginx

http://qutheory.io/

VAPOR

VAPOR

• Laraval like • シンプルに記述できることを目指す • ドキュメントが丁寧にまとめられている

https://github.com/vapor/vapor

https://developer.ibm.com/swift/kitura/

KITURA

KITURA• IBM製 • IBMのクラウドプラットフォームBluemixがSwiftに対応

• サンドボックスで試すことができる • Swift関連の取り組みも熱心

https://github.com/IBM-Swift

http://www.zewo.io/

Zewo

Zewo

• 豊富なパッケージ数 • BaseComponentsをVaporと入れ替えることができる

https://github.com/Zewo

https://github.com/necolt/Swifton

Swifton

https://github.com/necolt/Swifton

Swifton

• Ruby on Rails like

https://github.com/noppoMan/Slimane

Slimane

https://github.com/noppoMan

Slimane

• expressにインスパイア • マイクロフレームワーク+HTTPサーバー • Yuki Takeiさん作成

Environment construct

今回のデモで採用

8000 star over.

Perfect Template

https://github.com/PerfectlySoft/PerfectTemplate.git

Build

Xcode 8.0 or later

OS X El Captan (10.11.6)

$ swift —version

Apple Swift version 3.0 (swiftlang-800.0.43.6 clang-800.0.38)Target: x86_64-apple-macosx10.9

$ xcode-select --switch /Applications/Xcode.app/Contents/Developer

Build

$ brew install openssl$ brew link openssl --force

Build

$ git clone https://github.com/PerfectlySoft/PerfectTemplate.git

$ cd PerfectTemplate$ swift build

$ .build/debug/PerfectTemplate

// Create HTTP server. let server = HTTPServer()

// Register your own routes and handlers var routes = Routes()

// Add the routes to the server. server.addRoutes(routes)

// Set a listen port of 8181 server.serverPort = 8181

// Set a document root. // This is optional. If you do not want to serve static content then do not set this. // Setting the document root will automatically add a static file handler for the route /** server.documentRoot = "./webroot"

// Gather command line options and further configure the server. // Run the server with --help to see the list of supported arguments. // Command line arguments will supplant any of the values set above. configureServer(server)

do { // Launch the HTTP server. try server.start() } catch PerfectError.networkError(let err, let msg) { print("Network error thrown: \(err) \(msg)") }

Server Start

// Create HTTP server. let server = HTTPServer()

// Register your own routes and handlers var routes = Routes()

// Add the routes to the server. server.addRoutes(routes)

// Set a listen port of 8181 server.serverPort = 8181

// Set a document root. // This is optional. If you do not want to serve static content then do not set this. // Setting the document root will automatically add a static file handler for the route /** server.documentRoot = "./webroot"

// Gather command line options and further configure the server. // Run the server with --help to see the list of supported arguments. // Command line arguments will supplant any of the values set above. configureServer(server)

do { // Launch the HTTP server. try server.start() } catch PerfectError.networkError(let err, let msg) { print("Network error thrown: \(err) \(msg)") }

Server Start

// Create HTTP server. let server = HTTPServer()

// Register your own routes and handlers var routes = Routes()

// Add the routes to the server. server.addRoutes(routes)

// Set a listen port of 8181 server.serverPort = 8181

// Set a document root. // This is optional. If you do not want to serve static content then do not set this. // Setting the document root will automatically add a static file handler for the route /** server.documentRoot = "./webroot"

// Gather command line options and further configure the server. // Run the server with --help to see the list of supported arguments. // Command line arguments will supplant any of the values set above. configureServer(server)

do { // Launch the HTTP server. try server.start() } catch PerfectError.networkError(let err, let msg) { print("Network error thrown: \(err) \(msg)") }

Server Start

// Create HTTP server. let server = HTTPServer()

// Register your own routes and handlers var routes = Routes()

// Add the routes to the server. server.addRoutes(routes)

// Set a listen port of 8181 server.serverPort = 8181

// Set a document root. // This is optional. If you do not want to serve static content then do not set this. // Setting the document root will automatically add a static file handler for the route /** server.documentRoot = "./webroot"

// Gather command line options and further configure the server. // Run the server with --help to see the list of supported arguments. // Command line arguments will supplant any of the values set above. configureServer(server)

do { // Launch the HTTP server. try server.start() } catch PerfectError.networkError(let err, let msg) { print("Network error thrown: \(err) \(msg)") }

Server Start

// Create HTTP server. let server = HTTPServer()

// Register your own routes and handlers var routes = Routes()

// Add the routes to the server. server.addRoutes(routes)

// Set a listen port of 8181 server.serverPort = 8181

// Set a document root. // This is optional. If you do not want to serve static content then do not set this. // Setting the document root will automatically add a static file handler for the route /** server.documentRoot = "./webroot"

// Gather command line options and further configure the server. // Run the server with --help to see the list of supported arguments. // Command line arguments will supplant any of the values set above. configureServer(server)

do { // Launch the HTTP server. try server.start() } catch PerfectError.networkError(let err, let msg) { print("Network error thrown: \(err) \(msg)") }

Server Start

Make

Router// list routes.add(method: .get, uri: "/list", handler: listHandler) // login routes.add(method: .post, uri: "/login", handler: loginHandler) // get message routes.add(method: .get, uri: "/message", handler: getMessageHandler) // post message routes.add(method: .post, uri: "/message", handler: postMessageHandler)

https://github.com/PerfectlySoft/PerfectExample-URLRouting

GET Method// listHandler func listHandler(request: HTTPRequest, _ response: HTTPResponse) { defer { response.completed() } response.setHeader(.contentType, value: "application/json") do { let listArray: [String : Any] = [ "name1": 300, "name2": 230.45, "name3": 150 ] try response.setBody(json: listArray) } catch let error as NSError { print(error) } }

GET Methodcurl -v -H "Accept: application/json" -H "Content-type: application/json" -X GET http://0.0.0.0:8181/list* Trying 0.0.0.0...* Connected to 0.0.0.0 (127.0.0.1) port 8181 (#0)> GET /list HTTP/1.1> Host: 0.0.0.0:8181> User-Agent: curl/7.43.0> Accept: application/json> Content-type: application/json> < HTTP/1.1 200 OK< Content-Type: application/json< Connection: Keep-Alive< Content-Length: 40< * Connection #0 to host 0.0.0.0 left intact{"name1":300,"name2":230.45,"name3":150}

GET Method// check thread let thread = Thread.current print(thread)

[INFO] Starting HTTP server on 0.0.0.0:8181 with document root ./webroot<NSThread: 0x7fab61c15530>{number = 2, name = (null)}<NSThread: 0x7fab61e06cd0>{number = 3, name = (null)}<NSThread: 0x7fab61f05290>{number = 4, name = (null)}

全てのリクエストが別のThreadで実行されていることがわかる

POST Method// loginHandler func loginHandler(request: HTTPRequest, _ response: HTTPResponse) { defer { response.completed() } do { let json = try request.postBodyString?.jsonDecode() response.setHeader(.contentType, value: "application/json") guard let decoded = json as? [String : Any] else { return } let result: [String : Any] = decoded["user"].map { ["result": true, "user": $0] } ?? ["result": false] try response.setBody(json: result) } catch let error as NSError { print(error) } }

POST Methodcurl -v -H "Accept: application/json" -H "Content-type: application/json" -X POST -d '{"user": "tana"}' http://0.0.0.0:8181/login* Trying 0.0.0.0...* Connected to 0.0.0.0 (127.0.0.1) port 8181 (#0)> POST /login HTTP/1.1> Host: 0.0.0.0:8181> User-Agent: curl/7.43.0> Accept: application/json> Content-type: application/json> Content-Length: 16> * upload completely sent off: 16 out of 16 bytes< HTTP/1.1 200 OK< Content-Type: application/json< Connection: Keep-Alive< Content-Length: 29< * Connection #0 to host 0.0.0.0 left intact{"result":true,"user":"tana"}

Database

• Perfect Redis • Perfect SQLite • Perfect PostgreSQL • Perfect MySQL • Perfect MongoDB • Perfect FileMaker

DB Connector

• Perfect Redis • Perfect SQLite • Perfect PostgreSQL • Perfect MySQL • Perfect MongoDB • Perfect FileMaker

DB Connector

https://github.com/PerfectlySoft/Perfect-PostgreSQL

let package = Package( name: "PerfectTemplate", targets: [], dependencies: [ .Package( url: "https://github.com/PerfectlySoft/Perfect-PostgreSQL.git", versions: Version(0,0,0)..<Version(10,0,0)) ] )

Postgresql

プロジェクト直下のpackage.swiftに Perfect-PostgresSQLを追加する

create table message ( id serial primary key, message text, created_at timestamp with time zone, updated_at timestamp with time zone );

• create message table

Create Table

GET Method

// get message routes.add(method: .get, uri: "/message", handler: { request, response in defer { response.completed() } do { response.setHeader(.contentType, value: "application/json") let connection = PGConnection() let status = connection.connectdb(db) let result = connection.exec( statement: "select * FROM message order by updated_at desc") …

// DB let db = "postgresql://samplefuku:fukuoka@localhost:5432/exampledb"

GET Method… let num = result.numTuples() let messages: [[String : Any]] = (0..<num).map { x in let t1 = result.getFieldString(tupleIndex: x, fieldIndex: 0) let t2 = result.getFieldString(tupleIndex: x, fieldIndex: 1) let t3 = result.getFieldString(tupleIndex: x, fieldIndex: 2) let t4 = result.getFieldString(tupleIndex: x, fieldIndex: 3) let message: [String : Any] = [ "id" : t1, "message" : t2, "created_at" : t3, "updated_at" : t4 ] return message } result.clear() connection.close() try response.setBody(json: ["messages" : messages])

} catch let error as NSError { print(error) } })

POST Method// post message routes.add(method: .post, uri: "/message", handler: { request, response in

defer { response.completed() } do { let json = try request.postBodyString?.jsonDecode() response.setHeader(.contentType, value: “application/json")

guard let decoded = json as? [String : Any] else { return } …

POST Method… let result: [String : Any] = decoded["message"].map { message in let connection = PGConnection() let status = connection.connectdb(db)

let date = Date() let createdAt = RFC3339DateFormatter.string(from: date) let updatedAt = RFC3339DateFormatter.string(from: date) let result = connection.exec( statement: "insert into message (message, created_at, updated_at) values($1, $2, $3)", params: ["\(message)", "\(createdAt)", "\(updatedAt)"])

result.clear() connection.close() return ["message" : message] } ?? [:] try response.setBody(json: result) …

Demo

Deploy

http://perfect.org/heroku-buildpack-for-perfect-and-swift.html

Heroku Buildpack for Perfect and Swift

http://perfect.org/aws-buildpack-for-perfect-and-swift.html

AWS Buildpack for Perfect and Swift

AMI

AWSのマネージメントコンソールにログインし、 EC2のAMIから「us-east-1」リージョンにある パブリックイメージから「perfect-ubuntu-1510」を 検索します。

AMI

セキュリティグループのインバウンドにHTTPを 追加しておく

AMI

$ sudo ssh -i ~/.ssh/xxxxx.pem ubuntu@xx.xxx.xx.xxx

AMI

$ wget https://swift.org/builds/development/ubuntu1510/swift-DEVELOPMENT-SNAPSHOT-2016-08-26-a/swift-DEVELOPMENT-SNAPSHOT-2016-08-26-a-ubuntu15.10.tar.gz$ tar xzf swift-DEVELOPMENT-SNAPSHOT-2016-08-26-a-ubuntu15.10.tar.gz

https://swift.org/download/#using-downloads

AMIにインストールされているのがSwift 2.2のため、 ビルドするためにサポートされているSwift 3.0の SNAPSHOTを取得する

$ export PATH=./swift-DEVELOPMENT-SNAPSHOT-2016-08-26-a-ubuntu15.10/usr/bin:"${PATH}"

Build & Run

$ cd PerfectTemplate$ swift build

$ .build/debug/PerfectTemplate

Demo

Appendix

Call Shell// Commandline func command(launchPath: String, arguments: [String]) -> String { let task = Process() task.launchPath = launchPath task.arguments = arguments let pipe = Pipe() task.standardOutput = pipe task.launch() let data = pipe.fileHandleForReading.readDataToEndOfFile() let output = String( data: data, encoding: String.Encoding.utf8)! return output }

let cl = command(launchPath: "/bin/echo", arguments: ["aaaa"])

Recap

• 絶賛発展途上 • クライアントサイドの開発者もWebAPIの開発を経験しやすくなった

• コミッターになりやすい

Recap

Happy Swift life!!

One more thing...

http://dev.classmethod.jp/news/developers-io-2016-in-fukuoka/

http://dev.classmethod.jp/news/job-fair-20161007/

ありがとうございました🙇