Recuperar Dados da Internet com libcURL

por Fabio A. Mazzarino

Apesar de não ser muito comum utilizar linguagem C/C++ para a recuperação de dados na internet, às vezes é necessário, seja por questões de performance, de arquitetura, ou até mesmo requisitos de projeto. E nestes casos libcURL é a principal biblioteca para acesso a internet.

No exemplo de hoje será utilizado código em linguagem C, uma vez que devido a natureza da libcURL a conversão para C++ é relativamente simples.

Easy ou Multi

A libcURL tem duas interfaces: easy e multi. A primeira é uma interface síncrona, simples e de fácil utilização, principalmente para transferëncia de arquivos. Já a interface multi é uma interface assíncrona, permitindo múltiplas transferências de dados uma única thread.

Como este post pretende apenas uma introdução o exemplo irá utilizar a interface easy.

Inicializando o libcURL

Para inicializar a biblioteca é necessário utilizar a função curl_global_init():

CURLcode curl_global_init(long flags);

São diversas as flags disponíveis. Para o exemplo vamos utilizar CURL_GLOBAL_ALL, que procura inicializar a maior quantidade de flags possível.

Para facilitar a utilização a libcURL se vale de uma estrutura de dados: CURL. Para criar um CURL novo basta utilizar a função curl_easy_init():

CURL *curl_easy_init();

O retorno da função é uma estrutura CURL. É recomendo criar uma estrutura CURL para cada conjunto de configurações.

Configurando libcURL

A configuração do libcURL define como será executada a recuperação de dados, e como serão tratados os dados. A função curl_easy_setopt():

CURLcode curl_easy_setopt(CURL *handle, CURLoption option, parameter);

O primeiro parâmetro é a estrutura CURL obtida da função curl_easy_init(). O segundo é qual opção será configurada, e o terceiro depende de qual opção está sendo configurada.

Para este exemplo serão utilizados as seguintes opções:

  • CURLOPT_WRITEFUNCTION – define a função de callback que faz o tratamento dos dados recuperados;
  • CURLOPT_WRITEDATA – define o ponteiro para uma variável que será passado para a função de callback;
  • CURLOPT_USERAGENT – define qual será a identificação de agente ao no cabeçalho HTTP.

Existem diversas outras configurações, é possível verificar todas na documentação da função curl_easy_setopt().

A Função de Callback

Neste exemplo será utilizado CURLOPT_WRITEFUNCTION para recuperar os dados. A assinatura da função é a seguinte:

size_t write_callback(char *ptr, size_t size, size_t nmemb, void *userdata);

Começando a lista de parâmetros um ponteiro para uma parcela dos dados recebidos. O segundo é sempre igual a um, e o terceiro a quantidade de bytes apontados por ptr, em algumas plataformas o tamanho total em bytes é dado pelo produto entre size e nmemb. Por último um ponteiro para uma estrutura de dados fornecida no momento da configuração da função de callback.

Nesta função tem um ponto de atenção, pode acontecer dos dados virem quebrados em várias chamadas, por isso é necessário gerenciar as diversas chamadas em um único buffer de recepção. O código da função vai ficar assim:

static size_t write_callback(void *ptr, size_t size, size_t nmemb, void *user) {
    /* convert input ptr into string */
    size_t inputsize = size * nmemb;
    char *input = (char*)malloc(inputsize + 1);
    memcpy(input, ptr, inputsize);
    input[inputsize] = '\x0';

    /* retrieve user buffer */
    char *buffer = *(char**)user;

    /* resize user buffer */
    size_t buffersize = strlen(buffer);
    char* newbuffer = realloc(buffer, buffersize + inputsize + 1);
    if (!newbuffer)
        return 0;
    buffer = newbuffer;
	
    /* concat input into user buffer */
    strcat(buffer, input);

    /* update user buffer pointer */
    *(char**)user = buffer;
    return inputsize;
}

A Solução Completa

Para exemplificar a utilização do libcURL um programa que aceita de entrada um domínio e recupera o arquivo robots.txt quando disponível. Segue o código:




#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <curl/curl.h>

#define LABCPP_CURL_URLPROTOCOL	"https://"
#define LABCPP_CURL_URLFILE	"/robots.txt"
#define	LABCPP_CURL_USERAGENT	"labcpp-agent/1.0"

void usage(char *binary, char *msg) {
    fprintf(stderr, "%s\n%s <URL>\n\tRetrieve robots.txt from specified domain\n\tExample: %s http://cnn.com\n\n", msg, binary, binary);
}



static size_t write_callback(void *ptr, size_t size, size_t nmemb, void *user) {
    /* convert input ptr into string */
    size_t inputsize = size * nmemb;
    char *input = (char*)malloc(inputsize + 1);
    memcpy(input, ptr, inputsize);
    input[inputsize] = '\x0';

    /* retrieve user buffer */
    char *buffer = *(char**)user;

    /* resize user buffer */
    size_t buffersize = strlen(buffer);
    char* newbuffer = realloc(buffer, buffersize + inputsize + 1);
    if (!newbuffer)
        return 0;
    buffer = newbuffer;
	
    /* concat input into user buffer */
    strcat(buffer, input);

    /* update user buffer pointer */
    *(char**)user = buffer;
    return inputsize;
}

int main(int argc, char *argv[]) {
    int exitcode = 0;
    /* command line argument */
    if (argc < 2) { 
        usage(argv[0], "Insufficient arguments");
        exitcode = 1;
        goto exit_0;
    }

    /* build robots.txt URL */
    char *url = (char*)malloc(strlen(argv[1]) * sizeof(char) + 32);
    strcpy(url, LABCPP_CURL_URLPROTOCOL);
    strcat(url, argv[1]);
    strcat(url, LABCPP_CURL_URLFILE);

    /* init & config cURL */
    CURL *curl = NULL;
    curl_global_init(CURL_GLOBAL_ALL);
    curl = curl_easy_init();
    if (!curl) { 
        fprintf(stderr, "Cannot find lib cURL\n");
        exitcode = 1;
        goto exit_1;
    } 

    char *buffer = malloc(1);
    if (!buffer) {
        fprintf(stderr, "Insufficient memory\n");
        exitcode = 1;
        goto exit_2;
    }
    *buffer = '\x0';

    curl_easy_setopt(curl, CURLOPT_URL, url);
    curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, write_callback);
    curl_easy_setopt(curl, CURLOPT_WRITEDATA, &buffer);
    curl_easy_setopt(curl, CURLOPT_USERAGENT, LABCPP_CURL_USERAGENT);

    /* retrieve URL content */
    CURLcode ans = curl_easy_perform(curl);

    /* retrieve HTTP response */
    long httpcode = 200;
    curl_easy_getinfo (curl, CURLINFO_RESPONSE_CODE, &httpcode);

    if (httpcode != 200) {
        /* not success */
        fprintf(stderr, "Cannot retrieve %s: HTTP %ld\n", url, httpcode);
        exitcode = 1;
    } else {
        /* print retrieved buffer */
        printf("%s\n", buffer);
        exitcode = 0;
    }

    /* tidy it up */
    free(buffer);
exit_2:
    free(url);
exit_1:
    curl_easy_cleanup(curl);
exit_0:
    exit(1);
}

A compilação precisa, necessariamente, incluir a opção de linkagem da biblioteca libcURL, dessa forma, considerando o arquivo fonte curl.c e o binário de saída curl:

$ gcc -lcurl -o curl curl.c

E a execução:

$ ./curl wordpress.org

libcURL na Prática

Na prática é muito raro utilizar C/C++ para recuperar dados da internet. Normalmente são utilizados scripts para recuperação dos dados, e somente depois o tratamento dos dados em C/C++. Mas sempre é possível existirem premissas de projetos, ou limitações de plataforma, fazendo com que seja necessário utilizar C/C++. Nestes casos: libcURL é a opção mais popular.