Как правильно написать функцию-оболочку для выполнения HTTP Get-запроса с использованием curl?

#c #curl

#c #curl

Вопрос:

Я пытаюсь написать функцию-оболочку для выполнения http get-запроса с использованием lib curl. Однако я не могу понять, как вернуть заголовки и тело ответа, используя curl_easy_setopt функции с функциями обратного вызова.

Вот мой код моей функции-оболочки.

 
typedef struct {
  char* key;
  char* value;
} http_header_t;

typedef struct {
  int status;
  char *body;
  http_header_t headers[MAX_HEADERS];
} http_response_t;

size_t http_response_write(void *ptr, size_t size, size_t nmemb, void* userdata) {

  return size * nmemb;
}

size_t header_callback(char *buffer, size_t size, size_t nitems, void *userdata) {
  http_response_t *response = (http_response_t*)userdata;

  printf("buf: %sn", buffer);
  return nitems * size;
}

/**
 * @brief HTTP GET request
 * 
 * @param response The response buffer
 * @param url The URL to request
 */
void http_get(http_response_t *response, const char *url) {
  CURL *curl;
  CURLcode res;

  curl = curl_easy_init();
  if (curl) {
    curl_easy_setopt(curl, CURLOPT_URL, url);
    curl_easy_setopt(curl, CURLOPT_WRITEHEADER, header_callback);
    curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, http_response_write);
    curl_easy_setopt(curl, CURLOPT_WRITEDATA, response);
    curl_easy_setopt(curl, CURLOPT_FOLLOWLOCATION, 1L);
    curl_easy_setopt(curl, CURLOPT_SSL_VERIFYPEER, 0L);
    curl_easy_setopt(curl, CURLOPT_SSL_VERIFYHOST, 0L);
    curl_easy_setopt(curl, CURLOPT_USERAGENT, "libcurl-agent/1.0");
    res = curl_easy_perform(curl);
    curl_easy_cleanup(curl);
  }
}
 

Я не понимаю, что означает параметр userstata в функции обратного CUROPT_WRITEHEADER вызова и другие параметры. Вам нужно передавать вашу структуру таким образом?

 size_t header_callback(char *buffer, size_t size, size_t nitems, void *userdata) {
  http_response_t *response = (http_response_t) userdata;
  response->headers = buffer; // lets pretend that the headers key type in my struct is a char
  return (int) response; // return the pointer address of the response
}
 

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

Ответ №1:

Я не понимаю, что означает параметр userstata в функции обратного вызова CUROPT_WRITEHEADER и другие параметры.

CUROPT_WRITEHEADER не устанавливает функцию обратного вызова; CURLOPT_HEADERFUNCTION делает это, чтобы функция получала данные заголовка (вместо этого вы можете установить CURLOPT_HEADER , что заставляет CURLOPT_WRITEFUNCTION обратный вызов получать все данные, заголовки и тело, но это более запутанно).

CUROPT_WRITEHEADER по сути, это псевдоним для CURLOPT_HEADERDATA ; оба задают данные для передачи в качестве userdata параметра CURLOPT_HEADERFUNCTION (curl не устанавливает это сам; он передаст NULL, если вы его не установите). Это аналогично CURLOPT_WRITEDATA for CURLOPT_WRITEFUNCTION . Используйте его так, как CURLOPT_WRITEDATA используется в примере кода:

 void http_get(http_response_t *response, const char *url) {
    //...
        curl_easy_setopt(curl, CURLOPT_HEADERFUNCTION, header_callback);
        curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, http_response_write);
        curl_easy_setopt(curl, CURLOPT_HEADERDATA, response);
        curl_easy_setopt(curl, CURLOPT_WRITEDATA, response);

 

Каждый заголовок передается отдельно функции обратного вызова заголовка, поэтому вам нужно где-то отслеживать количество заголовков ( http_response_t это естественное место).

 typedef struct {
    //...
    size_t nHeaders;
} http_response_t;
 

Что касается обработки заголовков, у вас есть это прямо там header_callback , где вы приводите userdata к a http_response_t . Присвоение buffer функции response->headers не совсем правильное, но я предполагаю, что вы вставили это в качестве заполнителя на основе комментария «давайте притворимся». Возвращаемое значение обоих обратных вызовов должно быть количеством обработанных байтов.

Поскольку buffer curl принадлежит и управляется curl, при анализе функции обратного buffer вызова ей необходимо будет создать поля заголовка и скопировать данные заголовка. Это может быть передано некоторым другим функциям, которые вы создаете для этой цели (например http_header_set_key , and http_header_set_value ), которые взяли бы на себя эту ответственность. Еще лучше, передайте ответственность за синтаксический анализ строки заголовка другой функции (которая сама будет вызывать функции, ответственные за создание и настройку полей):

 // In the "http_response" library
// return non-0 if error
int http_header_set_key(char *buffer, size_t nChars, http_header_t *header);
// return non-0 if error
int http_header_set_value(char *buffer, size_t nChars, http_header_t *header);
// return bytes processed (should be == bufSize)
size_t http_header_parse_line(char *buffer, size_t bufSize, http_header_t *header);

//...

// In the client library

size_t header_callback(char *buffer, size_t size, size_t nItems, void *userdata) {
    http_response_t *response = (http_response_t) userdata;
    int iHdr = response->nHeaders;
    if (iHdr >= MAX_HEADERS - 1) {
        // Error: ran out of space in response to store header; signal this
        // condition somehow.
        // ...
        // If signaling doesn't interrupt this function, must stop here
        return 0;
    }
    int nProcessed = http_header_parse_line(buffer, size * nItems, response->headers   iHdr);
    if (nProcessed) {
        // A header was added to response->headers.
          response->nHeaders;
    }
    return nProcessed;
}
 

Обратите внимание, что buffer это не завершается нулем, поэтому его размер передается http_header_parse_line . Если вместо этого вы хотите http_header_parse_line работать со строкой C, вам нужно сначала скопировать и завершить строку заголовка null buffer ; это может http_header_parse_line немного упростить, но усложнит header_callback и будет менее эффективным.

Комментарии:

1. Какую опцию мне нужно использовать, чтобы я мог установить обратный вызов для http_header_init ?

2. @ViniDalvino: http_header_init это был не обратный вызов, а предлагаемая функция, которую вы бы написали и вызвали из обратных вызовов. Надеюсь, обновление более четкое (и более конкретное в отношении дизайна).