회사에서 투자정보 서버에서 서비스로 제공해 주던 데이터를 갑자기 웹 파트를 통해 URL을 이용하여 JSON 파일(.json)로 데이터를 다운 받을 수 있게 제공해 준다고 하여 웹 URL 상의 JSON 파일을 연결하여 JSON 데이터를 읽어와서 사용해야 하는 경우가 발생했다.
그래서 우리 코드 상에 잘 없는 경우인 Http URL 통신 로직을 본격적으로 추가하게 되었다.
새로운 HTTP URL 파일 읽기 함수 추가
기존 Utility 모듈(DLL)에 관련 함수가 있지만 오래된 로직이고 하니 이 참에 새로 추가하기로 한다.
추가할 기능의 내용과 예제 기록을 시작해 보자.
구현 내용
CInternetSession을 사용하여 인터넷 세션을 생성하고 이 세션으로 CHttpConnection을 얻어 웹 서버에 연결한다.
다음, CHttpConnectio 객체를 사용하여 CHttpFile 객체를 열고 URL에서 C++ JSON 라이브러리에서 처리 가능한 JSON 문자열로 JSON 데이터를 읽어 온다.
예제 코드
#include <afxinet.h> // CInternetSession, CHttpConnection, CHttpFile을 사용하기 위해 필요합니다.
#include <iostream> // 콘솔 출력을 위해 (디버깅 목적)
#include <string> // std::string을 사용하기 위해
// URL에서 JSON 데이터를 읽어오는 함수
std::string ReadJsonFromUrl(const CString& strUrl)
{
std::string strJsonData;
CInternetSession session; // 인터넷 세션을 생성합니다.
CHttpConnection* pConnection = NULL;
CHttpFile* pFile = NULL;
try
{
// URL을 파싱하여 서버 이름, 객체 경로 등을 얻습니다.
DWORD dwServiceType;
CString strServer;
CString strObject;
INTERNET_PORT nPort;
if (!AfxParseURL(strUrl, dwServiceType, strServer, strObject, nPort) ||
dwServiceType != INTERNET_SERVICE_HTTP)
{
// URL 파싱 실패 또는 HTTP 서비스가 아닌 경우
AfxMessageBox(_T("유효하지 않은 URL이거나 HTTP/HTTPS URL이 아닙니다."));
return "";
}
// HTTP 연결을 엽니다.
pConnection = session.GetHttpConnection(strServer, nPort);
if (pConnection == NULL)
{
AfxMessageBox(_T("HTTP 연결을 설정할 수 없습니다."));
return "";
}
// HTTP 파일을 엽니다 (GET 요청)
pFile = pConnection->OpenRequest(CHttpConnection::HTTP_VERB_GET, strObject);
if (pFile == NULL)
{
AfxMessageBox(_T("HTTP 요청을 열 수 없습니다."));
return "";
}
// 요청을 보냅니다.
pFile->SendRequest();
// HTTP 상태 코드를 확인합니다. (예: 200 OK)
DWORD dwRet;
pFile->QueryInfoStatusCode(dwRet);
if (dwRet != HTTP_STATUS_OK)
{
CString strMessage;
strMessage.Format(_T("HTTP 요청이 실패했습니다. 상태 코드: %d"), dwRet);
AfxMessageBox(strMessage);
return "";
}
// 파일에서 데이터를 읽습니다.
char szBuff[4096]; // 4KB 버퍼
UINT nRead;
while ((nRead = pFile->Read(szBuff, sizeof(szBuff) - 1)) > 0)
{
szBuff[nRead] = '\0'; // 널 종료
strJsonData += szBuff;
}
// --- BOM 제거 로직 추가 시작 ---
// UTF-8 BOM (0xEF, 0xBB, 0xBF) 확인 및 제거
if (strJsonData.length() >= 3 &&
(unsigned char)strJsonData[0] == 0xEF &&
(unsigned char)strJsonData[1] == 0xBB &&
(unsigned char)strJsonData[2] == 0xBF)
{
strJsonData = strJsonData.substr(3); // 앞 3바이트 제거
TRACE("UTF-8 BOM이 감지되어 제거되었습니다.\n");
}
// --- BOM 제거 로직 추가 끝 ---
}
catch (CInternetException* pEx)
{
TCHAR szErr[1024];
pEx->GetErrorMessage(szErr, sizeof(szErr) / sizeof(TCHAR));
AfxMessageBox(szErr); // 예외 메시지를 표시합니다.
pEx->Delete(); // 예외 객체를 삭제합니다.
}
// 리소스를 해제합니다.
if (pFile)
{
pFile->Close();
delete pFile;
}
if (pConnection)
{
pConnection->Close();
delete pConnection;
}
session.Close(); // 세션을 닫습니다.
return strJsonData;
}
// 예제 사용법
void ExampleUsageOfReadJsonFromUrl()
{
// 예시 JSON URL (실제 작동하는 JSON URL로 변경하세요)
// 이 예제에서는 더미 JSON 데이터를 제공하는 API를 사용했습니다.
// https://jsonplaceholder.typicode.com/posts/1
CString url = _T("http://jsonplaceholder.typicode.com/posts/1");
std::string jsonContent = ReadJsonFromUrl(url);
if (!jsonContent.empty())
{
// JSON 데이터가 성공적으로 읽혔을 때
AfxMessageBox(_T("JSON 데이터를 성공적으로 읽었습니다. 디버그 창을 확인하세요."));
// 디버그 출력 (Visual Studio의 출력 창에서 확인 가능)
TRACE("읽어온 JSON 데이터:\n%s\n", jsonContent.c_str());
// 여기서 jsonContent를 JSON 파싱 라이브러리(예: nlohmann/json, rapidjson 등)를 사용하여 파싱할 수 있습니다.
// 예를 들어:
// #include "nlohmann/json.hpp"
// json::json parsedJson = json::json::parse(jsonContent);
// TRACE("JSON 파싱 예시 - userId: %d\n", parsedJson["userId"].get<int>());
}
else
{
AfxMessageBox(_T("JSON 데이터를 읽는 데 실패했습니다."));
}
}
이슈 : BOM(Byte Order Mark)
잘 되나 싶었는데 웹 파트에서 올려 놓은 JSON 파일의 제일 앞 3바이트에 알 수 없는 값이 들어 있어 JSON Parsing이 안되었다.
한참을 씨름하다가 결국 알아낸 내용이 BOM(Byte Order Mark)에 관한 내용이다.
BOM은 유니코드 텍스트 파일의 시작 부분에 붙는 선택적인 바이트 시퀀스이다. 이 마크는 해당 텍스트 파일이 어떤 유니코드 인코딩으로 되어 있는지, 멀바이트의 경우 바이트 오더링이 어떻게 되는지 알려주는 역할을 한다.
우리 회사에서 발생한 문제는 UTF-8 BOM 이었다. 그래서 위 예제 코드와 같은 코드에 BOM 제거 코드를 추가해 주어야 했다.
결과
다행히 BOM(Byte Order Mark) 이슈까지 잘 해결하였는데 웹 파트에서 HTTP URL에 올려 놓은 파일 자체를 조치하여 BOM 바이트 3개를 제거하기로 했다.
오늘도 덕분에 열코딩으로 시간이 잘 가는 하루였다. 😂