Perfect 미니 프로젝트 [6]

1. 게시물 삭제 API


CRUD 중 마지막! Delete에 해당하는 API를 만들어 봅니다!!


참고

2017년 8월 현재 Swift 3 / Perfect 최신버전 2.0.x 환경에서 진행함을 알려드립니다.

* Swift 최신 버전 확인(https://github.com/apple/swift/releases)

* Perfect 최신 버전 확인(https://github.com/PerfectlySoft/Perfect/releases)


지난 내용 돌아보기

2017/06/27 - [Swift/Perfect] - 사진 게시판 API 만들기 [1]

2017/07/11 - [Swift/Perfect] - 사진 게시판 API 만들기 [2]

2017/08/09 - [Swift/Perfect] - 사진 게시판 API 만들기 [3]

2017/08/30 - [Swift/Perfect] - 사진 게시판 API 만들기 [4]

2017/09/06 - [Swift/Perfect] - 사진 게시판 API 만들기 [5]



게시물 삭제 API


지난 번에 올려둔 게시물을 삭제하는 API를 구성해보려고 합니다. 일단 처음 라우팅 해 둔대로 게시물 내용을 수정하는 API의 핸들러는 deleteArticleHandler(request:response:) 함수입니다. 게시물을 삭제하는 API 데이터 모양을 살펴볼까요?

요청(Request)

  • HTTP Method : DELETE
  • Content-Type: application/json

매개변수 없음


응답(Response)

Key 

자료형 

비고 

필수여부 

article_id

string

삭제된 게시물 고유 식별자

Y




HTTP DELETE 메서드로 URI Path를 통해 게시물 ID를 전달하면 게시물을 삭제한 후 게시물 ID를 응답으로 주면 끝입니다! 어려울 것 없어보여요!  

한 번 함께해보시죠 :)

( 이전 API들과 중복 코드가 많지만 포스팅에서 이전 내용을 수정하는 것이 여긴 귀찮은 것이 아니므로... 일단 중복되는 코드가 많아도 꾸역꾸역 구현해 봤습니다. 차후에 여러분 스스로 중복 코드를 줄여보세요 :D )


API 핸들러의 코드를 작성하기 위해 Souces/handlers.swift 파일의 deleteArticleHandler(request:response:) 함수 내부에 코드를 작성해봅니다.


우선 요청정보를 통해 게시물 고유번호를 받아보겠습니다.

    // 응답 컨텐츠 형식은 JSON
    response.setHeader(.contentType, value: ContentsType.json)
    
    // 요청 경로에서 이미지 고유번호를 가져옵니다
    guard let articleId: String = request.pathComponents.last else {
        sendCompleteResponse(response, status: .badRequest, jsonBody: [JSONKey.message:"need article id"])
        return
    }

이제 가져온 게시물 고유번호를 통해 MongoDB에서 문서를 찾아야합니다. find 메서드를 사용하여 쿼리를 보내면 DB는 커서정보를 보내줍니다. 이 커서는 우리가 파일탐색기에서 커서를 옮겨가며 파일을 지정하는 것처럼 DB에 저장되어있는 문서의 위치를 지정하여 가리키는 정보입니다. 위의 코드 아래에 이어서 작성합니다.
    // MongoDB에서 문서를 찾으려면 쿼리를 위한 BSON 객체를 만들 필요가 있습니다.
    // 새로 생성한 BSON 객체에 고유번호를 넣어서 쿼리 객체를 생성합니다
    let query: BSON = BSON()
    let oid: BSON.OID = BSON.OID(articleId)
    query.append(oid: oid)

    // 사용한 BSON 문서는 나중에 닫아줍니다
    defer {
        query.close()
    }
    
    // DB 컬렉션에 쿼리를 전송하여 해당 문서를 가리키는 커서를 가져옵니다
    guard let cursor: MongoCursor = DB.collection?.find(query: query, limit: 1) else {
        sendCompleteResponse(response, status: .badRequest, jsonBody: [JSONKey.message:"wrong article id"])
        return
    }


커서가 가리키는 문서, 즉, 우리가 DB에서 찾은 문서의 정보를 토대로 이미지도 삭제하고 DB에서 문서도 삭제합니다.
    do {
        // 커서가 가리키는 문서를 쭉 돌면서 수행됩니다
        try cursor.enumerated().forEach { (pair: (offset: Int, element: BSON)) in
            
            // 사용한 BSON 문서는 나중에 닫아줍니다
            defer {
                pair.element.close()
            }
            
            // DB 문서를 JSON 문자열로 변환 한 후 디코드하여 딕셔너리 인스턴스로 변경해줍니다
            guard let article: [String : Any] = try pair.element.asString.jsonDecode() as? [String : Any] else {
                sendCompleteResponse(response, status: .internalServerError, jsonBody: [JSONKey.message:"can not find article from DB"])
                return
            }
           
            // mongoDB 컬렉션 가져오기
            guard let collection = DB.collection else {
                sendCompleteResponse(response, status: .internalServerError, jsonBody: [JSONKey.error:"data base initialize failed"])
                return
            }
            
            // 컬렉션에서 게시물 정보에 해당하는 문서를 삭제합니다
            let result: MongoCollection.Result = collection.remove(selector: pair.element)
          
            // 삭제 결과를 토대로 성공/실패여부를 판단합니다.
            switch result {
                
            // 게시물 정보 삭제 성공
            case .success:
                // DB에 저장되어있던 이미지경로
                guard let imagePath = article[JSONKey.imagePath] as? String else {
                    sendCompleteResponse(response, status: .internalServerError, jsonBody: [JSONKey.message:"can not image path from DB"])
                    return
                }
                
                // 이미지경로에 존재하는 파일을 삭제합니다
                File(imagePath).delete()
                
            // 게시물 정보 삭제 실패
            case .error(let errorCode, let errorCode2, let message):
                sendCompleteResponse(response, status: .internalServerError, jsonBody: [JSONKey.error:"\(errorCode), \(errorCode2): \(message)"])
                return
            
            // 그 외 다른 응답
            default:
                sendCompleteResponse(response, status: .internalServerError, jsonBody: [JSONKey.error:"wrong transaction"])
                return
            }
            
            // 모든 작업을 완료하고 JSON 응답
            sendCompleteResponse(response, status: .ok, jsonBody: [JSONKey.articleId:articleId])

        }
    } catch {
        // 도중에 오류가 생겼다면 오류 전달
        sendCompleteResponse(response, status: .internalServerError, jsonBody: [JSONKey.error:error.localizedDescription])
        return
    }

삭제는 사실 간단하면서도 고려해야 할 점이 많습니다. 이미지를 먼제 삭제하고 DB 문서를 삭제할 것인지, 문서를 먼저 삭제하고 이미지를 삭제할 것인지. 등등, 잘 고민해보세요 :)


동작 테스트를 해봅니다.

삭제 후 정상적으로 게시물 ID가 전송된 것을 확인할 수 있습니다!


이제 CRUD가 다 끝났군요! 

다음엔 무엇을 해볼까요? 아직은 계획이 없습니다 :)

필요한 정보가 있다면 언제든 요청주세요!



오늘은 여기까지~!

다음에 또 만나요~~ :D



참고문서





by yagom

facebook : http://www.facebook.com/yagomSoft

facebook group : https://www.facebook.com/groups/yagom

p.s 제 포스팅을 RSS 피드로 받아보실 수 있습니다.

RSS Feed 받기   


↓↓↓ 블로거에게 공감은 큰 힘이 됩니다 ↓↓↓ 




저작자 표시 비영리 변경 금지
신고

'Swift > Perfect' 카테고리의 다른 글

사진 게시판 API 만들기 [6]  (0) 2017.09.13
사진 게시판 API 만들기 [5]  (0) 2017.09.06
사진 게시판 API 만들기 [4]  (0) 2017.08.30
사진 게시판 API 만들기 [3]  (0) 2017.08.09
사진 게시판 API 만들기 [2]  (0) 2017.07.11
사진 게시판 API 만들기 [1]  (6) 2017.06.27
Posted by yagom


Perfect 미니 프로젝트 [5]


1. 게시물 수정 API

CRUD 중 Read에 해당하는 API를 만들어 봅니다!!


참고

2017년 8월 현재 Swift 3 / Perfect 최신버전 2.0.x 환경에서 진행함을 알려드립니다.

* Swift 최신 버전 확인(https://github.com/apple/swift/releases)

* Perfect 최신 버전 확인(https://github.com/PerfectlySoft/Perfect/releases)


지난 내용 돌아보기

2017/06/27 - [Swift/Perfect] - 사진 게시판 API 만들기 [1]

2017/07/11 - [Swift/Perfect] - 사진 게시판 API 만들기 [2]

2017/08/09 - [Swift/Perfect] - 사진 게시판 API 만들기 [3]

2017/08/30 - [Swift/Perfect] - 사진 게시판 API 만들기 [4]

게시물 수정 API

지난 번에 올려둔 게시물의 내용을 수정하는 API를 구성해보려고 합니다. 일단 처음 라우팅 해 둔대로 게시물 내용을 수정하는 API의 핸들러는 modifyArticleHandler(request:response:) 함수입니다. 게시물 내용을 수정하는 API 데이터 모양을 살펴볼까요?

요청(Request)

  • HTTP Method : POST
  • Content-Type: multipart/form-data

 매개변수

자료형 

값의 범위/기본 값 

비고 

 필수여부

image 

binary data

이전 데이터

이미지 데이터 

N

user_name

string 


사용자 이름이 이전 사용자 이름과 일치하지 않으면 수정에 실패

Y

description

string 

이전 데이터

이미지 설명 

N

title

string 

이전 데이터

이미지 제목 

N


응답(Response)

Key 

자료형 

비고 

필수여부 

article_id 

string 

수정된 게시물 고유 식별자 

 image_url

string 

이미지 주소 

user_name 

string 

사용자 이름 

description 

 string

이미지 설명 

title 

string 

이미지 제목 


사진 게시물 등록 API와 크게 다른 모양은 아닙니다만, 필수 매개변수 여부가 다릅니다. 이전 게시물 내용이 이미 등록되어 있으므로, 변경을 원하는 매개변수만 담아서 요청하도록 되어있습니다. 다만, 사용자 이름은 필수항목이군요. 응답 데이터의 모양도 다르지 않습니다.   

로직이 크게 다르지 않을 것 같은데요? 한 번 함께해보시죠 :)


( 사실 게시물 작성 API, 게시물 정보 API와 로직이 비슷한 부분이 많아 공용으로 사용할 함수를 많이 만들 수도 있지만, 포스팅에서 이전 내용을 수정하는 것이 여긴 귀찮은 것이 아니므로... 일단 중복되는 코드가 많아도 해당 로직으로 꾸역꾸역 구현해 봤습니다. 차후에 여러분 스스로 중복 코드를 줄여보세요 :D )


API 핸들러의 코드를 작성하기 위해 Souces/handlers.swift 파일의 modifyArticleHandler(request:response:) 함수 내부에 코드를 작성해봅니다.


일단 부족한 매개변수가 없는지 먼저 확인합니다.

    // 응답 컨텐츠 형식은 JSON
    response.setHeader(.contentType, value: ContentsType.json)
   
    // 부족한 매개변수가 없는지 확인
    if let lakedParams = lakedParams(paramsNeeded: [JSONKey.userName], paramsReceived: request.postParams) {
        sendCompleteResponse(response, status: .badRequest, jsonBody: [JSONKey.message:"need more params \(lakedParams)"])
    }


그 후, 요청정보를 통해 게시물 고유번호를 받아보겠습니다.
    // 요청 경로에서 이미지 고유번호를 가져옵니다
    guard let articleId: String = request.pathComponents.last else {
        sendCompleteResponse(response, status: .badRequest, jsonBody: [JSONKey.message:"need article id"])
        return
    }

이제 가져온 게시물 고유번호를 통해 MongoDB에서 문서를 찾아서 게시물 정보를 가져옵니다. 지난 번에 만들어둔 articleDocumentDicrionaries(query:skip:limit:)함수를 사용합니다. 위의 코드 아래에 이어서 작성합니다.
    // MongoDB에서 문서를 찾으려면 쿼리를 위한 BSON 객체를 만들 필요가 있습니다.
    // 새로 생성한 BSON 객체에 고유번호를 넣어서 쿼리 객체를 생성합니다
    let query: BSON = BSON()
    let oid: BSON.OID = BSON.OID(articleId)
    query.append(oid: oid)
    
    // 게시물 정보를 담은 딕셔너리 배열을 가져온 후 첫 번째 요소를 꺼냅니다
    guard let articleDocumentDictionary = articleDocumentDictionaries(query: query, limit: 1)?.first else {
        sendCompleteResponse(response, status: .internalServerError)
        return
    }

DB에서 정보를 가져왔으니 request를 통해 전달받은 사용자 이름과 DB에 저장되어 있던 사용자 이름이 같은지 확인합니다.
    // 전달받은 사용자 이름과 DB에 작성된 게시물의 사용자 이름과 같은지 확인합니다
    guard let dbUserName = articleDocumentDictionary[JSONKey.userName] as? String,
        let paramUserName = request.param(name:JSONKey.userName) else {
            
            sendCompleteResponse(response, status: .internalServerError, jsonBody: [JSONKey.error:"can not check user name"])
            return
    
    }
    
    guard dbUserName == paramUserName else {
        sendCompleteResponse(response, status: .badRequest, jsonBody: [JSONKey.message:"diffrent user name"])
        return
    }

매개변수로 전달되어온 데이터가 새로이 있다면 DB에 저장할 데이터를 업데이트 해줍니다.
    // DB에 저장할 데이터
    // 차후에 응답 데이터로도 사용합니다
    var jsonDictionary = articleDocumentDictionary
    
    // 매개변수로 사진 제목이 전달되었다면 수정
    if let title = request.param(name: JSONKey.title) {
        jsonDictionary[JSONKey.title] = title
    }
    
    // 매개변수로 사진 설명이 전달되었다면 수정
    if let description = request.param(name: JSONKey.description) {
        jsonDictionary[JSONKey.description] = description
    }

이제 이미지만 남았습니다. 이미지가 업로드 되었다면 새로이 서버에 저장하고, 지난 이미지를 삭제합니다. 업로드 되지 않았다면 저장할 데이터 형태만 살짝 바꿔줍니다.
    // 이미지가 저장될 디렉터리
    guard let imageDirectory = imageDir else {
        sendCompleteResponse(response, status: .internalServerError, jsonBody: nil)
        return
    }
    
    // 원본 이미지 파일 이름 가져오기
    guard let originImageUrl: String = articleDocumentDictionary[JSONKey.imageUrl] as? String, let originImageFileName: String = originImageUrl.filePathComponents.last else {
        
        sendCompleteResponse(response, status: .internalServerError, jsonBody: [JSONKey.error: "can not find origin image"])
        return
    }
    
    // 원본 이미지 서버 내부 경로
    let originImageFilePath: String = imageDirectory.path + originImageFileName
    
    // 새로운 이미지 서버 내부 경로
    let imageFilePath: String!
    
    // 최종 이미지 파일 이름
    let imageFileName: String!
    
    // DB 저장시에는 URL과 게시물 ID가 필요없으므로 미리 삭제
    jsonDictionary.removeValue(forKey: JSONKey.imageUrl)
    jsonDictionary.removeValue(forKey: JSONKey.articleId)
    
    // 이미지 파일이 업로드 되었는지 확인
    if let imageInformation: MimeReader.BodySpec = request.postFileUploads?.first,
        let imageFile = imageInformation.file {
    
        
        // 고유한 이미지 이름을 위해 타임스템프 값을 활용
        let timestamp: Int = icuDateToSeconds(getNow())
        
        // 사용자이름_타임스템프.jpg 형식으로 파일이름 지정
        imageFileName = paramUserName + "_" + String(timestamp) + ".jpg"
        
        // 이미지가 저장될 경로
        imageFilePath = imageDirectory.path + imageFileName
        
        // 이미지 저장에 실패할 경우 실패 응답 보내기
        do {
            try imageFile.copyTo(path: imageFilePath, overWrite: false)
        } catch {
            sendCompleteResponse(response, status: .internalServerError, jsonBody: [JSONKey.error:error.localizedDescription])
            return
        }
        
        // 기존 파일 삭제해주기
        File(originImageFilePath).delete()
        
        jsonDictionary[JSONKey.imagePath] = imageFilePath
        
    } else {    // 이미지 파일을 변경하지 않는 경우
       
        // 이미지 서버 내부 경로
        imageFilePath = imageDirectory.path + originImageFileName
        
        imageFileName = originImageFileName
    }

이제 마지막으로 변경된 딕셔너리를 DB에 업데이트 해주기만 하면 됩니다. 새로운 게시물 작성 API에서 사용하였던 컬렉션의 save(document:) 메서드는 OID가 같다면 업데이트를 수행합니다.
    // mongoDB 컬렉션 가져오기
    guard let collection = DB.collection else {
        sendCompleteResponse(response, status: .internalServerError, jsonBody: [JSONKey.error:"data base initialize failed"])
        return
    }
    
    // DB에 저장
    do {
        let jsonString = try jsonDictionary.jsonEncodedString()
        
        let document: BSON = try BSON(json: jsonString)
        
        // 문서 사용 후에는 닫아주는게 좋겠습니다
        defer {
            document.close()
        }
        
        // 문서 고유 아이디 부여
        document.append(oid: oid)
        
        // 컬렉션에 문서 저장 - save 메서드는 업데이트 겸용입니다
        let result = collection.save(document: document)
        
        if case .success = result { } else {
            sendCompleteResponse(response, status: .internalServerError, jsonBody: [JSONKey.error:result])
            return
        }
        
    } catch {
        
        Log.error(message: error.localizedDescription)
        sendCompleteResponse(response, status: .internalServerError, jsonBody: [JSONKey.error:error.localizedDescription])
        return
    }

이제 DB에 저장도 마쳤으니 응답 데이터를 전송하는 일만 남았습니다. 필요없는 정보 대신에 클라이언트에서 필요한 정보로 대체해 준 후 전달합니다.
    // 응답 데이터에는 이미지 경로 대신 이미지 URL 전송
    jsonDictionary[JSONKey.imagePath] = nil
    jsonDictionary[JSONKey.imageUrl] = server.serverAddress + ":\(server.serverPort)" + "/image/" + imageFileName
    
    // 모든 작업을 완료하고 JSON 응답
    sendCompleteResponse(response, status: .created, jsonBody: jsonDictionary)


기존 게시물 정보입니다.


수정 후 게시물 정보입니다. 게시물 ID는 동일하지만 이미지 URL도 달라져 있고, 제목과 내용도 변경되었네요!



Update까지 해봤군요! 다음엔 Delete로 마무리 해봅니다!

오늘은 여기까지~!

다음에 또 만나요~~ :D




참고문서





by yagom

facebook : http://www.facebook.com/yagomSoft

facebook group : https://www.facebook.com/groups/yagom

p.s 제 포스팅을 RSS 피드로 받아보실 수 있습니다.

RSS Feed 받기   


↓↓↓ 블로거에게 공감은 큰 힘이 됩니다 ↓↓↓ 




저작자 표시 비영리 변경 금지
신고

'Swift > Perfect' 카테고리의 다른 글

사진 게시판 API 만들기 [6]  (0) 2017.09.13
사진 게시판 API 만들기 [5]  (0) 2017.09.06
사진 게시판 API 만들기 [4]  (0) 2017.08.30
사진 게시판 API 만들기 [3]  (0) 2017.08.09
사진 게시판 API 만들기 [2]  (0) 2017.07.11
사진 게시판 API 만들기 [1]  (6) 2017.06.27
Posted by yagom

Perfect 미니 프로젝트 [4]


1. 게시물 정보 API 
2. 게시물 목록 API 


CRUD 중 Read에 해당하는 API를 만들어 봅니다!!


참고

2017년 8월 현재 Swift 3 / Perfect 최신버전 2.0.x 환경에서 진행함을 알려드립니다.

* Swift 최신 버전 확인(https://github.com/apple/swift/releases)

* Perfect 최신 버전 확인(https://github.com/PerfectlySoft/Perfect/releases)


지난 내용 돌아보기

2017/06/27 - [Swift/Perfect] - 사진 게시판 API 만들기 [1]

2017/07/11 - [Swift/Perfect] - 사진 게시판 API 만들기 [2]

2017/08/09 - [Swift/Perfect] - 사진 게시판 API 만들기 [3]


게시물 정보 API

지난 번에 POST로 등록해본 게시물의 정보를 가져오는 API를 구성해보려고 합니다. 일단 프로젝트를 처음 생성하면서 라우팅 해 둔대로 게시물 정보를 가져오는 API의 핸들러는 articleInfoHandler(request:response:) 함수입니다. 게시물 정보 API 데이터 모양을 살펴볼까요?


요청(Request)

  • HTTP Method : GET
  • Content-Type: application/json

매개변수 없음


응답(Response)

매개변수 

자료형 

비고 

필수여부 

image_url

string

이미지 URL

Y

user_name

string

업로드한 사용자 이름 

description

string 

이미지 설명 

title

string 

이미지 제목 

article_id

string 

게시물 고유 식별자



게시물 정보를 받기 위해서 GET 메서드로 요청하고, PATH의 마지막에 게시물 고유번호를 보내게 되어있습니다. 그럼 우리도 받을 때 게시물 고유번호를 받고, DB에서 고유번호를 통해 게시물을 찾아 보내주면 됩니다~!


일단 요청정보를 통해 게시물 고유번호를 받아보겠습니다.

Souces/handlers.swift 파일의 articleInfoHandler(request:response:) 함수 내부에 코드를 작성해봅니다.

    response.setHeader(.contentType, value: ContentsType.json)
    
    // 요청 경로에서 이미지 고유번호를 가져옵니다
    guard let articleId: String = request.pathComponents.last else {
        sendCompleteResponse(response, status: .badRequest, jsonBody: [JSONKey.message:"need article id"])
        return
    }

이제 가져온 게시물 고유번호를 통해 MongoDB에서 문서를 찾아야합니다. find 메서드를 사용하여 쿼리를 보내면 DB는 커서정보를 보내줍니다. 이 커서는 우리가 파일탐색기에서 커서를 옮겨가며 파일을 지정하는 것처럼 DB에 저장되어있는 문서의 위치를 지정하여 가리키는 정보입니다. 위의 코드 아래에 이어서 작성합니다.

    // MongoDB에서 문서를 찾으려면 쿼리를 위한 BSON 객체를 만들 필요가 있습니다.
    // 새로 생성한 BSON 객체에 고유번호를 넣어서 쿼리 객체를 생성합니다
    let query: BSON = BSON()
    let oid: BSON.OID = BSON.OID(articleId)
    query.append(oid: oid)
    
    // 사용한 BSON 문서는 닫아줍니다
    defer {
        query.close()
    }
        
    // DB 컬렉션에 쿼리를 전송하여 해당 문서를 가리키는 커서를 가져옵니다
    guard let cursor: MongoCursor = DB.collection?.find(query: query, limit: 1) else {
        sendCompleteResponse(response, status: .internalServerError)
        return
    }


커서정보를 받아왔으면 이제 문서를 JSON 형태로 변환하여 클라이언트에게 전송해주면 됩니다. 자세한 설명은 주석으로 달려있으니, 위 코드 아래에 이어서 작성해봅니다.

    do {
        // 커서가 가리키는 객체들을 모두 JSON 문자열로 변경한 후,
        // JSON 문자열을 딕셔너리 배열로 변환해 봅니다
        guard let articleDocumentDictionaries = try cursor.jsonString.jsonDecode() as? [[String : Any]] else {
            sendCompleteResponse(response, status: .internalServerError)
            return
        }
        
        // 우리는 하나의 게시물 정보만 필요하므로 배열에서 첫 번째 요소를 꺼내옵니다.
        // 만약 배열이 비어있다면 게시물을 찾지 못했을 확률이 큽니다
        guard let article = articleDocumentDictionaries.first else {
            sendCompleteResponse(response, status: .notFound)
            return
        }
        
        // 꺼내온 게시물 정보를 응답해줍니다
        sendCompleteResponse(response, status: .ok, jsonBody: article)
        
    } catch {
        sendCompleteResponse(response, status: .internalServerError, jsonBody: [JSONKey.message : error.localizedDescription])
        return
    }



짠! 벌써 끝났어요!! 우하하 한 번 테스트해봅니다.

(주소의 마지막 경로에 위치한 게시물 고유번호는 여러분의 DB에서 부여한 번호에 따라 다릅니다)

헛... 그런데 내려온 JSON 형식을 보아하니, 문서에 정리되어 있는 것처럼 깔끔하지가 않군요. DB에서 관리하고 있는 _id 객체라던지... null 상태로 내려온 description이라던지... 마음에 들지 않는 부분이 너무 많습니다. 게다가 이미지의 URL이 아닌 서버 내부의 경로가 내려오고 있죠. 이것들을 다 바꿔주고 싶습니다.

그리고 생각해보니 우리는 나중에 게시물 목록을 내려주는 API도 구성해야 합니다. 아마 중복작업들이 일어날 것 같으니 그냥 미리 빼서 함수로 만들어 놓는 것이 좋을 것 같아요. 이런 사전처리들을 위해서 Souces/handlers.swift 파일의 맨 하단에 함수를 하나 추가해봅니다.

// 쿼리를 통해 데이터베이스에서 문서 정보를 딕셔너리 형태로 변환하여 반환합니다.
// 딕셔너리에 불필요한 정보를 제거하고, 필요한 정보를 형식에 맞게 다시 넣어줍니다
private func articleDocumentDictionaries(query: BSON = BSON(), skip: Int = 0, limit: Int = 0) -> [[String: Any]]? {
    
    // 사용한 BSON 문서는 나중에 닫아줍니다
    defer {
        query.close()
    }
    
    // DB 컬렉션에 쿼리를 전송하여 해당 문서를 가리키는 커서를 가져옵니다
    guard let cursor: MongoCursor = DB.collection?.find(query: query, skip: skip, limit: limit) else {
        return nil
    }
    
    // 반환할 딕셔너리 배열 생성
    var documentDictionaries: [[String : Any]] = []
    
    do {
        // 커서가 가리키는 문서를 쭉 돌면서 수행됩니다
        try cursor.enumerated().forEach { (pair: (offset: Int, element: BSON)) in
            
            // 사용한 BSON 문서는 나중에 닫아줍니다
            defer {
                pair.element.close()
            }
            
            var iterator = pair.element.iterator()
            
            // 문서 내부에서 고유번호를 가져오기 위해 key에 해당하는 값을 가져옵니다
            guard iterator?.find(key: "_id") == true,
                let oid = iterator?.currentValue?.oid?.description else {
                return
            }
            
            // DB 문서를 JSON 문자열로 변환 한 후 디코드하여 딕셔너리 인스턴스로 변경해줍니다
            guard var article: [String : Any] = try pair.element.asString.jsonDecode() as? [String : Any] else {
                return
            }
            
            // 이미지 파일 이름을 가져온 후
            guard let imageFileName: String = (article[JSONKey.imagePath] as? String)?.lastFilePathComponent else {
                return
            }
            
            // 응답 데이터에는 이미지 경로 대신 이미지 URL을 넣어줍니다
            article.removeValue(forKey: JSONKey.imagePath)
            article[JSONKey.imageUrl] = server.serverAddress + ":\(server.serverPort)" + "/image/" + imageFileName
            
            // _id 키대신 우리가 원하는 형식으로 게시물 고유번호를 넣어줍니다
            article.removeValue(forKey: "_id")
            article[JSONKey.articleId] = oid
            
            // 딕셔너리 내부에 Null 값이 있다면 해당 키를 삭제해줍니다
            article.forEach({ (pair: (key: String, value: Any)) in
                if type(of: pair.value) == JSONConvertibleNull.self {
                    article.removeValue(forKey: pair.key)
                }
            })
            
            // 반환할 배열에 넣어줍니다
            documentDictionaries.append(article)
        }
        
        // 배열 반환
        return documentDictionaries
    } catch {
        return nil
    }
}

그리고 다시 사진 게시물 정보 핸들러 함수를 깔끔하게 정리해봅니다.

// 사진 게시물 정보
func articleInfoHandler(request: HTTPRequest, response: HTTPResponse) {
    response.setHeader(.contentType, value: ContentsType.json)
    
    // 요청 경로에서 이미지 고유번호를 가져옵니다
    guard let articleId: String = request.pathComponents.last else {
        sendCompleteResponse(response, status: .badRequest, jsonBody: [JSONKey.message:"need article id"])
        return
    }
    
    // MongoDB에서 문서를 찾으려면 쿼리를 위한 BSON 객체를 만들 필요가 있습니다.
    // 새로 생성한 BSON 객체에 고유번호를 넣어서 쿼리 객체를 생성합니다
    let query: BSON = BSON()
    let oid: BSON.OID = BSON.OID(articleId)
    query.append(oid: oid)
        
    // 게시물 정보를 담은 딕셔너리 배열을 가져온 후 첫 번째 요소를 꺼냅니다
    guard let articleDocumentDictionary = articleDocumentDictionaries(query: query, limit: 1)?.first else {
        sendCompleteResponse(response, status: .internalServerError)
        return
    }
    
    // 모든 작업을 완료하고 JSON 응답
    sendCompleteResponse(response, status: .ok, jsonBody: articleDocumentDictionary)
}

그리고 동작확인!

크아~ 우리가 원하던 대로 동작합니다!!



게시물 목록 API


게시물 목록을 가져오는 것은 이제 일도 아니군요! 미리 함수를 만들어 뒀으니 전체 게시물 목록도 손쉽게 가져올 수 있습니다. 물론 우리가 원하는 깔끔한 형태로요~

DB 컬렉션의 find 메서드의 매개변수로 전달하는 query 객체가 아무 정보도 가지고 있지 않다면 컬렉션에 존재하는 모든 문서를 가져올 수 있습니다. skip은 찾은 문서 중 지나갈 문서의 수, limit은 커서가 가리키게 될 문서의 최대 개수입니다. 이것을 설명하는 이유는 게시물 목록 가져올 때 전송할 매개변수 값들과 관계가 있기 때문이겠죠? 헤헤


게시물 목록을 가져오기 위한 API 정보입니다.


사진 게시물 목록

요청(Request)

  • HTTP Method : GET
  • Content-Type: application/json

 매개변수

자료형 

값의 범위/기본 값 

비고 

 필수여부

page

integer

0

조회하고자 하는 페이지 번호

user_name

string 


특정 사용자의 게시물만 받아오고자 할 때

N

articles_per_page

integer

1~100 / 10 

각 페이지 당 게시물 수 

N


응답(Response)

Key 

자료형 

비고 

필수여부 

articles 

json object array (string) 

게시물 정보 배열 

Y

articles_per_page

integer

각 페이지 당 게시물 수

 Y

current_page

integer 

전송된 페이지 

total_page

integer 

전체 페이지 수 

Y

articles item object 형태

Key 

자료형 

비고 

필수여부 

image_url

string

이미지 URL

Y

user_name

string

업로드한 사용자 이름 

description

string 

이미지 설명 

title

string 

이미지 제목 

article_id

string 

게시물 고유 식별자



클라이언트에서 요청할 때 부가정보 전달을 위해서 page, user_name, articles_per_page 매개변수를 사용하는군요. 일단 이 매개변수를 가져오는 코드를 작성해봅니다.


사진 게시물 목록을 핸들링하는 articleListHandler(request:, response:) 함수에 기존의 내용을 지워주고 새로운 코드를 작성합니다.

    response.setHeader(.contentType, value: ContentsType.json)
    
    // 요청된 페이지 번호
    let page: Int = Int(request.param(name: JSONKey.page) ?? "0") ?? 0
    
    // 잘못된 범위라면 응답처리
    if page < 0 {
        sendCompleteResponse(response, status: .badRequest, jsonBody: [JSONKey.message : "wrong page number"])
        return
    }
    
    // 한 페이지에 담길 게시물 개수
    let articlesPerPage: Int = Int(request.param(name: JSONKey.articlesPerPage) ?? "10") ?? 10
    
    // 잘못된 범위라면 응답처리
    if articlesPerPage < 1 || articlesPerPage > 100 {
        sendCompleteResponse(response, status: .badRequest, jsonBody: [JSONKey.message : "wrong paging size"])
        return
    }
    
    // 특정 사용자의 게시물만 검색하고자 할 때 요청하는 사용자 이름
    let userName: String? = request.param(name: JSONKey.userName)

자 이렇게 요청 정보에서 필요한 정보들을 가져왔습니다. 이제 이 정보들을 기반으로 검색할 쿼리를 만들어줍니다. 위 코드 바로 아래에 이어서 코드를 작성합니다.

    // 쿼리에 사용할 BSON 객체
    let query: BSON = BSON()
    
    // 만약 사용자 이름에 대해 검색하고 한다면 BSON 객체에 값 추가
    if let name = userName {
        query.append(key: JSONKey.userName, string: name)
    }

쿼리는 역시나 간단합니다. 그러면 쿼리를 통해 검색된 문서의 총 개수를 알아봅니다. 검색결과는 MongoResult라는 열거형 타입으로 반환됩니다. 그리고 그 연관 값으로 결과 값을 알아낼 수 있지요.

    // 쿼리를 통해 조건에 해당하는 문서가 총 몇개인지 확인
    guard let countResult = DB.collection?.count(query: query) else {
        sendCompleteResponse(response, status: .internalServerError)
        return
    }
    
    // 총 문서의 개수를 저장할 상수
    let totalCount: Int
    
    // count 결과에 따른 처리
    if case .replyInt(let count) = countResult {
        totalCount = count
    } else if case .error(_, _, let message) = countResult {
        sendCompleteResponse(response, status: .internalServerError, jsonBody: [JSONKey.message : message])
        return
    } else {
        sendCompleteResponse(response, status: .internalServerError)
        return
    }


마지막으로 쿼리를 통해 검색한 문서들을 우리가 원하는 범위에서 뽑아내어 딕셔너리 형태로 반환받습니다.

    // 반환할 데이터 딕셔너리
    var responseData : [String : Any] = [:]
    responseData[JSONKey.articles] = articleDictionaries
    responseData[JSONKey.articlesPerPage] = articlesPerPage
    responseData[JSONKey.currentPage] = page
    responseData[JSONKey.totalPage] = totalCount / articlesPerPage
    
    // 모든 작업을 완료하고 JSON 응답
    sendCompleteResponse(response, status: .ok, jsonBody: responseData)

빠밤! 참 쉽죠? 헤헿 






Read까지 해봤군요! 다음엔 Update를 할까요 Delete를 할까요?

오늘은 여기까지~!


다음에 또 만나요~~ :D



참고문서




by yagom

facebook : http://www.facebook.com/yagomSoft

facebook group : https://www.facebook.com/groups/yagom

p.s 제 포스팅을 RSS 피드로 받아보실 수 있습니다.

RSS Feed 받기   


↓↓↓ 블로거에게 공감은 큰 힘이 됩니다 ↓↓↓ 




저작자 표시 비영리 변경 금지
신고

'Swift > Perfect' 카테고리의 다른 글

사진 게시판 API 만들기 [6]  (0) 2017.09.13
사진 게시판 API 만들기 [5]  (0) 2017.09.06
사진 게시판 API 만들기 [4]  (0) 2017.08.30
사진 게시판 API 만들기 [3]  (0) 2017.08.09
사진 게시판 API 만들기 [2]  (0) 2017.07.11
사진 게시판 API 만들기 [1]  (6) 2017.06.27
Posted by yagom


Perfect 미니 프로젝트 [3]


1. 사진 게시물 등록하기 
2. URL을 통해 사진 GET 


API를 하나씩 하나씩 완성해 나가도록 합시다!!

참고

2017년 7월 현재 Swift 3 / Perfect 최신버전 2.0.x 환경에서 진행함을 알려드립니다.

* Swift 최신 버전 확인(https://github.com/apple/swift/releases)

* Perfect 최신 버전 확인(https://github.com/PerfectlySoft/Perfect/releases)


지난 내용 돌아보기

2017/06/27 - [Swift/Perfect] - 사진 게시판 API 만들기 [1]

2017/07/11 - [Swift/Perfect] - 사진 게시판 API 만들기 [2]



사진 게시물 등록하기

먼저 CRUD 중, Create를 먼저 해보도록 할게요. 사진 게시물 등록을 위한 API를 구현해 봅니다. 사진 게시물 등록은 /article 경로에 POST 메서드를 통해 multipart/form-data를 전송합니다.

지난 번 정의한 API 스펙 중 사진 게시물 등록 API의 정보입니다.

요청(Request)

  • HTTP Method : POST
  • Content-Type: multipart/form-data

 매개변수

자료형 

값의 범위/기본 값 

비고 

 필수여부

image 

binary data

 

이미지 데이터 

user_name

string 

 

사용자 이름 

Y

description

string 

 

이미지 설명 

N

title

string 

 

이미지 제목 

Y


응답(Response)

Key 

자료형 

비고 

필수여부 

article_id 

string 

업로드된 게시물 고유 식별자 

 image_url

string 

이미지 주소 

user_name 

string 

사용자 이름 

description 

 string

이미지 설명 

title 

string 

이미지 제목 


우선 Sources/handlers.swift 파일로 이동하여 반복적으로 수행할 것 같은 기능이나, 상수를 정의해보려 합니다.

먼저 필수 매개변수가 모두 실려왔는지 확인하는 함수를 만듭니다. handlers.swift 파일 맨 아래쪽에 함수를 만들어봅니다.


// 부족한 매개변수가 없는지 확인하여 부족한 매개변수가 있다면 String Array로 반환
private func lakedParams(paramsNeeded: [String], paramsReceived: [(String, String)]) -> [String]? {
    
    var laked: [String] = []
    
    for param in paramsNeeded {
        if paramsReceived.filter({ (key, _) -> Bool in
            return key == param
        }).isEmpty {
            laked.append(param)
        }
    }
    
    return laked.count > 0 ? laked : nil
}

또, 나중에 해보시면 아시겠지만 JSONConvertible 타입의 데이터를 응답 객체의 body에 셋팅하려면 꼭 오류처리(try)를 해줘야 합니다. 매 번 하기 귀찮아서 그냥 함수를 하나 만들어줬습니다. lakedParams(paramsNeeded:paramsReceived:) 함수 아래에 작성해봅니다.

// 응답 보내기
private func sendCompleteResponse(_ response: HTTPResponse, status: HTTPResponseStatus = .ok, jsonBody: JSONConvertible? = nil) {
    do {
        if let json = jsonBody {
            try response.setBody(json: json)
        }
        response.completed(status: status)
    } catch {
        response.completed(status: .internalServerError)
    }
}


참, 그리고 요청에서 실려온 이미지를 저장할 디렉터리도 미리 지정해 두고 싶구요, 매개변수 이름이나 JSON 키값도 미리 지정해두고 싶습니다. ContentsType 구조체 선언 아래쪽에 아래 코드를 추가해봅니다. 사실 어디에 위치하든 크게 상관은 없지만 그래도 이게 예쁘잖아요 :D

// 이미지 저장 디렉터리
private let imageDir: Dir? = {
    let imageDir = Dir("./image")
    
    if imageDir.exists == false {
        do {
            try imageDir.create()
            print("Working Directory (\(imageDir.path)) for examples created.")
        } catch {
            print("Could not create Working Directory for examples.")
            return nil
        }
    }
    
    return imageDir
}()

// JSON 데이터의 Key 값 혹은 요청 매개변수 이름에 사용
private struct JSONKey {
    static let image = "image"
    static let userName = "user_name"
    static let description = "description"
    static let title = "title"
    static let articles = "articles"
    static let articleId = "article_id"
    static let page = "page"
    static let articlesPerPage = "articles_per_page"
    static let currentPage = "current_page"
    static let totalPage = "total_page"
    static let imageUrl = "image_url"
    static let imagePath = "image_path"
    static let message = "message"
    static let error = "error"
}


자, 밑준비를 마쳤습니다.

사용자가 게시물 작성을 요청하는 postArticleHandler(request: response:) 함수를 본격적으로 작성해봅시다. 기존에 함수 내부에 있던 코드는 지워주세요 :)


먼저, 응답은 JSON 형식이 될 것이므로 맨 윗줄에 아래 코드를 작성합니다. response 객체는 클라이언트에게 응답을 줄 때 사용할 객체입니다.

// 응답 컨텐츠 형식은 JSON response.setHeader(.contentType, value: ContentsType.json)


매개변수로 전달된 request 객체에 클라이언트의 요청 정보가 모두 실려있습니다. 먼저 모든 필수 매개변수가 전달되었는지 확인합니다.


POST 메서드로 전달된 매개변수는 request 객체의 postParams() 메서드를 통해 가져올 수 있습니다. postParams() 메서드의 반환 타입은 (String, String) 튜플의 Array 타입입니다.

필수 매개변수인 user_name, title이 모두 전달되었는지 확인하고 싶습니다(image 매개변수는 파일로 받아올 것이니 다음 차례에서 확인합니다). 

// 부족한 매개변수가 없는지 확인
    if let lakedParams = lakedParams(paramsNeeded: [JSONKey.userName, JSONKey.title], paramsReceived: request.postParams) {
        sendCompleteResponse(response, status: .badRequest, jsonBody: [JSONKey.message:"need more params \(lakedParams)"])
    }
    
    // 이미지 파일이 업로드 되었는지 확인
    guard let imageInformation: MimeReader.BodySpec = request.postFileUploads?.first,
        let imageFile = imageInformation.file else {
        sendCompleteResponse(response, status: .badRequest, jsonBody: [JSONKey.message:"need image file"])
            return
    }

자, 이렇게 모든 전달 데이터가 확인되었다면 이미지를 서버의 이미지 디렉터리에 저장할 차례입니다. 먼저 이미지 이름에 사용자 이름을 넣고 싶기 때문에 클라이언트에서 전달된 데이터를 빼냅니다.

// 사용자 이름, 제목 추출
    guard let userName = request.param(name: JSONKey.userName),
        let title = request.param(name: JSONKey.title) else {
            sendCompleteResponse(response, status: .internalServerError, jsonBody: nil)
            return
    }


그리고 이미지가 저장될 디렉터리에 접근할 수 있는지 확인합니다.

// 이미지가 저장될 디렉터리
    guard let imageDirectory = imageDir else {
        sendCompleteResponse(response, status: .internalServerError, jsonBody: nil)
        return
    }

고유한 이미지 파일 이름을 만들어주기 위해서 타임스탬프 값을 활용했습니다. 위에서 request 객체에서 꺼내온 이미지 파일을 서버의 이미지 디렉터리 내부의 경로로 복사해줍니다.

// 고유한 이미지 이름을 위해 타임스템프 값을 활용
    let timestamp: Int = icuDateToSeconds(getNow())
    
    // 사용자이름_타임스템프.jpg 형식으로 파일이름 지정
    let imageFileName: String = userName + "_" + String(timestamp) + ".jpg"
    
    // 이미지가 저장될 경로
    let imageFilePath: String = imageDirectory.path + imageFileName
    
    // 이미지 저장에 실패할 경우 실패 응답 보내기
    do {
        try imageFile.copyTo(path: imageFilePath, overWrite: false)
    } catch {
        sendCompleteResponse(response, status: .internalServerError, jsonBody: [JSONKey.error:error.localizedDescription])
        return
    }

이미지 복사도 마쳤으니 이제 데이터베이스에 정보를 저장할 차례입니다. mongoDB의 컬렉션도 불러오고, DB에 저장할 데이터를 담은 딕셔너리를 생성해줍니다.
  // mongoDB 컬렉션 가져오기
    guard let collection = DB.collection else {
        sendCompleteResponse(response, status: .internalServerError, jsonBody: [JSONKey.error:"data base initialize failed"])
        return
    }
    
    // DB에 저장할 데이터
    // 차후에 응답 데이터로도 사용합니다
    var jsonDictionary = [JSONKey.userName: userName,
                          JSONKey.description: request.param(name: JSONKey.description),
                          JSONKey.title: title,
                          JSONKey.imagePath:imageFilePath]

이제 실질적으로 DB에 저장합니다. 저장을 하면서 문서의 고유ID도 생성해줍니다. 만약 DB 저장에 실패한다면 아까 복사해온 이미지 파일은 서버 디렉터리에서 삭제해주어야 합니다.

    // DB에 저장
    do {
        let jsonString = try jsonDictionary.jsonEncodedString()
        
        let document: BSON = try BSON(json: jsonString)
        
        // 문서 사용 후에는 닫아주는게 좋겠습니다
        defer {
            document.close()
        }
        
        // 문서 고유 아이디 생성 및 아이디 부여
        let oid: BSON.OID = BSON.OID(imageFileName)
        document.append(oid: oid)
        
        // 컬렉션에 문서 저장
        let result = collection.save(document: document)
        
        // 고유 아이디를 응답 데이터에 추가
        jsonDictionary[JSONKey.articleId] = oid.description
        
        if case .success = result { } else {
            sendCompleteResponse(response, status: .internalServerError, jsonBody: [JSONKey.error:result])
            return
        }
        
    } catch {
        
        // DB 저장에 실패했으므로 이미지 파일은 삭제
        File(imageFilePath).delete()
        
        Log.error(message: error.localizedDescription)
        sendCompleteResponse(response, status: .internalServerError, jsonBody: [JSONKey.error:error.localizedDescription])
        return
    }

DB 저장을 완료하면 응답 데이터를 보내 게시물 등록 처리를 완료합니다.

    // 응답 데이터에는 이미지 경로 대신 이미지 URL 전송
    jsonDictionary[JSONKey.imagePath] = nil
    jsonDictionary[JSONKey.imageUrl] = server.serverAddress + ":\(server.serverPort)" + "/image/" + imageFileName
    
    // 모든 작업을 완료하고 JSON 응답
    sendCompleteResponse(response, status: .created, jsonBody: jsonDictionary)

자 이렇게 postArticleHandler(request:response:) 함수 작성을 마쳤습니다.

// 사진 게시물 등록
func postArticleHandler(request: HTTPRequest, response: HTTPResponse) {
    
    // 응답 컨텐츠 형식은 JSON
    response.setHeader(.contentType, value: ContentsType.json)
    
    // 부족한 매개변수가 없는지 확인
    if let lakedParams = lakedParams(paramsNeeded: [JSONKey.userName, JSONKey.title], paramsReceived: request.postParams) {
        sendCompleteResponse(response, status: .badRequest, jsonBody: [JSONKey.message:"need more params \(lakedParams)"])
    }
    
    // 이미지 파일이 업로드 되었는지 확인
    guard let imageInformation: MimeReader.BodySpec = request.postFileUploads?.first,
        let imageFile = imageInformation.file else {
        sendCompleteResponse(response, status: .badRequest, jsonBody: [JSONKey.message:"need image file"])
            return
    }
    
    // 사용자 이름, 제목 추출
    guard let userName = request.param(name: JSONKey.userName),
        let title = request.param(name: JSONKey.title) else {
            sendCompleteResponse(response, status: .internalServerError, jsonBody: nil)
            return
    }
    
    // 이미지가 저장될 디렉터리
    guard let imageDirectory = imageDir else {
        sendCompleteResponse(response, status: .internalServerError, jsonBody: nil)
        return
    }
    
    // 고유한 이미지 이름을 위해 타임스템프 값을 활용
    let timestamp: Int = icuDateToSeconds(getNow())
    
    // 사용자이름_타임스템프.jpg 형식으로 파일이름 지정
    let imageFileName: String = userName + "_" + String(timestamp) + ".jpg"
    
    // 이미지가 저장될 경로
    let imageFilePath: String = imageDirectory.path + imageFileName
    
    // 이미지 저장에 실패할 경우 실패 응답 보내기
    do {
        try imageFile.copyTo(path: imageFilePath, overWrite: false)
    } catch {
        sendCompleteResponse(response, status: .internalServerError, jsonBody: [JSONKey.error:error.localizedDescription])
        return
    }
    
    // mongoDB 컬렉션 가져오기
    guard let collection = DB.collection else {
        sendCompleteResponse(response, status: .internalServerError, jsonBody: [JSONKey.error:"data base initialize failed"])
        return
    }
    
    // DB에 저장할 데이터
    // 차후에 응답 데이터로도 사용합니다
    var jsonDictionary = [JSONKey.userName: userName,
                          JSONKey.description: request.param(name: JSONKey.description),
                          JSONKey.title: title,
                          JSONKey.imagePath:imageFilePath]
    
    // DB에 저장
    do {
        let jsonString = try jsonDictionary.jsonEncodedString()
        
        let document: BSON = try BSON(json: jsonString)
        
        // 문서 고유 아이디 생성 및 아이디 부여
        let oid: BSON.OID = BSON.OID(imageFileName)
        document.append(oid: oid)
        
        // 컬렉션에 문서 저장
        let result = collection.save(document: document)
        
        // 고유 아이디를 응답 데이터에 추가
        jsonDictionary[JSONKey.articleId] = oid.description
        
        if case .success = result { } else {
            sendCompleteResponse(response, status: .internalServerError, jsonBody: [JSONKey.error:result])
            return
        }
        
    } catch {
        
        // DB 저장에 실패했으므로 이미지 파일은 삭제
        File(imageFilePath).delete()
        
        Log.error(message: error.localizedDescription)
        sendCompleteResponse(response, status: .internalServerError, jsonBody: [JSONKey.error:error.localizedDescription])
        return
    }

    // 응답 데이터에는 이미지 경로 대신 이미지 URL 전송
    jsonDictionary[JSONKey.imagePath] = nil
    jsonDictionary[JSONKey.imageUrl] = server.serverAddress + ":\(server.serverPort)" + "/image/" + imageFileName
    
    // 모든 작업을 완료하고 JSON 응답
    sendCompleteResponse(response, status: .created, jsonBody: jsonDictionary)
}

자 이제 mongoDB도 실행된 상태에서, Perfect 서버 애플리케이션을 실행해봅니다. 그리고는 /article 경로에 새로운 게시물 등록을 요청해봅니다.

 

 


 

짜잔!! 성공했습니다! 올바른 응답이 오고 있어요!



URL을 통해 사진 GET


자, 위 이미지의 응답 데이터를 보면 image_url이 오고 있습니다. 그 URL을 통해서 이미지를 한 번 받아와볼까요?

 


아니, 응답받은 URL인데...!! 왜 다운로드가 안되는거죠!? 흐음... 라우팅을 뭔가 해줘야 할 것 같습니다.


으음... 자 그럼 요청을 핸들링 할 수 있는 함수를 하나 또 만들어줍니다. handlers.swift 파일에 아래 함수를 하나 만들어줬습니다. 요청의 주소의 마지막에 위치한 이미지파일의 이름을 가지고 이미지 디렉터리의 파일이 있는지 확인하여 파일 핸들러를 사용하여 응답합니다.

// 이미지 파일
func imageHandler(request: HTTPRequest, response: HTTPResponse) {
    response.setHeader(.contentType, value: ContentsType.formData)
    
    guard let imageDirectory = imageDir else {
        sendCompleteResponse(response, status: .internalServerError)
        return
    }
    
    guard let imageFileName = request.pathComponents.last, imageFileName.contains(".jpg") else {
        sendCompleteResponse(response, status: .badRequest)
        return
    }
    
    request.path = imageFileName
    
    let handler = StaticFileHandler(documentRoot: imageDirectory.path)
    
    handler.handleRequest(request: request, response: response)
}

main.swift 파일로 이동하여 길을 하나 더 뚫어줍니다. 일단 articleURI 상수 아래에 imageURI도 하나 선언해줍니다.

let articleURI = "/article" let imageURI = "/image" let subArticleURI = articleURI + "/*"


그리고 서버 객체를 생성하는 코드 윗 줄에 아래 코드를 작성해줍니다.

routes.add(method: .get, uri: imageURI + "/*", handler: imageHandler(request:response:))

이렇게 작성하면 /image/xxx.jpg 형식으로 요청이 들어오면 imageHandler(request:response:) 함수가 핸들링 하게 될 것입니다.


자 이제 서버를 재시작하고, 다시 요청해볼까요?



 

으헿, 잘 나왔네요!


이번에는 CRUD 중, Create 먼저 해봤습니다. 다음 번에는 Read를 함께 해봐요~!

뿅!




참고문서



 



by yagom

facebook : http://www.facebook.com/yagomSoft

facebook group : https://www.facebook.com/groups/yagom

p.s 제 포스팅을 RSS 피드로 받아보실 수 있습니다.

RSS Feed 받기   


↓↓↓ 블로거에게 공감은 큰 힘이 됩니다 ↓↓↓ 




저작자 표시 비영리 변경 금지
신고

'Swift > Perfect' 카테고리의 다른 글

사진 게시판 API 만들기 [5]  (0) 2017.09.06
사진 게시판 API 만들기 [4]  (0) 2017.08.30
사진 게시판 API 만들기 [3]  (0) 2017.08.09
사진 게시판 API 만들기 [2]  (0) 2017.07.11
사진 게시판 API 만들기 [1]  (6) 2017.06.27
Perfect 라우팅  (0) 2017.05.24
Posted by yagom

더 알아보기

이제까지 포스팅한 내용 외에 추가적으로 알아가야 할 문법과 개념들을 모아봤습니다.

  • 제네릭(Generics)
  • 서브스크립트(Subscript)
  • 접근수준(Access Control)
  • ARC(Automatic Reference Counting)
  • 중첩타입(Nested Types)
  • 사용자정의 연산자(Custom Operators)

스위프트 기본문법 강좌는 여기서 마칩니다. 많은 도움이 되었길 소망합니다.


yagom  



관련저서


스위프트 프로그래밍 - 야곰 지음 (한빛미디어)

http://book.naver.com/bookdb/book_detail.nhn?bid=11445773





by yagom

facebook : http://www.facebook.com/yagomSoft

facebook group : https://www.facebook.com/groups/yagom/


p.s 제 포스팅을 RSS 피드로 받아보실 수 있습니다.

RSS Feed 받기   


↓↓↓ 블로거에게 공감은 큰 힘이 됩니다 ↓↓↓ 

저작자 표시 비영리 변경 금지
신고

'Swift > 기본문법' 카테고리의 다른 글

기본문법을 마치며...  (0) 2017.07.31
고차함수  (0) 2017.07.27
오류처리  (0) 2017.07.24
익스텐션  (0) 2017.07.20
프로토콜  (0) 2017.07.17
assert와 guard  (0) 2017.07.13
Posted by yagom


티스토리 툴바