해당 소스에서 form 데이터 내 input type을 "file"로 설정했었던 적이 있었죠?"file" 속성은 말 그대로 파일 업로드를 위해 설정하는 속성인데요, 이를 form 데이터에 담아 서버 측으로 전송하는 원리입니다. 이 때, form 태그의 enctype을 "multipart/form-data"로 지정했었습니다.
"multipart/form-data"로 보내는 이유를 알려면 먼저 HTTP 통신 원리와 Content-type을 알아야 합니다.
간략하게 설명을 드리자면 통신 원리는 매우 간단해요!
스펙에 맞게 클라이언트와 서버가 문자로 구성된 데이터를 송수신하는 것이라고 생각해주시면 됩니다. 파일을 전송한다고 해서 jpg 파일이나 txt 파일 자체가 전송되는게 아니라 파일 역시도 문자의 형태로 전송되는 것이고, 이러한 문자의 형태를 스펙에 맞추어 서버에 송신하는 것 뿐이거든요. 이미지, 텍스트, 동영상 파일 모두 문자열로 이루어져 있는데 확인하고 싶으신 분들은 아무 이미지 파일이나 동영상 파일 등을 메모장 등 텍스트 편집기로 열어보세요!
Content-Type은 Body에 전송되는 메세지(데이터)의 타입을 정의하는 속성으로 HTTP 요청 Header에서 정의합니다. 이 중에서 파일을 송신할 때 사용하는 속성값이 바로 "multipart/form-data" 형식인 것이지요.
<?php
header("Content-Type:text/html; charset=utf-8;");
// 요청 데이터(API 요구 사항에 맞게 파라미터 설정)
$value_1 = "value"; // key_1에 대한 value
$value_2 = "value"; // key_2에 대한 value
// 파일 절대 경로 설정 (예시)
$fileDir = "C:/Bitnami/wampstack-7.2.14-0/apache2/htdocs/img/test.jpg";
// 요청자 검증을 위한 암호화 데이터(수정 금지).
$userId = "ID";
$userPwd = "Password";
$EncryptBase64 = base64_encode($userId.":".$userPwd);
// 요청 데이터를 배열 형식으로 최초 set.
$data = array(
'key_1' => $value_1,
'key_2' => $value_2,
'fileDir' => $fileDir, // 파일경로
'fileContents' => file_get_contents($fileDir) // 파일 내용
);
// 배열 형식의 데이터를 multipart/form-data 형식으로 변환
$postData = setMultipartFormData($data);
// 요청 API Header 정보(Api-Key 값은 전달받은 값 그대로 사용.)
$headers = array(
'Content-Type: multipart/form-data;boundary=^*******^',
'Content-Length: '.strlen($postData),
'Authorization: Basic '.$EncryptBase64
);
// 요청 URL
$postUrl = "https://";
// 응답 파라미터 변수 선언 및 초기화
$response = "";
// API 호출 및 응답 데이터 저장
$response = reqPost($postData, $postUrl, $headers);
// 응답 데이터 print
echo "Response : " . $response;
// api 요청
function apiCall($data, $url, Array $headers){
$ch = curl_init();
curl_setopt($ch, CURLOPT_URL, $url);
curl_setopt($ch, CURLOPT_HEADER, true);
curl_setopt($ch, CURLOPT_HTTPHEADER, $headers);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_CONNECTTIMEOUT, 30); //connection timeout 30
curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, false);
curl_setopt($ch, CURLOPT_POST, true); // API 호출 시 method를 post로 지정
curl_setopt($ch, CURLOPT_POSTFIELDS, $data); // Data
$response = curl_exec($ch);
curl_close($ch);
return $response;
}
// multipart/form-data 형식으로 변환하는 함수
function setMultipartFormData($arrayData){
$fileData = $arrayData['fileContents'];
$boundary = "^*******^";
$data = "";
unset($arrayData['fileContents']);
foreach ($arrayData as $name => $content) {
$data .= '--' . $boundary . '\r\n'
. 'Content-Disposition: form-data; name="' . $name . '\"\r\n\r\n'
. $content . '\r\n';
}
// 파일 데이터 저장
$data .= '--' . $boundary . '\r\n'
. 'Content-Disposition: form-data; name="file"; filename="' . $arrayData['fileDir'] . '"' . '\r\n'
. 'Content-Type:application/octet-stream' . '\r\n\r\n';
$data .= $fileData . '\r\n';
$data .= "--" . $boundary . '--\r\n';
return $data;
}
?>
** 주의사항 : 요청 필드는 제가 임의로 작성해서 Key_1, Key_2 등으로 만든 것이지만 실제로는 API 명세서에서 요구하는 필드를 입력하여야 합니다. 일반적으로 파일 절대 경로나 파일명을 요구하므로 fileDir이라는 필드를 만들었지만 이는 제가 임의로 작성한 코드이므로 API 명세에 따라 수정이 필요합니다.
또한 예시 코드로 작성하다보니 인증 방식 역시 HTTP 기본 인증 방식인 Basic Authorization을 선택하여 만들었습니다. 실제 코드를 작성하실 때 API 명세에 별도 암호화 방식을 요구할 수 있으니 주의하셔야 합니다.
이렇게 예시 코드를 작성해 보았는데요,
위에서 언급했던 "multipart/form-data" 파라미터는 $headers에 담아 HTTP header 정보에 담았습니다!
이중 가장 자세히 보아야 할 부분은 제가 임의로 만든 setMultipartFormData 함수입니다!
배열로 세팅한 $data를 매개변수로 받아 key-value 데이터와 파일 내용을 분리해서 다시 세팅하는 형태로 구성되어 있는데요, 굳이 이러한 과정을 거치는 이유는 multipart/form-data 형식을 준수하기 위함입니다!
우선 첫째로 $boundary 변수는 header에 Content-type=multipart/form-data 지정 시 함께 지정한 boundary와 같은 값을 가져야 합니다. 이유는 boundary가 HTTP 요청 Body 데이터 중 파일 데이터를 구분하기 위한 역할을 가지기 때문입니다.
둘째로 $boundary 양 옆으로 "--" 기호와 "\r\n" 이스케이프 시퀀스를 넣어 놓았는데요,
먼저 "--" 기호는 body의 끝을 알리는 구분자이며 "\r\n" 이스케이프 시퀀스는 Header와 Body, Body 데이터 간의 구분을 위해 넣었다고 생각하시면 됩니다!
...?
이해가 되지 않는 부분들이 생기실 거에요..!
여기선 요청 Body 데이터만 세팅하는데 왜 또 header/body를 구분하느냐 말이죠??
이유는 body의 내용을 보시면 이해되실 거에요!!
body 데이터에서 Content-Disposition을 지정해주었죠? 이는 HTTP 응답 Header의 한 종류로 브라우저에 Content가 웹페이지로 나타낼지, 다운로드될지 정하는 속성이에요. 즉, $headers에 담은 HTTP 요청 Header 정보가 아닌 응답 Header 정보와 구분하기 위해 "\r\n"을 두번 써서 구분하는 것이랍니다 ㅎㅎ