PHP에서 다른 서버의 API나 폼 처리 페이지로 값을 보내고 결과를 받아야 할 때가 자주 있습니다. 결제 결과를 알려 주거나, 사내 서버의 처리 페이지를 호출하거나, 외부 서비스에 데이터를 넘기는 경우죠. 브라우저가 폼을 전송하듯 서버에서 POST 요청을 만들면 되는데, 가장 많이 쓰는 도구가 curl입니다.
curl로 POST 보내기
먼저 보낼 값을 연관 배열로 만듭니다.
$data = array('title' => $post_title, 'num' => $post_number);
그리고 curl 핸들을 만들어 옵션을 하나씩 지정한 뒤 실행합니다.
$post_field_string = http_build_query($fields, '', '&');
$ch = curl_init(); // curl 초기화
curl_setopt($ch, CURLOPT_URL, $url); // url 지정하기
curl_setopt($ch, CURLOPT_POST, true); // POST 전송 여부
curl_setopt($ch, CURLOPT_POSTFIELDS, http_build_query($data, '', '&')); // POST DATA
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true); // 요청 결과를 문자열로 받음
curl_setopt($ch, CURLOPT_CONNECTTIMEOUT, 10); // curl이 첫 응답 시간에 대한 timeout : 10초
curl_setopt($ch, CURLOPT_TIMEOUT, 60); // curl 전체 실행 시간에 대한 timeout
curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, false); // 원격 서버의 인증서가 유효한지 검사하지 않음
curl_setopt($ch, CURLOPT_HTTPHEADER, array(
'Host: 192.168.0.100',
'User-Agent: curl',
'Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8',
'Accept-Language: ko-KR,ko;q=0.8,en-US;q=0.5,en;q=0.3',
'Content-Type: application/x-www-form-urlencoded',
'Referer: http://program1472.com/',
'Connection: keep-alive',
'Upgrade-Insecure-Requests: 1'
));
$response = curl_exec($ch); // 요청 결과
curl_close ($ch);
//echo $response;
/*
요청 결과를 받지 않아도 된다면, 'CURLOPT_RETURNTRANSFER'를 false로 바꾸고 '$result = curl_exec($ch);'를 'curl_exec($ch);'로 바꾸어도 된다.
CURLOPT_CONNECTTIMEOUT은 처음으로 응답을 받기까지 기다리는 시간(단위: 초)이다.
전체 요청 시간이 CURLOPT_TIMEOUT을 넘기면 전체 작업을 강제로 끝낸다. CURLOPT_TIMEOUT은 file_get_contents 함수에서 넣는 timeout과 같다.
*/
http_build_query 가 배열을 title=...&num=... 형태로 바꾸고 값도 URL 인코딩해 줍니다. 한글 값이 들어가도 따로 인코딩할 필요가 없어요. 첫 줄의 $post_field_string 은 정의되지 않은 $fields 를 쓰고 있고 아래에서 쓰이지도 않으니 지워도 됩니다. 실제로 전송되는 건 CURLOPT_POSTFIELDS 에 넣은 $data 입니다.
CURLOPT_RETURNTRANSFER 를 true 로 줘야 curl_exec 가 응답 본문을 문자열로 돌려줍니다. 이걸 빼먹으면 응답이 그대로 화면에 출력되고 curl_exec 는 true 만 돌려줘서, "결과가 왜 1이지?" 하고 헤매게 됩니다. 반대로 결과가 필요 없는 알림성 호출이라면 주석에 적은 대로 false 로 두고 curl_exec($ch); 만 호출해도 됩니다.
타임아웃 두 개의 차이
CURLOPT_CONNECTTIMEOUT 은 상대 서버와 연결이 맺어질 때까지 기다리는 시간이고, CURLOPT_TIMEOUT 은 연결부터 응답을 다 받을 때까지 전체 시간의 상한입니다. 상대 서버가 꺼져 있으면 앞의 10초에서 끊기고, 연결은 됐는데 처리가 오래 걸리면 뒤의 60초에서 끊깁니다. 웹 페이지 안에서 호출한다면 PHP 자체의 max_execution_time(보통 30초)보다 길게 잡아 봐야 PHP가 먼저 멈추니, 그 안에서 맞추는 게 좋습니다.
CURLOPT_SSL_VERIFYPEER 를 false 로 둔 건 사설 인증서를 쓰는 내부 서버에 붙을 때 편하라고 한 설정입니다. 외부 서비스와 통신할 때는 인증서 검증을 끄지 않는 게 원칙입니다. 그리고 헤더의 Host: 192.168.0.100 은 내부망 서버를 호출하던 예제라 들어간 값이라, 다른 서버로 보낼 때는 이 줄을 빼거나 실제 호스트로 바꿔야 합니다. Host가 맞지 않으면 가상 호스트를 쓰는 서버에서는 엉뚱한 사이트가 응답합니다.
실패 원인을 알고 싶으면 curl_close 전에 curl_error($ch) 와 curl_getinfo($ch, CURLINFO_HTTP_CODE) 를 찍어 보세요. 응답이 빈 문자열일 때 연결 문제인지 서버가 4xx/5xx를 준 건지 바로 구분됩니다. PHP 8부터는 curl_close 를 호출하지 않아도 핸들이 자동으로 정리되지만, 호출해도 문제는 없습니다.
file_get_contents로 하는 방법
curl 확장이 없는 환경이라면 스트림 컨텍스트를 만들어 file_get_contents 로도 POST를 보낼 수 있습니다. 아래는 같은 데이터를 보내는 예입니다.
$context = stream_context_create(array(
'http' => array(
'method' => 'POST',
'header' => "Content-Type: application/x-www-form-urlencoded\r\n",
'content' => http_build_query($data, '', '&'),
'timeout' => 60,
),
));
$response = file_get_contents($url, false, $context);
코드는 짧지만 이 방식은 allow_url_fopen 설정이 켜져 있어야 동작합니다. 공유 호스팅 중에는 보안상 이걸 꺼 두는 곳이 많아서, 서버를 옮겼더니 갑자기 원격 호출이 안 되는 경우가 생깁니다. 그래서 저는 어디서든 돌아가야 하는 코드라면 curl 쪽으로 짭니다.