don't stop believing

이미지 업로드와 static file (이미지, html) 보기 본문

Swift/Perfect

이미지 업로드와 static file (이미지, html) 보기

Tongchun 2018. 2. 7. 17:14

Perfect로 이미지를 업로드하고 이미지를 확인해 보겠습니다.

언제나 그렇듯이 swift 버전을 확인하고 가겠습니다.

$ swift -version
Apple Swift version 4.0.3 (swiftlang-900.0.74.1 clang-900.0.39.2)
Target: x86_64-apple-macosx10.9

프로젝트 폴더를 만들고 이동합니다. 그리고 swift package init으로 초기화 합니다.

$ mkdir nGleServer014
$ cd nGleServer014
TongChunui-MacBook-Pro:nGleServer014 tongchunkim$ swift package init --type executable
Creating executable package: nGleServer014
Creating Package.swift
Creating README.md
Creating .gitignore
Creating Sources/
Creating Sources/nGleServer014/main.swift
Creating Tests/

xcodeproj 파일을 생성하고 열어줍니다.

$ swift package generate-xcodeproj
$ open nGleServer014.xcodeproj/

xcode가 실행되면 Package.swift 파일에 PerfectHTTPServer를 추가합니다.

// swift-tools-version:4.0
import PackageDescription

let package = Package(
    name: "nGleServer014",
    dependencies: [
        .package(url: "https://github.com/PerfectlySoft/Perfect-HTTPServer.git", from: "3.0.0"),
    ],
    targets: [
        .target(
            name: "nGleServer014",
            dependencies: ["PerfectHTTPServer",]),
    ]
)

 xcode를 닫고 swift package update와 swift build를 해줍니다.

$ swift package update
$ swift build

그리고 webroot 폴더를 만들고 그 안에 업로드한 이미지를 받을 images와 html 파일을 담을 docs 폴더를 만들어 줍니다.

그리고 main.swift가 있는 위치에 controller.swift 파일을 만들어 줍니다. 서버 실행과 route를 분산해 줄겁니다.

$ mkdir webroot
$ cd webroot/
$ mkdir images docs
$ cd ../Sources/nGleServer014/
$ touch controller.swift

전체 폴더의 tree 구조는 아래와 같이 만들어 집니다.

$ tree
.
├── Package.resolved
├── Package.swift
├── README.md
├── Sources
│   └── nGleServer014
│       ├── controller.swift
│       └── main.swift
├── Tests
├── nGleServer014.xcodeproj
│   ├── COpenSSL_Info.plist
│   ├── PerfectCHTTPParser_Info.plist
│   ├── PerfectCZlib_Info.plist
│   ├── PerfectCrypto_Info.plist
│   ├── PerfectHTTPServer_Info.plist
│   ├── PerfectHTTP_Info.plist
│   ├── PerfectLib_Info.plist
│   ├── PerfectNet_Info.plist
│   ├── PerfectThread_Info.plist
│   ├── project.pbxproj
│   ├── project.xcworkspace
│   │   ├── contents.xcworkspacedata
│   │   └── xcuserdata
│   │       └── tongchunkim.xcuserdatad
│   │           └── UserInterfaceState.xcuserstate
│   ├── xcshareddata
│   │   └── xcschemes
│   │       ├── nGleServer014-Package.xcscheme
│   │       └── xcschememanagement.plist
│   └── xcuserdata
│       └── tongchunkim.xcuserdatad
│           └── xcschemes
│               ├── nGleServer014.xcscheme
│               └── xcschememanagement.plist
└── webroot
    ├── docs
    └── images
        └── dejavu_1517991366.jpg

이제 다시 xcodeproj를 만들어 주고 열어 봅니다.

$ swift package generate-xcodeproj
$ open nGleServer014.xcodeproj/

먼저 main.swift로 이동합니다.

Controller 클래스는 아직 안만들었지만 우선 아래와 같이 작성합니다.

import PerfectLib
import PerfectHTTP
import PerfectHTTPServer

let server = HTTPServer()
server.serverPort = 8080

let control = Controller()
server.addRoutes(Routes(control.routes))

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

이제 controller.swift로 이동합니다.

import먼저 해줍니다.

import Foundation
import PerfectLib
import PerfectHTTP
import PerfectHTTPServer

이제 request로 들어오는 json 데이터 key 정보들을 정의합니다.

json 데이터가 많아지면 계속 추가하면 됩니다.

struct JSONKey {
    static let userName = "user_name"
    static let image = "image"
    static let imagePath = "image_path"
}

이제 Controller 클레스를 만들고 routes를 정의합니다.

이미지를 업로드할 uri와 이미지를 볼 uri를 추가하고 그에 맞는 handler를 추가합니다. handler 함수는 아래에 설명됩니다.

final class Controller {

    var routes: [Route] {
        return [
            Route(method: .post, uri: "/upload", handler: uploadImage),
            Route(method: .get, uri: "/image/**", handler: viewImage),
        ]
    }
}

request로 전달되는 데이터 중 필수로 지정된 key가 모두 있는지 확인하는 함수를 만들어 줍니다.

필수로 지정된 key가 모두 있다면 nil을 반환하고 부족하다면 Array로 반환됩니다. 이후 옵셔널 체인(if let)으로 처리합니다.

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
}

이미지 저장 디렉토리를 불러옵니다.

이미지가 저장될 폴더는 ./webroot/images 입니다.

let imageDir: Dir? = {
    let imageDir = Dir("./webroot/images")

    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
}()

response로 전달할 함수를 만들어 줍니다.

func returnMessage(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)
    }
}

timestamp를 만들기 위해 extension Date도 해줍니다.

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

드디어 upload 함수를 만들 차례입니다.

func uploadImage(request: HTTPRequest, response: HTTPResponse) {

        // 부족한 매개변수가 없는지 확인합니다.
        let needParams = [JSONKey.userName]
        if let lakedParams = lakedParams(needParams, request.postParams) {

            let returnData = ["error": "필수 key가 전달되지 않았습니다. \(lakedParams)"] as [String: Any]
            returnMessage(result: 1001, data: returnData, response: response)
            return
        }

        // 이미지 파일이 업로드 되었는지 확인
        guard let imageInformation: MimeReader.BodySpec = request.postFileUploads?.first,
            let imageFile = imageInformation.file else {

                let returnData = ["error": "이미지 파일이 전달되지 않았습니다."] as [String: Any]
                returnMessage(result: 1002, data: returnData, response: response)
                return
        }

        // 사용자 이름 추출
        guard let userName = request.param(name: JSONKey.userName) else {
            let returnData = ["error": "사용자 이름 추출에 실패했습니다."] as [String: Any]
            returnMessage(result: 1003, data: returnData, response: response)
            return
        }

        // 이미지가 저장될 디렉터리
        guard let imageDirectory = imageDir else {
            let returnData = ["error": "이미지가 저장될 디렉터리를 확인하지 못했습니다."] as [String: Any]
            returnMessage(result: 1004, data: returnData, response: response)
            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 {
            response.completed(status: .internalServerError)
            return
        }

        let imageUrl = "http://" + server.serverAddress + ":\(server.serverPort)" + "/image/" + imageFileName
        let returnData = [JSONKey.userName: userName, JSONKey.imageUrl: imageUrl]
        returnMessage(result: 0, data: returnData, response: response)
    }

이제 Postman으로 데이터를 전달해 봅시다.

response 데이터가 잘 옵니다.

xcode에서 webroot/images 폴더에 이미지가 업로드 되는지도 확인해 줍니다.

여러번 테스트했는데 모두 잘 들어오고 있습니다.


이제 이미지를 불러 봅시다.

이미지를 보기위해 위에서 route에 get method를 추가했었습니다.

Route(method: .get, uri: "/image/**", handler: viewImage),

viewImage 함수는 아래와 같이 작성합니다.

func viewImage(request: HTTPRequest, response: HTTPResponse) {
    request.path = request.urlVariables[routeTrailingWildcardKey]!
    let handler = StaticFileHandler(documentRoot: "webroot/images", allowResponseFilters: true)
    handler.handleRequest(request: request, response: response)
}

이미지를 upload하고 response로 받은 데이터의 image_url 값을 브라우저로 불러 봅니다.

http://0.0.0.0:8080/image/dejavu_1517993432.jpg

viewImage() 함수는 이미지나 html처럼 static한 데이터를 보는데 사용할 수 있습니다.


여기까지 입니다.





Comments