don't stop believing

Web 서버 기본 구성 2 (PerfectCURL을 이용한 요청) 본문

Swift/Perfect

Web 서버 기본 구성 2 (PerfectCURL을 이용한 요청)

Tongchun 2018. 6. 19. 14:41

이번에는 API 서버와 연결해 데이터를 불러오는 부분을 구현하겠습니다.


먼저 json을 파싱하는 클래스를 하나 만들어 주겠습니다. API 서버와 연결해 데이터를 가져오면 json string을 dictionary로 변경해 주는 클래스와 함수입니다.

nGleServer002 폴더에서 nglelib.swift 파일을 하나 만들어 줍니다. 그리고 아래와 같이 작성합니다.

import Foundation
import SwiftyJSON

public class NgleLib {

    public func jsonStringToDic(_ jsonString: String) -> [String: Any]? {
        guard let dataFromString = jsonString.data(using: .utf8, allowLossyConversion: false) else {
            return nil
        }
        var jsonDic = JSON(data: dataFromString).dictionaryObject!
        let nullValueArray = jsonDic.filter { $0.value is NSNull }
        for (jsonKey, _) in jsonDic {
            if let _ = nullValueArray.index(forKey: jsonKey) {
                jsonDic[jsonKey] = ""
            }
        }
        return jsonDic
    }
}


그 다음 API 서버와 연결해 데이터를 불러오는 부분을 구현할 차례입니다.

request.swift라고 파일을 하나 만들어 주고 아래와 같이 클래스를 만들어 줍니다.

import Foundation import PerfectCURL import SwiftyJSON class NgleRequest{ let ngleLib = NgleLib() var host: String = "" var port: Int = 80 init(host: String, port: Int) { self.host = host self.port = port } // 이 아래부터 함수를 만들 예정입니다. }

NgleRequest 클래스를 초기화 할때 host와 post를 지정합니다.


하나의 method 호출 함수는 동기(Synchronous)와 비동기(Asynchronous) 두 가지 모두 만들어 보겠습니다.

먼저 get method의 Sync 요청입니다. NgleRequest 클래스 안에 추가합니다.

/// get method 동기 처리(Synchronous)
func getSync(_ uri: String, _ params: [String]) -> [String: Any] {

    var jsonDic = [String: Any]()
    var url: String = "http://\(self.host):\(self.port)\(uri)"

    if params.count > 0 {
        url.append("?")
        url.append(params.joined(separator: "&"))
    }

    let request = CURLRequest(url, .failOnError)
    do {
        let response = try request.perform()
        let responseCode = response.responseCode
        print("status code: \(responseCode)")

        jsonDic = response.bodyJSON

    } catch let error as CURLResponse.Error {
        print("Error Status Code: \(error.response.responseCode)")
    } catch {
        print(error.localizedDescription)
    }

    return jsonDic
}

getSync 라고 함수를 만듭니다. 입력 값으로는 uri와 dictionary 형태의 url 파르메터를 받습니다.

출력 값으로는 Dictionary를 리턴합니다.


getSync 함수를 handler.swift에서 호출해 보겠습니다.

Handler 클래스 안에 아래와 같이 추가합니다.

/// get method 동기처리를 이용
func callCrewByGetSync(request: HTTPRequest, response: HTTPResponse) {
    var values = MustacheEvaluationContext.MapType()
    let nGle = NgleRequest(host: "52.79.160.182", port: 10260)

    let jsonDic = nGle.getSync("/v1/crew", [])

    let crewList = jsonDic["data"] ?? ["":""]
    values["crews"] = crewList

    let webPath = request.documentRoot + "/crewlist.mustache"
    let mustachHandler = MustacheHelper(values: values)
    mustacheRequest(request: request, response: response, handler: mustachHandler, templatePath: webPath)
}

NgleRequest() 클래스를 초기화 할 때 host와 port를 할당합니다.

macaron API의 모든 crew 정보를 가져오는 API를 호출하겠습니다. get Method이며 uri는 /v1/crew입니다. url parameter는 없으니 빈 배열로 넣습니다.

getSync 함수를 통해 받아온 Dictionary 데이터 중 key가 data인 것을 Mastache와 맵핑된 values 변수에 넣습니다. 

그리고 crewlist.mustach라고 화면을 출력할 파일을 하나 만들겠습니다.

<html> <header> <title>nGle Crew List</title> <style> table, th, td { border: 1px solid black; border-collapse: collapse; padding: 15px; } </style> </header> <body> <h1>nGle Crew List</h1> <table class=table> <tr> <td>Name</td><td>department</td><td>team</td><td>title</td><td>id</td><td>phone</td> </tr> {{#crews}} <tr> <td>{{name}}</td><td>{{department}}</td><td>{{team}}</td><td>{{title}}</td><td>{{id}}</td><td>{{phone}}</td> </tr> {{/crews}} </table> {{^crews}} <h3>No Crew :(</h3> {{/crews}} </body> </html>


이제 controller.swift의 Route에 uri를 추가하고 호출해 보겠습니다.

Route(method: .get, uri: "/crew/sync", handler: sample.callCrewByGetSync)


이제 swift build 후 http://localhost:8080/crew/sync를 호출해 보겠습니다.

그럼 hander의 callCrewByGetSync 함수가 macaron API의 host와 port로 초기화 되고 getSync 함수로 /v1/crew API를 요청하게 됩니다.

동기로 받아온 데이터를 mustach를 이용해 html 형태로 화면에 출력하게 됩니다.

이제 비동기 get 호출을 만들어 보겠습니다.

request.swift 파일의 NgleRequest 클래스 안에 아래와 같이 추가합니다.

/// get method 비동기 처리(Asynchronous)
func getAsync(_ uri: String, _ params: [String], completion: @escaping (_ jsonData: [String: Any]) -> Void) {

    var url: String = "http://\(self.host):\(self.port)\(uri)"

    if params.count > 0 {
        url.append("?")
        url.append(params.joined(separator: "&"))
    }

    // async 처리를 시작합니다.
    CURLRequest(url).perform { confirmation in
        do {
            let response = try confirmation()
            let jsonDic: [String:Any] = response.bodyJSON
            completion(jsonDic)
        } catch let error as CURLResponse.Error {
            print("Failed: response code \(error.response.responseCode)")
        } catch {
            print("Fatal error \(error)")
        }
    }
}

비동기 처리는 closure를 이용합니다.

clouser에서 함수 밖으로 데이터를 보내기 위해 Escaping clouser를 사용합니다.


Handler 클래스에 getAsync를 사용해 mustach에 뿌려주는 함수를 만들어 보겠습니다.

Handler 클래스 안에 아래와 같이 작성합니다.

/// get method 비동기처리를 이용
func callCrewByGetAsync(request: HTTPRequest, response: HTTPResponse) {
    var values = MustacheEvaluationContext.MapType()
    let nGle = NgleRequest(host: "52.79.160.182", port: 10260)

    nGle.getAsync("/v1/crew", []) { (jsonDic) in
        let crewList = jsonDic["data"] ?? ["":""]
        values["crews"] = crewList

        let mustachHandler = MustacheHelper(values: values)
        let webPath = request.documentRoot + "/crewlist.mustache"
        mustacheRequest(request: request, response: response, handler: mustachHandler, templatePath: webPath)
    }
}

비동기 처리에서는 Closure를 사용해야 하며 Closure안에서 데이터 처리와 mustach 맵핑을 해야 합니다.

macaron API 서버에서 같은 API를 호출하는 함수이니 crewlist.mustach를 그대로 사용합니다.


이제 controller.swift 파일안에 Route를 추가합니다.

Route(method: .get, uri: "/crew/async", handler: sample.callCrewByGetAsync)

이제 http://localhost:8080/crew/async를 호출해 봅니다.

동일한 화면이 나오는지 확인해 봅니다.

이번에는 post Method 호출입니다.

먼저 post Method의 동기 호출을 하겠습니다. request.swift 파일의 NgleRequest 클래스 안에 아래와 같이 작성합니다.

/// post method 동기 처리(Synchronous)
func postSync(_ uri: String, _ params: [String], _ header: [String: String], _ body: String) -> [String: Any] {

    var jsonDic = [String: Any]()
    var url: String = "http://\(self.host):\(self.port)\(uri)"

    if params.count > 0 {
        url.append("?")
        url.append(params.joined(separator: "&"))
    }

    do {
        let start = Date()
        let response = try CURLRequest(url, .httpMethod(.post), .postString(body), .failOnError).perform()
        let responseCode = response.responseCode
        jsonDic = response.bodyJSON

        let duration = Date().timeIntervalSince1970 - start.timeIntervalSince1970
        print("status code: \(responseCode)")
        print("Duratin: \(duration)")

    } catch let error as CURLResponse.Error {
        print("Error Status Code: \(error.response.responseCode)")
    } catch {
        print(error.localizedDescription)
    }

    return jsonDic
}

postSync 함수를 사용해 macaron API 서버의 로그인을 처리해 보겠습니다.

Handler 클래스에 아래와 같이 작성합니다.

/// post method 동기처리를 이용
func callLoginResultByPostSync(request: HTTPRequest, response: HTTPResponse) {
    var values = MustacheEvaluationContext.MapType()
    let nGle = NgleRequest(host: "52.79.160.182", port: 10260)

    let bodyJson = "{ \"id\": \"tongchun\", \"password\": \"ngle3596\"}"
    let jsonDic = nGle.postSync("/v1/auth/login", [], [:], bodyJson)

    let loginData = jsonDic["data"] ?? ["":""]
    print(loginData)
    values["login"] = loginData

    let webPath = request.documentRoot + "/loginresult.mustache"
    let mustachHandler = MustacheHelper(values: values)
    mustacheRequest(request: request, response: response, handler: mustachHandler, templatePath: webPath)
}

로그인 처리 후 결과로 받은 데이터를 화면에 보여줍니다.

loginresult.mustache 파일을 생성하고 아래와 같이 작성합니다.

<html>
    <header>
        <title>nGle Login Result</title>
        <meta charset="UTF-8">
    </header>
    <body>
        <h1>nGle Login Result</h1>

        {{#login}}
            <li>ID: {{id}}</li>
            <li>Access Token: {{accesstoken}}</li>
            <li>마지막 로그인 시간: {{lastaccess}}</li>
        {{/login}}

        {{^login}}
        <h3>No Crew :(</h3>
        {{/login}}
    </body>
</html>

이레 controller.swift안에 Route를 추가합니다.

Route(method: .get, uri: "/login/sync", handler: sample.callLoginResultByPostSync)

http://localhost:8080/login/sync를 호출해 봅니다.

이번에는 post Method의 비동기 처리입니다.

앞서 동기처리하는 함수에서 header 처리를 안하고 넘어왔네요. 비동기 처리할때 header 처리를 추가해 보겠습니다.

request.swift의 NgleRequest 클래스 안에 아래와 같이 함수를 추가합니다.

/// post method 비동기 처리(Asynchronous)
func postAsync(_ uri: String, _ params: [String], _ header: [String: String], _ body: String, completion: @escaping (_ jsonData: [String: Any]) -> Void) {

    var url: String = "http://\(self.host):\(self.port)\(uri)"

    if params.count > 0 {
        url.append("?")
        url.append(params.joined(separator: "&"))
    }

    // accessToken을 보내기 위한 custom Header
    let headerAccessToken = CURLRequest.Header.Name.custom(name: "AccessToken")
    let tokenValue: String = header["AccessToken"] ?? ""

    // async 처리를 시작합니다.
    CURLRequest(url, .httpMethod(.post), .addHeader(headerAccessToken, tokenValue), .postString(body), .failOnError).perform { confirmation in
        do {
            let response = try confirmation()
            let jsonDic: [String: Any] = response.bodyJSON
            completion(jsonDic)

        } catch let error as CURLResponse.Error {
            print("Failed: response code \(error.response.responseCode)")
        } catch {
            print("Fatal error \(error)")
        }
    }
}

macaron API 서버 로그인 후 AccessToken을 받아오며 이후 request 호출할때마다 AccessToken을 header에 AccessToken이라는 key로 항상 넘겨줘야 합니다.

Custom Header를 만들기 위해 CURLRequest.Header.Name.custom(name: "AccessToken")를 추가합니다.

입력 값으로 받아온 Dictionary 타입의 header에서 key가 AccessToken인 값을 tokenValue 변수에 할당합니다.

그리고 CURRequest()를 호출할 때 .addHeader()로 AccessToken header를 추가합니다.


Handler에서 postAsync()를 사용해 로그인해 보겠습니다.

/// post method 비동기처리를 이용
func callLoginResultByPostAsync(request: HTTPRequest, response: HTTPResponse) {
    var values = MustacheEvaluationContext.MapType()
    let nGle = NgleRequest(host: "52.79.160.182", port: 10260)

    let bodyJson = "{ \"id\": \"tongchun\", \"password\": \"ngle3596\"}"
    let accessTokenDic: [String: String] = ["AccessToken": "8F3C1222-C7D9-48B0-9344-8F2FCB59FF1E"]

    nGle.postAsync("/v1/auth/login", [], accessTokenDic, bodyJson) { (jsonDic) in
        let loginData = jsonDic["data"] ?? ["":""]
        print(loginData)
        values["login"] = loginData

        let mustachHandler = MustacheHelper(values: values)
        let webPath = request.documentRoot + "/loginresult.mustache"
        mustacheRequest(request: request, response: response, handler: mustachHandler, templatePath: webPath)
    }
}

이제 Route에 추가하고 호출해 보겠습니다.

Route(method: .get, uri: "/login/async", handler: sample.callLoginResultByPostAsync)



여기까지 CURLRequest을 이용한 get과 post method의 동기/비동기 처리였습니다.


Comments