KLingo Project Documentation 1.0.0
Unreal Engine 5.6 C++ Project Documentation
로딩중...
검색중...
일치하는것 없음
FHttpMultipartFormData.cpp
이 파일의 문서화 페이지로 가기
1// Copyright (c) 2025 Doppleddiggong. All rights reserved. Unauthorized copying, modification, or distribution of this file, via any medium is strictly prohibited. Proprietary and confidential.
2
3
9#include "Misc/FileHelper.h"
10#include "Misc/Guid.h"
11#include "Misc/Paths.h"
12#include "GenericPlatform/GenericPlatformHttp.h"
13
15 : FormDataType(Type)
16{
17 Reset();
18}
19
21{
22 TextParts.Reset();
23 FileParts.Reset();
24 BuiltBody.Reset();
25
26 // Multipart일 때만 Boundary 생성
28 {
29 // 어떤 고유한 토큰이든 괜찮습니다. 디버깅을 위해 읽기 쉽게 유지합니다.
30 Boundary = TEXT("----UE_Multipart_") + FGuid::NewGuid().ToString(EGuidFormats::Digits);
31 }
32}
33
34void FHttpMultipartFormData::AddText(const FString& FieldName, const FString& Value)
35{
36 TextParts.Add({ FieldName, Value });
37}
38
39bool FHttpMultipartFormData::AddFile(const FString& FieldName, const FString& FilePath, const FString& InMimeType)
40{
41 TArray<uint8> FileBytes;
42 if (!FFileHelper::LoadFileToArray(FileBytes, *FilePath))
43 {
44 UE_LOG(LogTemp, Warning, TEXT("[HttpMultipartFormData] Failed to load file: %s"), *FilePath);
45 return false;
46 }
47
48 FString FileNameOnly = FPaths::GetCleanFilename(FilePath);
49 FString Mime = InMimeType.IsEmpty() ? DetectMimeFromExtension(FileNameOnly) : InMimeType;
50
51 FileParts.Add({ FieldName, FileNameOnly, Mime, MoveTemp(FileBytes) });
52 return true;
53}
54
55void FHttpMultipartFormData::SetupHttpRequest(const TSharedRef<IHttpRequest, ESPMode::ThreadSafe>& Request)
56{
57 BuildBody();
58
59 // FormDataType에 따라 다른 Content-Type 설정
61 {
62 Request->SetHeader(TEXT("Content-Type"),
63 TEXT("application/x-www-form-urlencoded"));
64 }
65 else // Multipart
66 {
67 // 경계를 포함한 Content-Type
68 Request->SetHeader(TEXT("Content-Type"),
69 FString::Printf(TEXT("multipart/form-data; boundary=%s"), *Boundary));
70 }
71
72 // 참고: Accept 헤더는 호출자가 설정해야 합니다. 많은 STT 엔드포인트는 JSON을 기대합니다.
73 // Request->SetHeader(TEXT("Accept"), TEXT("application/json"));
74
75 Request->SetContent(BuiltBody);
76}
77
79{
80 BuiltBody.Reset();
81
83 {
84 // application/x-www-form-urlencoded: key1=value1&key2=value2
85 TArray<FString> Pairs;
86 for (const FTextPart& P : TextParts)
87 {
88 FString EncodedKey = FGenericPlatformHttp::UrlEncode(P.Name);
89 FString EncodedValue = FGenericPlatformHttp::UrlEncode(P.Value);
90 Pairs.Add(FString::Printf(TEXT("%s=%s"), *EncodedKey, *EncodedValue));
91 }
92
93 FString Body = FString::Join(Pairs, TEXT("&"));
94 AppendUtf8(BuiltBody, Body);
95 }
96 else // Multipart
97 {
98 auto Add = [](TArray<uint8>& Dest, const FString& S)
99 {
100 AppendUtf8(Dest, S);
101 };
102
103 const FString LineEnd = TEXT("\r\n");
104 const FString BoundaryLine = TEXT("--") + Boundary;
105
106 // 텍스트 파트
107 for (const FTextPart& P : TextParts)
108 {
109 Add(BuiltBody, BoundaryLine + LineEnd);
110 Add(BuiltBody, FString::Printf(
111 TEXT("Content-Disposition: form-data; name=\"%s\"%s"),
112 *P.Name, *LineEnd));
113 Add(BuiltBody, LineEnd); // 헤더와 값 사이의 빈 줄
114 Add(BuiltBody, P.Value + LineEnd);
115 }
116
117 // 파일 파트
118 for (const FFilePart& P : FileParts)
119 {
120 Add(BuiltBody, BoundaryLine + LineEnd);
121 Add(BuiltBody, FString::Printf(
122 TEXT("Content-Disposition: form-data; name=\"%s\"; filename=\"%s\"%s"),
123 *P.Name, *P.FileName, *LineEnd));
124 Add(BuiltBody, FString::Printf(
125 TEXT("Content-Type: %s%s"),
126 *P.MimeType, *LineEnd));
127 Add(BuiltBody, LineEnd); // 바이너리 데이터 앞의 빈 줄
128
129 // 바이너리 데이터
130 BuiltBody.Append(P.Data);
131
132 // 파일 내용 뒤의 CRLF
133 AppendUtf8(BuiltBody, LineEnd);
134 }
135
136 // 닫는 경계
137 Add(BuiltBody, BoundaryLine + TEXT("--") + LineEnd);
138 }
139}
140
141void FHttpMultipartFormData::AppendUtf8(TArray<uint8>& Dest, const FString& Str)
142{
143 FTCHARToUTF8 Conv(*Str);
144 Dest.Append(reinterpret_cast<const uint8*>(Conv.Get()), Conv.Length());
145}
146
148{
149 const FString Ext = FPaths::GetExtension(FileName, /*bIncludeDot*/ false).ToLower();
150
151 // 일반적인 오디오 타입
152 if (Ext == TEXT("mp3")) return TEXT("audio/mpeg");
153 if (Ext == TEXT("wav")) return TEXT("audio/wav");
154 if (Ext == TEXT("ogg")) return TEXT("audio/ogg");
155 if (Ext == TEXT("flac")) return TEXT("audio/flac");
156 if (Ext == TEXT("m4a")) return TEXT("audio/mp4");
157 // 대체 타입
158 if (Ext == TEXT("txt")) return TEXT("text/plain");
159 if (Ext == TEXT("json")) return TEXT("application/json");
160
161 // 기본 타입
162 return TEXT("application/octet-stream");
163}
FHttpMultipartFormData 클래스를 선언합니다.
EFormDataType
HTTP 폼 데이터 전송 방식을 정의합니다.
@ FormUrlEncoded
application/x-www-form-urlencoded (OAuth2 등)
@ Multipart
multipart/form-data (파일 업로드용)
TArray< FTextPart > TextParts
static void AppendUtf8(TArray< uint8 > &Dest, const FString &Str)
문자열을 UTF-8로 변환해 버퍼에 추가합니다.
void BuildBody()
등록된 파트를 기반으로 HTTP 본문을 구성합니다.
void AddText(const FString &FieldName, const FString &Value)
간단한 텍스트 필드를 추가합니다.
static FString DetectMimeFromExtension(const FString &FileName)
파일 확장자를 기반으로 MIME 타입을 추론합니다.
TArray< FFilePart > FileParts
void Reset()
모든 파트를 제거하고 경계를 재생성합니다.
FHttpMultipartFormData(EFormDataType Type=EFormDataType::Multipart)
FormData 객체를 생성합니다.
void SetupHttpRequest(const TSharedRef< IHttpRequest, ESPMode::ThreadSafe > &Request)
HTTP 요청에 Content-Type 헤더와 본문을 설정합니다.
bool AddFile(const FString &FieldName, const FString &FilePath, const FString &MimeType=TEXT(""))
파일 데이터를 multipart 본문에 추가합니다.