Perfect là gì ?

Perfect là một web-server và toolkit cho phép các nhà phát triển sử dụng ngôn ngữ lập trình Swift để xây dựng những ứng dụng và những REST service. Nó cho phép những developer có thể phát triển ứng dụng cả client-side và server-side trong cùng 1 workspace, cùng 1 ngôn ngữ lập trình Swift. Nó là một bộ khung hoàn hảo cho những kỹ thuật cloud và mobile.



 

Tại sao lại sử dụng Perfect?

- Hiện tại Swift đã được Open Source vì thế nó sẽ còn phát triển nhanh và mạnh, do đó Swift là ngôn ngữ của tương lai.

- Đa số những dân lập trình trên iOS chỉ biết về Objective-C hay Swift hoặc cả 2, vì thế nếu bạn muốn viết cho server-side thì bạn phải học thêm ngôn ngữ PHP hoặc Java để có thể viết cho server-side. Nếu bạn dùng thư viện Perfect thì bạn khỏi cần quan tâm đến việc học thêm ngôn ngữ khác cho công việc đó, bạn có thể dễ dàng debug cả bên client lẫn server đều được.

- Hiện tại Perfect cũng đang trong giai đoạn phát triển và đã có 1 phiên bản release là Perfect version 1.0. Code của thư viện Perfect cũng được open source trên GitHub, nếu các bạn muốn học thêm cách người ta viết library đó như thế nào thì cũng có thể tải về và đọc code.

- Swift và Perfect có thể chạy trên nền tảng Linux vì thế các bạn có thể viết để deploy trên Linux server. Hiện tại trên mạng cũng có nhiều hướng dẫn về việc này.

Làm thế nào thiết lập thư viện Perfect trên XCode?


Demo này mình viết 1 ứng dụng trên iOS truy xuất REST api, có trả về dữ liệu danh sách các text theo dạng GET. Dùng dạng POST để post text lên server và nhận về danh sách các text mới nhất thông qua websocket để thay đổi danh sách hiện tại dưới client:

 

- Bước 1: Bạn mở XCode, chọn New > Workspace để tạo 1 workspace mới, đặt tên workspace ví dụ như PerfectTutorials.

- Bước 2: Bạn tải version 1.0 của Perfect về và giải nén sẽ xuất hiện những thư mục như sau:

Bạn copy 2 thư mục là PerfectLibPerfectServer vào cùng thư mục chứa workspace:

Quay trở lại XCode, bạn nhấn vào biểu tượng dấu cộng ở góc dưới bên trái, và chọn option là Add files to "PerfectTutorials", để thêm 2 project đó vào wordspace:

 


- Bước 3: Tạo 1 project trên iOS với tên là MyServer. Trong project đó bạn tạo 1 target mới với kiểu là 1 Cocoa framework cho OS X với tên là MyServer:


- Bước 4: Bạn phải link PerfectLib vào Framework này, chọn target MyServer, chọn General tab, chỗ Linked Frameworks and Libraries bạn chọn add PerfectLib cho OSX vào.

 

Sau đó bạn chuyển sang Build Settings tab, và thiết lập những thuộc tính sau:

  • Skip Install = No
  • Deployment Location = Yes
  • Installation Directory = /PerfectLibraries
  • Installation Build Products Location = $(CONFIGURATION_BUILD_DIR)


- Bước 5: Tại Group MyServer trên XCode bạn tạo 1 file mới với tên như PerfectHandler.swift trong file đó bạn viết những đoạn code như sau:

 

import Foundation

import PerfectLib

 

let DB_PATH = PerfectServer.staticPerfectServer.homeDir() + serverSQLiteDBs + "MyDB"

 

public func PerfectServerModuleInit() {

    

    // Install the built-in routing handler.

    // This is required by Perfect to initialize everything

    Routing.Handler.registerGlobally()

    

    // register a route for gettings posts

    Routing.Routes["GET", "/posts"] = { _ in

        return GetPostHandler()

    }

    

    // register a route for creating a new post

    Routing.Routes["POST", "/insertPosts"] = { _ in

        return PostHandler()

    }

    

    // Add the endpoint for the WebSocket example system

    Routing.Routes["GET", "/echo"] = {

        _ in

        return WebSocketHandler(handlerProducer: {

            (request: WebRequest, protocols: [String]) -> WebSocketSessionHandler? in

            

            // Check to make sure the client is requesting our "echo" service.

            guard protocols.contains("echo") else {

                return nil

            }

            

            // Return our service handler.

            return EchoHandler()

        })

    }

    

    do {

        let sqlite = try SQLite(DB_PATH)

        try sqlite.execute("CREATE TABLE IF NOT EXISTS posts (id INTEGER AUTO INCREMENT, content STRING, PRIMARY KEY (id))")

        print("database created");

    } catch {

        print("Failure creating database at " + DB_PATH)

    }

}

 

class GetPostHandler: RequestHandler {

    func handleRequest(request: WebRequest, response: WebResponse) {

        do {

            let sqlite = try SQLite(DB_PATH)

            defer {

                sqlite.close()  // defer ensures we close our db connection at the end of this request

            }

            

            var posts:[AnyObject] = []

            

            // query the db for a random post

            try sqlite.forEachRow("SELECT * FROM posts") {

                (statement: SQLiteStmt, i:Int) -> () in

                posts.append(["id":statement.columnText(0),"content":statement.columnText(1)])

            }

            

            let respDict = ["message_code":0,"data":posts]

            let jsonData = try NSJSONSerialization.dataWithJSONObject(respDict, options: NSJSONWritingOptions.PrettyPrinted)

            let jsonStr = String(data: jsonData,encoding: NSASCIIStringEncoding)

            

            response.appendBodyString(jsonStr!)

            response.addHeader("Content-Type", value: "application/json")

            response.setStatus(200, message: "OK")

            

        } catch {

            response.setStatus(400, message: "Bad Request")

        }

        

        response.requestCompletedCallback()

    }

}

 

class PostHandler: RequestHandler {

    func handleRequest(request: WebRequest, response: WebResponse) {

        let reqData = request.postBodyString    // get the request body

        let jsonDecoder = JSONDecoder() // JSON decoder

        do {

            let json = try jsonDecoder.decode(reqData) as! JSONDictionaryType

            let content = json.dictionary["content"] as? String    // again, error check is VERY important here

            

            guard content != nil else {

                // bad request, bail out

                response.setStatus(400, message: "Bad Request")

                response.requestCompletedCallback()

                return

            }

            

            // put the content into our db

            let sqlite = try SQLite(DB_PATH)

            defer {

                sqlite.close()  // defer ensures we close our db connection at the end of this request

            }

            

            try sqlite.execute("INSERT INTO posts (content) VALUES (?)", doBindings: {

                (statement: SQLiteStmt) -> () in

                

                try statement.bind(1, content!) // this binds our input string to the first ? in the query

            })

            

            response.setStatus(200, message: "Created")

        } catch {

            response.setStatus(400, message: "Bad Request")

        }

        

        // this completes the request and sends the response to the client

        response.requestCompletedCallback()

    }

}

 

Lưu ý: Trong Demo này tôi dùng cơ sở dữ liệu SQLite. Perfect hỗ trợ được một số cơ sở dữ liệu như: SQLite, MySQL, PostgreSQL.

 

Bước 6: Tại Group MyServer trên XCode bạn tạo 1 file mới với tên như PerfectSocketHandler.swift để handle các request socket ở client và trả về danh sách các text về cho client hiển thị, trong file đó bạn viết những đoạn code như sau:

 

import Foundation

import PerfectLib

class EchoHandler: WebSocketSessionHandler {

    let socketProtocol: String? = "echo"

    

    // This function is called by the WebSocketHandler once the connection has been established.

    func handleSession(request: WebRequest, socket: WebSocket) {

        

        // Read a message from the client as a String.

        socket.readStringMessage {

            string, op, fin in

            guard let string = string else {

                // This block will be executed if, for example, the browser window is closed.

                socket.close()

                return

            }

            

            

            do {

                let sqlite = try SQLite(DB_PATH)

                defer {

                    sqlite.close()  // defer ensures we close our db connection at the end of this request

                }

                

                var posts:[AnyObject] = []

                

                // query the db for a random post

                try sqlite.forEachRow("SELECT * FROM posts") {

                    (statement: SQLiteStmt, i:Int) -> () in

                    posts.append(["id":statement.columnText(0),"content":statement.columnText(1)])

                }

                

                let respDict = ["message_code":0,"data":posts]

                let jsonData = try NSJSONSerialization.dataWithJSONObject(respDict, options: NSJSONWritingOptions.PrettyPrinted)

                let count = jsonData.length / sizeof(UInt8)

                

                // create an array of Uint8

                var array = [UInt8](count: count, repeatedValue: 0)

                

                // copy bytes into array

                jsonData.getBytes(&array, length:count * sizeof(UInt8))

                

                socket.sendBinaryMessage(array, final: true, completion: { () -> () in

                    // This callback is called once the message has been sent.

                    // Recurse to read and echo new message.

                    self.handleSession(request, socket: socket)

                })

                

            } catch {

            }

        }

    }

}

 

Sau đó bạn chọn Scheme MyServer và chọn build (Command + B). Mỗi khi bạn thay đổi code bạn bắt buộc phải build framework này lại và restart lại server.


Bạn có thể thiết lập Scheme cho PerfectServerHTTPApp để mỗi lần build app này thì sẽ tự động build framework chứa code web-service của mình:



- Bước 7: Sau khi build Framework thành công bạn chọn Scheme là Perfect Server HTTP App và chọn run trên OS X:
- Khi bạn chạy ứng dụng PerfectServerHTTPApp thành công sẽ xuất hiện màn hình như sau:

 

Do mình đã thiết lập Server Address(mặc định là 0.0.0.0) theo ip của máy mình để những máy khác cùng lớp mạng có thể truy suất được và đổi Document Root (mặc định là ./webroot/) theo dúng thư mục webroot trên máy của mình. Ngoài ra các bạn nên để ý log trong console phải xuất ra như sau:


PS: Cách mở port để những máy khác có thể truy suất được máy mình làm local server các bạn vào Settings của hệ thống chọn Sharing và bật File Sharing lên:


Bạn đang chạy server thành công bây giờ là lúc bạn test xem server có phản hồi những request  của bạn hay không thì bạn có thể gọi request bằng terminal, những addon trên browser hay viết ứng dụng để thử request lên server.

 

Link source code Demo để bạn tìm hiểu thêm các xử lý dưới client và test được các API được viết ở trên.


Leave a Comment

Fields with * are required.