제임스딘딘의
Tech & Life

개발자의 기록 노트/C

[C][CURL] CURLOPT_FOLLOWLOCATION 의 버전별 동작 차이

제임스-딘딘 2017. 8. 31. 01:49


C언어로 프로그램을 작성하다보면, HTTP 통신을 해야 할 때가 종종있는데, 이때 개인적으로 선호하는 라이브러리가 있다.

바로 CURL[각주:1] 이다. 라이브러리만을 따로 부를땐 libcurl 이라는 이름으로도 부른다.



이 libcurl을 사용해서 HTTP 처리를 하다보면, 서버가 주는 3xx 응답을 만날때가 있다. 이때 클라이언트는 이에 대한 추가적인 처리 - 변경 혹은 이동된 URL을 따라간다던가 하는 - 를 해야 할 경우가 있다.  libcurl은 이때 간단히 사용할 수 있는 옵션을 제공하는데 바로 'CURLOPT_FOLLOWLOCATION' 옵션이다.


우선 HTTP 3xx status에 대해 간단히 살펴보겠다.


300(여러 선택항목): 서버가 요청에 따라 여러 조치를 선택할 수 있다. 서버가 사용자 에이전트에 따라 수행할 작업을 선택하거나, 요청자가 선택할 수 있는 작업 목록을 제공한다.


301(영구 이동): 요청한 페이지를 새 위치로 영구적으로 이동했다. GET 또는 HEAD 요청에 대한 응답으로 이 응답을 표시하면 요청자가 자동으로 새 위치로 전달된다.


302(임시 이동): 현재 서버가 다른 위치의 페이지로 요청에 응답하고 있지만 요청자는 향후 요청 시 원래 위치를 계속 사용해야 한다.


303(기타 위치 보기): 요청자가 다른 위치에 별도의 GET 요청을 하여 응답을 검색할 경우 서버는 이 코드를 표시한다. HEAD 요청 이외의 모든 요청을 다른 위치로 자동으로 전달한다.


304(수정되지 않음): 마지막 요청 이후 요청한 페이지는 수정되지 않았다. 서버가 이 응답을 표시하면 페이지의 콘텐츠를 표시하지 않는다. 요청자가 마지막으로 페이지를 요청한 후 페이지가 변경되지 않으면 이 응답(If-Modified-Since HTTP 헤더라고 함)을 표시하도록 서버를 구성해야 한다.


305(프록시 사용): 요청자는 프록시를 사용하여 요청한 페이지만 액세스할 수 있다. 서버가 이 응답을 표시하면 요청자가 사용할 프록시를 가리키는 것이기도 하다.


307(임시 리다이렉션): 현재 서버가 다른 위치의 페이지로 요청에 응답하고 있지만 요청자는 향후 요청 시 원래 위치를 계속 사용해야 한다.


308(영구 리다이렉션, RFC에서 실험적으로 승인됨)


HTTP의 3xx 상태값은 총 9가지 상태가 IETF RFC 2616에 정의되어 있다.


예를들어 302(임시 이동 - temporarily moved) 를 서버로 부터 응답받았다고 하자. 서버는 보통 HTTP header에 이동된 URL을 함께 전달해야 한다.


HTTP 302 status header




위 사진들처럼 'Location:" 필드에 이동된 URL을 전달한다. 유의해야 할 점은, 서버에 따라  절대URL로 알려주기도 하고, 상대URL로 알려주기도 한다는 점이다.

이것을 자세히 알아보고자 하는건 아니고, libcurl을 사용할 때 이를 어떻게 처리할 지를 알아보고자 한다.



API header 및 prototype

#include <curl/curl.h>


CURLcode curl_easy_setopt(CURL *handle, CURLOPT_FOLLOWLOCATION, long enable);


앞서 언급한 CURLOPT_FOLLOWLOCATION 옵션은 curl_easy_setopt( ) 의 2번째 매개변수로 사용한다.

3번째 매개변수로 '0L' 혹은 '1L'을 줄수 있다. 어떤 값을 주느냐에 따라 동작이 달라진다.


0L : 3xx에 대해 libcurl은 아무런 처리를 하지 않는다.

1L : 3xx에 대해 libcurl의 내장된 로직으로 처리를 한다. 내장된 로직은 HTTP헤더의 Location: 필드의 URL로 HTTP request 다시 시도하는 것이다.


자세한 설명

3번째 'long enable' 매개변수를 1로 설정하면 libcurl 라이브러리가 3xx 응답을 받았을 때, HTTP 헤더의 일부로 서버가 보내는 Location: 필드의 값을 따라가도록 설정하는 것이다. Location: 필드는 따라야 할 상대URL 또는 절대URL가 지정될 수 있다.


libcurl은 새로운 URL에 대한 또 다른 요청을하고 더 이상 그러한 헤더가 반환되지 않을 때까지 새로운 Location: 헤더를 따라간다. CURLOPT_MAXREDIRS를 통해서 libcurl이 따라가는 'redirection'의 회수를 제한할 수 있다.


libcurl은 자동으로 따르는 프로토콜을 제한한다. 허용 된 프로토콜은 CURLOPT_REDIR_PROTOCOLS로 설정된다. 기본적으로 libcurl은 보안상의 이유로 인해 비활성화 된 프로토콜을 제외한 모든 프로토콜을 redirection 할 수 있다. => 7.19.4 버전부터 FILE 및 SCP 프로토콜이 비활성화되며, 7.40.0 버전부터는 SMB 및 SMBS도 비활성화된다.


Location을 따라갈 때, 3xx 응답 코드는 다음 요청에서 사용할 요청 방법을 지시한다. 301, 302 및 303 응답의 경우 CURLOPT_POSTREDIR이 libcurl을 지정하지 않는 한 libcurl은 메소드를 GET으로 전환합니다. 다른 모든 3xx 코드는 libcurl이 동일한 메소드를 다시 전송하도록 한다.


기존 위치 추적이 너무 단순하거나 기능이 부족하다고 생각하는 사용자라면, CURLOPT_FOLLOWLOCATION 대신 curl_easy_getinfo의 CURLINFO_REDIRECT_URL 옵션을 사용하여 redirection 추적 로직을 구현하는 것이 차라리 매우 싸게먹힌다.



겪은 이슈

libcurl 7.40 버전과 libcurl 7.50 버전에서 모든 세팅을 동일하게 했으나 서로 다른 동작을 하는 경우가 있다. 예상치 못한 문제였다.


조건

1. CURLOPT_PROXY로 proxy server를 사용하도록 libcurl을 설정할 것

2. CURLOPT_FOLLOWLOCATION를 사용하도록 libcurl을 설정할 것

3. 위 1에서 설정한 proxy server가 꽤나 저성능(기능도 적고, 단순하고, 스마트하지 못한)일 것


위 조건을 만족한 상태에서, 예를들어, http://x-sub.abcd.com/first.html 을 요청(HTTP GET)했다고 하자. 

그리고 x-sub.abcd.com 서버가 302 상태값과 함께 Location: http://subdomain.abcd.com/second.html 을 주었다고 가정하자. 

main domain은 abcd.com으로 동일하지만 sub domain이 다르며, 각각의 sub domain을 호스팅하는 서버가 물리적으로 다를 수 있다.


libcurl 7.40 동작

프록시 서버와 연결 -> HTTP GET 으로 http://x-sub.abcd.com/first.html 를 요청함.

-> 302  Location: http://subdomain.abcd.com/second.html 를 받음 -> 프록시 서버와 새로운 포트로 새로운 연결 -> http://subdomain.abcd.com/second.html 를 요청함 -> 200 OK 받음 -> 프록시 서버와 연결 끊음 -> 프록시 서버와 새로운 포트로 새롭게 열었던 연결 끊음


libcurl 7.50 동작

프록시 서버와 연결 -> HTTP GET 으로 http://x-sub.abcd.com/first.html 를 요청함.

-> 302  Location: http://subdomain.abcd.com/second.html 를 받음 ->  http://subdomain.abcd.com/second.html 를 요청함 -> 404 Not found 받음 -> 프록시 서버와 연결 끊음


libcurl 7.50에서는 프록시 서버와 새로운 포트로 새롭게 연결하는 동작이 없었다. 이 점은 개선된 점이다. 프록시 서버와 매번 연결하는 부하를 줄였다. 그런데 redirection URL의 서버가 물리적으로 다른 경우, 프록시 서버에서 새로운 서버로 연결을 진행해야 할 것 같은데, 그렇지 않은 경우가 있었다. 첫번째 요청했던 서버한테 그대로 redirection URL의 resource를 HTTP GET 으로 요청하는 것처럼 보였다.


해결방법

이를 해결하기 위해서는 아래와 같이 했다.


1. CURLOPT_FOLLOWLOCATION 를 사용하지 않도록 0L 로 libcurl을 설정한다.

2. 요청하려는 URL로의 curl_easy_perform의 반환값이 CURLE_OK 인 경우

 2-1. curl_easy_getinfo(curl, CURLINFO_REDIRECT_URL, &url) 

 2-2. url 이 존재하는 경우

  2-2-1. redirection url이므로 다시 curl_easy_perform을 수행한다.



기본값

0L (비활성화)


프로토콜

HTTP(S)





예제 코드

CURL *curl = curl_easy_init();
if(curl) {
  curl_easy_setopt(curl, CURLOPT_URL, "http://example.com");
 
  /* example.com is redirected, so we tell libcurl to follow redirection */
  curl_easy_setopt(curl, CURLOPT_FOLLOWLOCATION, 1L);
 
  curl_easy_perform(curl);
}


사용가능성

HTTP와 사용가능하다.


반환값

HTTP가 지원되면 CURLE_OK를 반환하고 그렇지 않으면 CURLE_UNKNOWN_OPTION을 반환한다.


같이 보기

CURLOPT_REDIR_PROTOCOLS

CURLOPT_PROTOCOLS

CURLOPT_POSTREDIR

CURLINFO_REDIRECT_URL

CURLINFO_REDIRECT_COUNT

  1. cURL(/kɝl/ 또는 /kə:l/)은 다양한 통신 프로토콜을 이용하여 데이터를 전송하기 위한 라이브러리와 명령 줄 도구를 제공하는 컴퓨터 소프트웨어 프로젝트이다. 이 cURL 프로젝트는 libcurl와 cURL이라는 2개의 제품을 만든다. [본문으로]