CURL en C/CPP

Recientemente tuve que desarrollar en C++ un daemon que informe datos a una APIRest, para lo cual utilice CURL.

El problema apareció que como el daemon tenia que hacer varias cosas secuencialmente no podía permitirme esperar a que la API me responda, por lo cual tenia comunicarme al APIRest y asincromicamente verificar luego si se pudo comunicar con el API. En C++ esto lo pude solucionar con curl_multi, en el cual se encolan las peticiones curl y luego podemos verificar si las mismas fueron procesadas.

La página principal de consulta donde tenemos muchos ejemplos es curl.se.

Lo primero que necesitamos tener son algunas variables globales para poder utilizar en varias funciones

// multi curl
CURLM *multi_handle;  /*Cola en donde se van a tener todas las peticiones*/
int still_running = 0;       /* mantiene el número de peticiones corriendo */
int msgs_left = -1;
struct curl_slist *headers = NULL; /* http headers a enviar en las peticiones*/

Luego tenemos que inicializar estas variables

/* init a multi stack */
curl_global_init(CURL_GLOBAL_ALL);
multi_handle = curl_multi_init();
/* set content type */
headers = curl_slist_append(headers, "Accept: application/json");
headers = curl_slist_append(headers, "Content-Type: application/json");

Una vez inicializados podemos tener la función que se va a encargar de agregar nuevos trabajos a la cola

int ws_genericJsonRequest(json_t *json_obj_out, json_t **json_obj_in) {

    CURL *ch; /* curl handle */
    char postJson[300];

     /* init curl handle */
     if ((ch = curl_easy_init()) == NULL) {
          /* return error */
           return -1;
     }

     /* set curl options */
    curl_easy_setopt(ch, CURLOPT_CUSTOMREQUEST, "POST");
    curl_easy_setopt(ch, CURLOPT_HTTPHEADER, headers);
    sprintf(postJson,"%s \n", json_dumps(json_obj_out,0));
    
    curl_easy_setopt(ch, CURLOPT_COPYPOSTFIELDS, postJson );
    curl_easy_setopt(ch, CURLOPT_POSTFIELDSIZE, -1L);
    // si queremos ver en forma verbose al curl se debe activar al siguiente opción
    // curl_easy_setopt(ch, CURLOPT_VERBOSE, 1L);
    
    /* set url to fetch */ 
    curl_easy_setopt(ch, CURLOPT_URL,<URL DEL WS>); 
    curl_easy_setopt(ch, CURLOPT_PRIVATE, <URL DEL WS>);
 
   /* set default user agent */ 
   curl_easy_setopt(ch, CURLOPT_USERAGENT, "libcurl-agent/1.0");
   curl_easy_setopt(ch, CURLOPT_WRITEFUNCTION, write_cb);

   /* add the individual transfers */ 
  curl_multi_add_handle(multi_handle, ch);
  return 0;
}/pre>

Si necesitamos procesar los datos que recibimos hay que completar la siguiente función de callback

static size_t write_cb(char *data, size_t n, size_t l, void *userp)
{
    /* take care of the data here, ignored in this example */
    (void)data;
    (void)userp;
    return n*l;
}

Luego debemos ir verificando constantemente si fueron procesadas o no las peticiones al APIRest


int ProcessMulti(void){ 
  /* we start some action by calling perform right away */ 
  curl_multi_perform(multi_handle, &still_running); 
  if(still_running){ 
    // aca tenemos información de que hay pendientes
  }else{ 
    // aca tenemos información de las terminadas
    if( msg = curl_multi_info_read(multi_handle, &msgs_left)){ 
        if(msg->msg == CURLMSG_DONE) { 
             char *url; 
             CURL *e = msg->easy_handle; 
             curl_easy_getinfo(msg->easy_handle, CURLINFO_PRIVATE, &url); 
             printf("R: %d - %s <%s>\n", msg->data.result, curl_easy_strerror(msg->data.result), url); 
             curl_multi_remove_handle(multi_handle, e); 
             curl_easy_cleanup(e); 
         } else { 
             printf("E: CURLMsg (%d)\n", msg->msg); 
         } 
      } 
  } 
}

Finalmente cerramos los elementos inicializados

curl_multi_cleanup(multi_handle);
curl_global_cleanup();
curl_slist_free_all(headers);