don't stop believing

Perfect에서 여러 방법으로 데이터 전달하기 (HTTPRequest) 본문

Swift/Perfect

Perfect에서 여러 방법으로 데이터 전달하기 (HTTPRequest)

Tongchun 2018. 6. 9. 17:43

오늘은 토요일입니다. 저넥에는 오리 고기를 먹을 예정입니다.


Perfect를 이용해 데이터를 전달하는 예제를 만들어 보겠습니다.

데이터를 전달하는(Request) 방법은 여러가지이며 이번에 설명할 방법은 아래와 같습니다.

  • Http의 Header로 전달하는 방법
    보통 AccessToken과 같이 Session 정보나 중요 데이터를 전달할 때 Header를 사용합니다.
    아래 예제를 설명할 때는 put method를 사용했습니다.

  • get method를 사용해 uri만을 전달하는 방법
    RESTFull API Design에서는 uri는 자원으로 사용되며 각 method를  통해 자원을 처리하게 됩니다.
    자원(uri)에 대한 데이터를 호출할 때 get method를 사용합니다.

  • get method를 사용하며 uri에 query를 사용해 데이터를 전달하는 방법
    브라우저의 주소창에 uri다음에 key, value처럼 데이터를 보내는 방법입니다.
    예를들어 https://www.youtube.com/watch?v=BN-fZdI5gzI 와 같은 주소가 있을 때 query는 ? 다음부터 이며 key=value 형식입니다.
    v는 key, BN-fZdI5gzI는 value 입니다.

  • get method를 사용해 uri parameter로 데이터를 전달하는 방법
    uri는 directory 경로처럼 / 로 구성되어 있으며 / 와 / 사이에 정의된 데이터를 넣어 전달할 수 있습니다.
    Youtube에서 제공하는 공개 API를 써보셨다면 알수 있듯이, 예를 들어 https://www.youtube.com/user/VOALearningEnglish/videos와 같은 url의 경우 /user/ 까지만 지정된 경로이며 VOALearingEnglish는 계정의 이름, videos는 계정의 vedio flip 리스트 입니다.

  • post method를 사용해 body에 form 형태의 데이터로 전달하는 방법
    html에서 사용하는 form tag와 같이 데이터를 전달하는 벙법입니다.

  • post method를 이용해 body에 string 형태의 데이터로 전달하는 방법
    보통 일반 text와 같은 string 데이터를 전달하지만 json 형태의 데이터 전달해 사용할 수 있습니다.

  • delete method를 사용하는 방법
    사실 put, get, post, delete과 같은 method와 데이터를 전달하는 벙밥은 별개입니다.
    API 서버에서 어떻게 처리하느냐에 따라 get method에서도 body로 데이터는 전달할 수 있습니다.
    보통 RESTFull API 형태의 서버를 개바할 때 주로 사용하는 method가 put, get, post, delete 이렇게 4개 입니다. 그 외에도 더 많이 있습니다.


자 이제 Perfect로 예제를 만들어 보겠습니다.

swift package manager를 이용해 perfect 프로젝트를 만들었다면 이제 아래와 같이 /Sources/<프로젝트 이름>/ 안에 controller.swift와 handler.swift 2개의 파일을 만듭니다.


저는 프로젝트 이름을 nGleServer001로 했습니다.

main.swift에는 Perfect 서버를 설정하고 구동하는 코드입니다.

import PerfectLib
import PerfectHTTP
import PerfectHTTPServer

let server = HTTPServer()
let controller = Controller()

server.serverPort = 8080
server.documentRoot = "Webroot"
server.addRoutes(Routes(controller.routes))

do {
    try server.start()
} catch PerfectError.networkError(let err, let msg) {
    Log.error(message: "Network error thrown: \(err) \(msg)")
}


controller.swift에는 Route를 정의합니다.

import Foundation
import PerfectLib
import PerfectHTTP
import PerfectHTTPServer

final class Controller {

    let sample = SampleHandler()

    var routes: [Route] {
        return [
            Route(method: .put,     uri: "/ngle/crew",  handler: sample.putSample),
            Route(method: .get,     uri: "/ngle/crew",  handler: sample.getSample),
            Route(method: .get,     uri: "/ngle/crew/search",  handler: sample.getSampleWithUriQuery),
            Route(method: .get,     uri: "/ngle/crew/info/{name}",  handler: sample.getSampleWithUriParam),
            Route(method: .post,    uri: "/ngle/crew/param",  handler: sample.postSampleWithBodyParam),
            Route(method: .post,    uri: "/ngle/crew/json",  handler: sample.postSampleWithBodyJson),
            Route(method: .delete,  uri: "/ngle/crew",  handler: sample.delSample)
        ]
    }
}


handler.swift에는 controller.swift에서 호출하는 hander를 작성합니다.

우선 아래와 같이 class와 extansion만 작성해 줍니다.

import Foundation
import PerfectLib
import PerfectHTTP
import PerfectHTTPServer

class SampleHandler {

}

extension Date {
    var ticks: UInt64 {
        return UInt64((self.timeIntervalSince1970 + 62_135_596_800) * 10_000_000)
    }
}

extension은 기본 데이터 타입에 추가 기능을 넣어 사용할 수 있는 기능입니다.

Date()는 날짜를 처리하는 데이터 타입이며 ticks라는 property를 지정하고 timestemp를 리턴하도록 했습니다.


이제 SampleHandler 클래스 안에 공통으로 사용할 함수 하나를 추가하겠습니다.

아래 함수는 Perfect에서 request로 받은 데이터를 처리한 다음 response 하는 함수입니다. 예제에서 사용되는 함수에서 동일하게 사용되기 때문에 별도의 함수로 뺐습니다.

/// response를 통해 데이터를 전송합니다.
func returnMessage(with result: Int, _ data: [String: Any], _ response: HTTPResponse) {
    do {
        try response.setBody(json: ["result": result, "data": data, "ts": Date().ticks])
            .setHeader(.contentType, value: "application/json")
            .completed()
    } catch {
        response.setBody(string: "Error handling request: \(error)")
            .completed(status: .internalServerError)
    }
}


이제 빠르게 가겠습니다.


먼저 header와 body로 데이터를 전달하는 코드입니다. put method를 사용했습니다.

controller에서는 아래와 같이 호출됩니다.

Route(method: .put, uri: "/ngle/crew", handler: sample.putSample)

/// put method를 사용합니다.
/// request data는 header와 body string입니다.
func putSample(request: HTTPRequest, response: HTTPResponse) {
    let headerValue = request.header(.custom(name: "ngleHeader"))
    let bodyString = request.postBodyString

    print("headerValue: \(String(describing: headerValue))")
    print("bodyString: \(String(describing: bodyString))")

    let message = "Custom Header's value is \(String(describing: headerValue)) and body string is \(String(describing: bodyString))"

    let returnData: [String: Any] = ["message": message]
    returnMessage(with: 0, returnData, response)
}

http header에 ngleHeader라는 key로 데이터를 보냅니다. 그리고 body에 string 형태로 보냅니다.


http header에 ngleHeader라는 key로 'Header Sample'이라고 value를 넣었습니다.

그리고 body에는 string 형태로 'body string sample'이라고 보냅니다


예제 프로젝트를 실행하고 데이터를 보내면 json 헝태의 response 값을 받을 수 있습니다.


이번에는 get method이며 아무런 데이터 전달 없이 uri만 호출합니다.

controller에서 아래와같이 호출됩니다.

Route(method: .get, uri: "/ngle/crew", handler: sample.getSample)

/// get method를 사용합니다.
func getSample(request: HTTPRequest, response: HTTPResponse) {

    let returnData: [String: Any] = ["message": "You called by get method."]
    returnMessage(with: 0, returnData, response)
}

get method로 rui만 호출해 봅니다.


get method에 query parameter로 데이트를 보냅니다.

Route(method: .get, uri: "/ngle/crew/search", handler: sample.getSampleWithUriQuery)

/// get method를 사용합니다.
/// uri query를 사용해 데이터를 전달합니다.
/// http://localhost:8080/ngle/crew/search?name=tongchun&age=26
func getSampleWithUriQuery(request: HTTPRequest, response: HTTPResponse) {

    var paramDic = [String: String]()
    for item: (String, String) in request.queryParams {
        paramDic["\(item.0)"] = item.1
    }

    print("\(paramDic)")

    let message = "Name is \(String(describing: paramDic["name"])) and age is \(String(describing: paramDic["age"])))"

    let returnData: [String: Any] = ["message": message]
    returnMessage(with: 0, returnData, response)
}

아래 rul처럼 호출합니다.

http://localhost:8080/ngle/crew/search?name=tongchun&age=26


uri parameter는 Route 정의할때 uri에 {변수명}으로 지정합니다.

Route(method: .get, uri: "/ngle/crew/info/{name}", handler: sample.getSampleWithUriParam)

위 Route에서는 uri에 /ngle/crew/info/ 다음으로 {name} 이라는 변수를 선언했습니다.

그리고 handler 함수에서 request.urlVariable로 지정한 현수를 가져올 수 있습니다.

/// get method를 사용합니다.
/// url variables를 이용해 데이터를 전달합니다.
/// http://localhost:8080/ngle/crew/info/sangji
func getSampleWithUriParam(request: HTTPRequest, response: HTTPResponse) {

    var urlVarDic = [String: String]()
    for item: (String, String) in request.urlVariables {
        urlVarDic["\(item.0)"] = item.1
    }

    print("\(urlVarDic)")

    let message = "Do you need to \(String(describing: urlVarDic["name"]))'s information?"

    let returnData: [String: Any] = ["message": message]
    returnMessage(with: 0, returnData, response)
}

api 호출은 아래 url처럼 합니다.

http://localhost:8080/ngle/crew/info/sangji


이번에는 post method를 사용하며 body에 parameter 형식의 데이터를 전달합니다.

Route(method: .post, uri: "/ngle/crew/param", handler: sample.postSampleWithBodyParam)

/// post method를 사용합니다.
/// body의 데이터 타입은 parameter 형식입니다.
func postSampleWithBodyParam(request: HTTPRequest, response: HTTPResponse) {

    var paramDic = [String: String]()
    for item: (String, String) in request.postParams {
        paramDic["\(item.0)"] = item.1
    }

    print("\(paramDic)")

    let message = "We are learning the \(String(describing: paramDic["language"])) to make an \(String(describing: paramDic["product"]))."

    let returnData: [String: Any] = ["message": message]
    returnMessage(with: 0, returnData, response)
}

위 코드에서는 넘어오는 parameter를 language와 product라고 두개를 정의해 놨습니다.

html tag의 form 형태로 데이터를 보낼때 사용합니다.

https://www.w3schools.com/html/html_forms.asp

post man으로 language: swift, product: API Server라고 보냈습니다.


이제 가장 많이 사용하는 post method로 body에 json 형태의 string을 보냅니다.

Route(method: .post, uri: "/ngle/crew/json", handler: sample.postSampleWithBodyJson)

/// post method를 사용합니다.
/// body의 데이터 타입은 json 입니다.
func postSampleWithBodyJson(request: HTTPRequest, response: HTTPResponse) {

    let bodyString = request.postBodyString

    do {
        let jsonDic = try bodyString?.jsonDecode() as! [String: String]
        let userName = jsonDic["name"]!
        let userAge = jsonDic["age"]!

        let message = "do you look for a person who name is \(userName) and age is \(userAge)?"

        let returnData: [String: Any] = ["message": message]
        returnMessage(with: 0, returnData, response)
    } catch {

        let returnData: [String: Any] = ["message": "Json Error: \(error.localizedDescription)"]
        returnMessage(with: -9999, returnData, response)
    }
}

body에 json 형태로 name과 age를 아래와 같이 보냅니다.

{"name": "sangji", "age": "19"}

참고로 swift에서 json을 다루는 방법은 여러가지가 있습니다. 위 json 데이터에 age의 값으로 string으로된 "19"가 들어갔는데 숫자 19로 들어가게 하려면 몇가지 처리를 해줘야 합니다. 


마지막으로 delete method를 사용해 보겠습니다.

데이터 전달은 동일하게 body에 json 형태의 string을 보내겠습니다.

Route(method: .delete,  uri: "/ngle/crew",  handler: sample.delSample)

/// delete method를 사용합니다.
/// body의 데이터 타입은 json 입니다.
func delSample(request: HTTPRequest, response: HTTPResponse) {

    let bodyString = request.postBodyString

    do {
        let jsonDic = try bodyString?.jsonDecode() as! [String: String]
        let userName = jsonDic["name"] ?? ""
        let userAge = jsonDic["age"] ?? ""

        let message = "would you delete a person who name is \(userName) and age is \(userAge) on the list?"

        let returnData: [String: Any] = ["message": message]
        returnMessage(with: 0, returnData, response)
    } catch {

        let returnData: [String: Any] = ["message": "Json Error: \(error.localizedDescription)"]
        returnMessage(with: -9999, returnData, response)
    }
}

동일하게 json 형태로 아래처럼 보내봅니다.

{"name": "tongchun", "age": "41"}


여기까지 하겠습니다.

다음으로는 Swift를 이용한 데이터 처리를 공부해야 합니다.


- 끝 -


Comments