Server Side? Swift
-
Upload
takaaki-tanaka -
Category
Technology
-
view
89 -
download
0
Transcript of 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
Perfect• Swift における’Rails’ の立ち位置を目指す • 豊富なDB接続ライブラリ • Mustacheテンプレート • FastCGI + apache2 or Nginx
VAPOR
• Laraval like • シンプルに記述できることを目指す • ドキュメントが丁寧にまとめられている
https://github.com/vapor/vapor
KITURA• IBM製 • IBMのクラウドプラットフォームBluemixがSwiftに対応
• サンドボックスで試すことができる • Swift関連の取り組みも熱心
https://github.com/IBM-Swift
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
$ 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/
ありがとうございました🙇