Не самый тривиальный пример использования libcurl

17 марта 2016

Библиотека cURL, написанная на языке C, реализует ряд сетевых протоколов, включая HTTP, FTP, SMTP, POP3, Telnet, и другие. Если вам нужно поговорить о чем-то с сервером, где-то в 90% случаев вы можете сделать это, используя cURL. В рамках сей заметки мы разберемся, как при помощи libcurl написать не самый тривиальный HTTP-клиент.

Рассмотрим для примера задачу заливки картинок на ImageShack. Эту задачу мы уже многократно решали. В частности, решение на Python можно найти в заметке Как я выбирал скриптовый язык и остановился на Python. Приступим!

Подключаем заголовочный файл:

#include <curl/curl.h>

Инициализируем библиотеку cURL:

if(curl_global_init(CURL_GLOBAL_ALL) != 0)
{
  fprintf(stderr, "curl_global_init() failed\n");
  return 1;  
}

Создаем «объект» CURL, который, собственно, и будет далее посылать запросы:

CURL* curl = curl_easy_init();
if(curl == NULL)
{
  fprintf(stderr, "curl_easy_init() failed\n");
  return 1;
}

// потом его нужно освободить вот так:
// curl_easy_cleanup(curl);

Указываем HTTP-заголовки:

struct curl_slist* curlHeaders = NULL;
curlHeaders = curl_slist_append(curlHeaders, USER_AGENT_STRING);

if(curlHeaders == NULL)
{
  fprintf(stderr, "curl_slist_append() failed\n");
  curl_easy_cleanup(curl);
  return 1;
}

// потом их нужно освободить вот так:
// curl_slist_free_all(curlHeaders);

Заполняем форму:

struct curl_httppost* formFirstItem = NULL;
struct curl_httppost* formLastItem = NULL;

curl_formadd(&formFirstItem,
  &formLastItem,
  CURLFORM_COPYNAME, "key",
  CURLFORM_COPYCONTENTS, "015EFMNVfe7f6f7e93cb4a7b0a41e19956ce59f8",
  CURLFORM_END);

// форма освобождается таким образом:
// curl_formfree(formFirstItem);

Чтобы в форме передать файл, говорим:

curl_formadd(&formFirstItem,
  &formLastItem,
  CURLFORM_COPYNAME, "Filedata",
  CURLFORM_FILE, fname,
  CURLFORM_END);

Связываем все это вместе — указываем URL, заголовки, форму, а также колбэк, в который прилетит ответ от сервера:

WriteFuncCtx ctx;

if(!writeFuncCtxInit(&ctx))
{
  // ...
  return 1;
}

curl_easy_setopt(curl, CURLOPT_URL, IMAGESHACK_UPLOAD_URL);
curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, writeCallback);
curl_easy_setopt(curl, CURLOPT_WRITEDATA, &ctx);
curl_easy_setopt(curl, CURLOPT_HTTPHEADER, curlHeaders);
curl_easy_setopt(curl, CURLOPT_HTTPPOST, formFirstItem);

… где writeCallback и WriteFuncCtx определены нами следующим образом:

typedef struct
{
    char* buff;
    size_t buffSize;
    size_t used;
} WriteFuncCtx;

bool
writeFuncCtxInit(WriteFuncCtx* ctx)
{
  // ...
}

bool
writeFuncCtxAppend(WriteFuncCtx* ctx, const char* data, size_t size)
{
  // ...
}

void
writeFuncCtxFree(WriteFuncCtx* ctx)
{
  // ...
}

size_t
writeCallback(char *ptr, size_t size, size_t nmemb, void *userdata)
{
  WriteFuncCtx* ctx = (WriteFuncCtx*)userdata;
  size_t totalSize = size*nmemb;

  if(writeFuncCtxAppend(ctx, ptr, totalSize))
    return totalSize;
  else
    return 0;
}

Другими словами, все, что делает writeCallback — это записывает ответ сервера в буфер переменного размера, тот самый WriteFuncCtx. Если место в буфере заканчивается, выделяется буфер в два раза большего размера, в него копируются все данные, а старый буфер освобождается. С тем же успехом мы могли бы использовать любой готовый контейнер, похожий на Vector.

Наконец, посылаем запрос:

CURLcode res = curl_easy_perform(curl);
if(res != CURLE_OK)
{
  // ...
  return 1;
}

В конец буфера записываем нулевой байт:

if(!writeFuncCtxAppend(&ctx, "", 1))
{
  // ...
  return 1;
}

После чего мы можем работать с ним, как с обычной строкой, найдя в ответе сервера URL загруженной картинки при помощи обыкновенного strstr.

Итак, мы научились отправлять формы, заливать файлы, проставлять HTTP заголовки — пожалуй, всему, что может потребоваться на практике при работе с HTTP. И все это на языке C. Согласитесь, это было совсем несложно!

Полную версию исходного кода вы найдете в этом репозитории. Более детальное описание API libcurl вы найдете либо здесь, либо в самом curl/curl.h. Любые дополнения и вопросы, как всегда, горячо приветствуются.

Метки: .


Вы можете прислать свой комментарий мне на почту, или воспользоваться комментариями в Telegram-группе.