2018년 3월 20일 화요일

cocos2d-x 에서 binary file 을 post 하기 (이미지파일 포스트)

2016. 1. 7. 15:21

게임에서 이미지 또는 Binary 파일을 Post 할 일이 뭐가 있을까 했지만 
이번 프로젝트에서는 그것을 사용하게 되었다. 

현재에 cocos에서 제공되는 것은 문자열만 post가 가능했기에 이 부분을 수정해 보았다. 
( 첨부된 파일은 cocos2d-x 3.4 버전 기준 , 3.8버전을 보니 HttpClient.cpp 내용이 조금 달랐다. 그건 나중에 수정해 보겠다 ^^ )

cocos2d/cocos/network 폴더에 있는 HttpClient.cpp 와 HttpRequest.h 를 수정하면 된다. 
변경된 내용은 첨부된 파일을 받아보면 되고 중요 부분만 설명을 하겠다. 

▶ cocos의 코드에서 호출할때 
( CSDHttpUtils 클래스는 회사 프로젝트이기에 모든 코드를 공개할 수는 없지만 아래와 같은 식으로 코드를 구성하면 된다. )

// postType : 새롭게 지정한 POSTFILE 입력
// pForm: form에 있는 필드 내용을 문자열로 입력
// nFormSize : form 데이터의 크기 , 문자열 크기
// pszFile: 파일명 전달 ( 파일 데이터가 아니고 파일명을 전달 한다 )
bool CSDHttpUtils::_postHttpData(HttpRequest::Type postType, const char * psdUrl, const char * pForm, int nFormSize,const char * pszFile)
{
    HttpRequest* request = new HttpRequest();

    request->setUrl(psdUrl);

    CCLOG("_postHttpData: url:%s", psdUrl);

    request->setRequestType(postType);

    if (postType == HttpRequest::Type::POSTFILE)        // file 전송일때
    {
        request->setFilePath(pszFile);                  // 파일명을 전달하면 curl 에서 해당 파일을 읽어들여 전달
    }

    request->setRequestData(pForm, nFormSize );    // post form data 
   

    auto client = HttpClient::getInstance();
    client->enableCookies(NULL);
    client->send(request);


    request->setResponseCallback([this](HttpClient* client, HttpResponse* response)
    {
        this->ResponseData(client, response);
    });

    request->release();

    return true;
}

// 요청에 대한 처리 결과를 받는다
bool CSDHttpUtils::ResponseData(HttpClient* client, HttpResponse* response)
{
    SDASSERT(client);
    SDASSERT(response);

    if ((!client) || (!response))
        return false;

    // http error code는 필요에 따라서 넣어 주면 된다
    if (!response->isSucceed() || (response->getResponseCode() >= 400 && response->getResponseCode() <= 410) )
    {
        CCLOG("ERROR : %s", response->getErrorBuffer());

        SDASSERT(!response->isSucceed());        
        return false;
    }

    if (response->getHttpRequest()->getTag())
        CCLOG("responseCode:%ld, url:%s, tag:%s", response->getResponseCode(), response->getHttpRequest()->getUrl(), response->getHttpRequest()->getTag());
    else
        CCLOG("responseCode:%ld, url:%s", response->getResponseCode(), response->getHttpRequest()->getUrl());

    if (response->isSucceed())
    {
        // dump string 
        char * pBuffer = NULL;
        std::vector<char> * buffer = response->getResponseData();
        if (buffer->size() > 0)
        {
            pBuffer = new char[buffer->size() + 1];         // 메모리 할당

            for (int i = 0; i < (int)buffer->size(); i++)
                pBuffer[i] = (*buffer)[i];

            pBuffer[buffer->size()] = '\0';

            // 문자열이 길때 CCLOG로 적으면 오류가 나기에 
            if (buffer->size() > 200)
                CCLOG("get buffer size: %d", buffer->size());
            else
                CCLOG("get buffer size: %d, %s", buffer->size(), pBuffer);

            delete[] pBuffer;

            return true;
        }
    }

    return false;
}

위와 같이 하고 _postHttpData() 함수는 아래처럼 호출 하면 된다. 
(sdUrl, sdPost, pszFilename 은 모두 Pseudo code로 이해해 주시기를 )

sdUrl = "http://192.168.1.1/posturl";       // post url 을 여기에 넣는다 ( ex: posttest.php 등등 )
sdPost.Format("part=%d&category=%d&question=%s&no=%d"
                , nPart, nCategory
                , pszQuestion
                , nNo
                );

pszFilename = "filename and path" ;      // 이값이 없을때는 넣지 않아도 상관 없다

_postHttpData(POSTFILE, sdUrl, sdPost, sdPost.GetLength(), pszFilename);


다음은 library 부분을 보면

▶ HttpClient.cpp 와 HttpRequest.h  수정 부분 

아래 3부분을 유의해서 보면 된다 

1. HttpRequest.h에 POSTFILE 선언 추가

2. processResponse()에 POSTFILE 일때 processPostFileTask() 함수 호출

3. processPostFileTask() 처리
cocos에서는 멀티플랫폼 지향이기에 http 관련 처리를 curl 을 이용한다. 
curl은 매우 편리한 라이브러리이기에 관심있는 사람은 한번 스터디를 해주는 것도 좋다. ( 쉽고 편함 ^^ )

아래코드를 보면 두부분으로 나뉘어 있는것을 알 수 있다. 

1) 파일명이 지정되었을때 해당 파일을 curl_formadd() 함수로 post할 form에 추가하는 것
2) 그외의 필드 내용들을 curl_formadd() 로 form에 추가하는 것

자세히 보면 

1) 파일명이 지정되었을때 해당 파일을 curl_formadd() 함수로 post할 form에 추가하는 것
    // 파일명이 지정되어 있을때
    if (request->getFilePath().size() > 3)  // 확장자보다는 크게 들어 와야지
    {
        string mimetype = "";

        // node 서버에서 mime type 체크해서 필터링하기에 정확한 값으로 넣어준다 ( 현재는 jpg,png 이외에는 안 받음)
        if (request->getFilePath().find(".jpg") || request->getFilePath().find(".jpeg"))
            mimetype = "image/jpeg";
        else if (request->getFilePath().find(".png"))
            mimetype = "image/png";
        else
            mimetype = "application/octet-stream";

        curl_formadd(&form, &postend,
            CURLFORM_COPYNAME, "files",     // 서버에서 이미지등 파일로 지정한 이름
            CURLFORM_FILE, request->getFilePath().c_str(),
            CURLFORM_CONTENTTYPE, mimetype.c_str(),
            CURLFORM_END);

    }

위에서 files는 php, asp, node.js등등의 웹서버에서 request로 받는 이름을 명시한 것이다. 
mimetype 을 지정한 이유는 해당 웹서버에서 지정된 mime type 을 체크하는 부분이 있어서 그렇게 한 것인데 
서버에 그런 작업을 하지 않는 경우는 이것을 빼도 상관없겠다. ( 그냥 octet-stream 을 넣어 주면 됨 )

2) 그외의 필드 내용들을 curl_formadd() 로 form에 추가하는 것
    for (vector<string>::iterator iter = tokens.begin(); iter != tokens.end();)
    {
        strTmp = (*iter).c_str();
        
        tokensField.clear();

        SDTokenize(strTmp, tokensField, "=");   // 다시 한번 = 로 key 와 vaule를 분리

        if (tokensField.size() >= 1)            // 최소한 key 값은 있으면
        {
            const char * pVal = nullptr;
            int nLen = 0;

            if (tokensField.size() > 1)         // value가 있으면
            {
                pVal = tokensField[1].c_str();
                nLen = tokensField[1].size();
            }

            curl_formadd(&form, &postend,
                CURLFORM_COPYNAME, tokensField[0].c_str(),
                CURLFORM_COPYCONTENTS, pVal,
                CURLFORM_CONTENTSLENGTH, nLen,
                CURLFORM_END);
        }

        ++iter;
    }

루프를 돌면서 필드 내용을 모두 추가해 준다. 

그리고 최종적으로 perform()을 호출해서 모든 내용을 전송하면 된다

댓글 없음:

댓글 쓰기