c++curllibcurlhttp2multiplexing

libcurl C++: retrieving HTTP response code when multiplexing


Goal:

To only print the contents of a multiplexed response if a certain HTTP status code such as 200 is detected. This requires reading in the header and extracting the HTTP code whenever the callback function receives a response.

Since responses received by multiplexing can be returned to the program in any order via a callback method, finding this status code must be done asynchronously / in a non-blocking manner.

The program seen further below is based on the libcurl tutorial found here. A question containing information about HTTP multiplexing can be found here.

Problem:

The program successfully sends requests asynchronously, receives, and then prints responses, but I am currently unsure of where within the program to use curl_easy_getinfo(curl, CURLINFO_RESPONSE_CODE, &response_code), or if this is even the correct approach for a multi interface program when looking for the HTTP status code.

Current output:

Program prints to stdout the complete header and payload, but when using the aforementioned CURLINFO_RESPONSE_CODE the program behaves synchronously (i.e. every time any information from headers to payload is received, the program will print the response code (whereas it should only be printed if and when it is detected by the callback function dump(...)).

Code:

#include <stdlib.h>
#include <errno.h>  
#include <iostream>
#include <string>

/* somewhat unix-specific */
#include <sys/time.h>
#include <unistd.h>
 
/* curl stuff */
#include <curl/curl.h>
#include <curl/mprintf.h>
 
#ifndef CURLPIPE_MULTIPLEX
#define CURLPIPE_MULTIPLEX 0
#endif

struct transfer {
  CURL *easy;
  unsigned int num;
  FILE *out;
};

#define NUM_HANDLES 1000

static void dump(const char *text, int num, unsigned char *ptr, size_t size,
          char nohex)
{
    // Print the response
    for (int i = 0; i < size; i++) {
        std::cout << ptr[i];
    }
}
 
static int my_trace(CURL *handle, curl_infotype type,
             char *data, size_t size,
             void *userp)
{
  const char *text;
  struct transfer *t = (struct transfer *)userp;
  unsigned int num = t->num;
  (void)handle; /* prevent compiler warning */
 
  switch(type) {
  default: /* in case a new one is introduced to shock us */
    return 0;
 
  case CURLINFO_SSL_DATA_OUT:
    text = "=> Send SSL data";
    break;
  case CURLINFO_HEADER_IN:
    text = "<= Recv header";
    break;
  case CURLINFO_DATA_IN:
    text = "<= Recv data";
    break;
  case CURLINFO_SSL_DATA_IN:
    text = "<= Recv SSL data";
    break;
  }

  dump(text, num, (unsigned char *)data, size, 1);
  return 0;
}
 
static void setup(struct transfer *t, int num)
{
  char filename[128];
  CURL *hnd;
 
  hnd = t->easy = curl_easy_init();
 
  curl_msnprintf(filename, 128, "dl-%d", num);
 
  t->out = fopen(filename, "wb");
  if(!t->out) {
    std::cout << "ERROR: could not open file for writing" << std::endl;
    exit(1);
  }
 
  /* write to this file */
  curl_easy_setopt(hnd, CURLOPT_WRITEDATA, t->out);
 
  /* set the same URL */
  curl_easy_setopt(hnd, CURLOPT_URL, "https://sometesturl.xyz");
 
  /* please be verbose */
  curl_easy_setopt(hnd, CURLOPT_VERBOSE, 1L);
  curl_easy_setopt(hnd, CURLOPT_DEBUGFUNCTION, my_trace);
  curl_easy_setopt(hnd, CURLOPT_DEBUGDATA, t);
 
  /* HTTP/2 please */
  curl_easy_setopt(hnd, CURLOPT_HTTP_VERSION, CURL_HTTP_VERSION_2_0);

  /* we use a self-signed test server, skip verification during debugging */
  curl_easy_setopt(hnd, CURLOPT_SSL_VERIFYPEER, 0L);
  curl_easy_setopt(hnd, CURLOPT_SSL_VERIFYHOST, 0L);
 
#if (CURLPIPE_MULTIPLEX > 0)
  /* wait for pipe connection to confirm */
  curl_easy_setopt(hnd, CURLOPT_PIPEWAIT, 1L);
#endif

}
 
/*
 * Download many transfers over HTTP/2, using the same connection!
 */
int main(int argc, char **argv)
{

  struct transfer trans[NUM_HANDLES];
  CURLM *multi_handle;
  int i;
  int still_running = 0; /* keep number of running handles */
  int num_transfers;
  if(argc > 1) {
    /* if given a number, do that many transfers */
    num_transfers = atoi(argv[1]);
    if((num_transfers < 1) || (num_transfers > NUM_HANDLES))
      num_transfers = 3; /* a suitable low default */
  }
  else
    num_transfers = 3; /* suitable default */
 
  /* init a multi stack */
  multi_handle = curl_multi_init();
 
  for(i = 0; i < num_transfers; i++) {
    setup(&trans[i], i);
 
    /* add the individual transfer */
    curl_multi_add_handle(multi_handle, trans[i].easy);
  }
 
  curl_multi_setopt(multi_handle, CURLMOPT_PIPELINING, CURLPIPE_MULTIPLEX);
 
  do {
    CURLMcode mc = curl_multi_perform(multi_handle, &still_running);
 
    if(still_running)
      /* wait for activity, timeout or "nothing" */
      mc = curl_multi_poll(multi_handle, NULL, 0, 1000, NULL);
 
    if(mc)
      break;

  } while(still_running);

  // This behaves in a blocking / synchronous manner...
  // Not sure if this is the correct place to extract the status code?
  for (int i = 0; i < num_transfers; i++) {
      long response_code; // test variable
      curl_easy_getinfo(trans[i].easy, CURLINFO_RESPONSE_CODE, &response_code);
  }

  for(i = 0; i < num_transfers; i++) {
    curl_multi_remove_handle(multi_handle, trans[i].easy);
    curl_easy_cleanup(trans[i].easy);
  }
 
  curl_multi_cleanup(multi_handle);  
  return 0;
}

Summary question:

1. When using multiplexing whereby responses can be received in any order, how can the HTTP status code be extracted with libcurl such that it can be found asynchronously like the responses are currently being received as?


Solution

  • Move the call to curl_easy_getinfo(CURLINFO_RESPONSE_CODE) inside of your main loop. Use curl_multi_info_read() inside the loop to detect when each request is complete before retrieving its response code. For example:

    do {
        CURLMcode mc = curl_multi_perform(multi_handle, &still_running);
     
        if (still_running) {
            /* wait for activity, timeout or "nothing" */
            mc = curl_multi_poll(multi_handle, NULL, 0, 1000, NULL);
        }
     
        if (mc)
            break;
    
        do {
            int queued;
            CURLMsg *msg = curl_multi_info_read(multi_handle, &queued);
            if ((msg) && (msg->msg == CURLMSG_DONE) && (msg->result == CURLE_OK)) {
                long response_code;
                curl_easy_getinfo(mg->easy_handle, CURLINFO_RESPONSE_CODE, &response_code);
                ...
            }
        }
        while (msg);
    }
    while (still_running);
    

    Use CURLOPT_HEADERFUNCTION/CURLOPT_WRITEFUNCTION callbacks to save the response headers/payload into your transfer struct as needed, don't just dump it to stdout in the CURLOPT_DEBUGFUNCTION callback. This way, you can print/save the data only if the final response code is what you are looking for.