Making POST request and receiving JSON response in C++

5k views Asked by At

I have an application where I send base64 data of images from my OpenCV C++ code to an AWS URL which will find the age and gender of the faces in the images and send the results back in a JSON format.

I use cURL library to send the data through POST request. This part works fine, I am able to send the data and it the image is getting saved correctly every frame.

For reading the JSON data, I use the jsoncpp library to parse the JSON data.

I want to integrate both the parts in one function. This works as well when I send a single image. I have an issue when I send multiple images in a loop and receive their JSON responses. I keep receiving the first JSON response every time. I have tested this by generating random numbers and sending that as a JSON response each time on the server side and when I receive it in my C++ code, I only get the first JSON response, ie the first number, every time. But when I try receiving JSON data without sending POST data and just send a GET request, I receive the JSON data correctly every time. My code for both the parts integrated is here:

#include <iostream>
#include <string>
#include <memory>
#include <sstream>
#include <curl/curl.h>
#include <jsoncpp/json/json.h>
#include <stdio.h>
#include "opencv2/opencv.hpp"
#include "base64.h"

using namespace std;
using namespace cv;

/*Converts Mat to base64 string*/
string image_to_base64(Mat image)
{
    string base64Output;
    vector<uchar> vec_frame;

    vector<int> vecCompression_params;

    vecCompression_params.push_back(CV_IMWRITE_JPEG_QUALITY);
    vecCompression_params.push_back(90);

    imencode(".jpg", image, vec_frame, vecCompression_params);

    uchar *enc_msg = new uchar[vec_frame.size()];

    for(int i=0; i < vec_frame.size(); i++) 
        enc_msg[i] = vec_frame[i];

    base64Output = base64_encode(enc_msg, vec_frame.size());

    std::replace(base64Output.begin(), base64Output.end(), '+', '-'); 
    std::replace(base64Output.begin(), base64Output.end(), '/', '_');
    std::replace(base64Output.begin(), base64Output.end(), '=', '*');

    return base64Output;
}

/**Sends the base64 string to AWS**/
struct WriteThis 
{
  const char *readptr;

  long long sizeleft;
};

static size_t read_callback(void *ptr, size_t size, size_t nmemb, void *userp)
{
  struct WriteThis *pooh = (struct WriteThis *)userp;

  if(size*nmemb < 1)
    return 0;

  if(pooh->sizeleft) 
  {
    *(char *)ptr = pooh->readptr[0]; /* copy one single byte */ 
    pooh->readptr++;                 /* advance pointer */ 
    pooh->sizeleft--;                /* less data left */ 
    return 1;                        /* we return 1 byte at a time! */ 
  }

  return 0;                          /* no more data left to deliver */ 
}

string cURL_JSON_data;
size_t writeJSONURLCallback(char* buf, size_t size, size_t nmemb, void* up)//Callback function to store the URL's data
{ 
    for (int c = 0; c<size*nmemb; c++)
    {
        cURL_JSON_data.push_back(buf[c]);
    }
    return size*nmemb; 
}
string jsonTest;

tuple<string, string> send_base64(string base64_string)
{
    CURL *curl;
    CURLcode res;

    long long int httpCode(0);
    unique_ptr<std::string> httpData(new std::string());
    string age_info, gender_info;

    struct WriteThis pooh;
    //cout<<"base64_string.length(): "<<base64_string.length();

    char data[base64_string.length()];
    strcpy(data, base64_string.c_str());

    pooh.readptr = data;
    pooh.sizeleft = (long long)strlen(data);
    //cout<<"strlen(data): "<<strlen(data)<<endl;

    /* In windows, this will init the winsock stuff */ 
    res = curl_global_init(CURL_GLOBAL_DEFAULT);
    /* Check for errors */ 
    if(res != CURLE_OK) 
    {
        fprintf(stderr, "curl_global_init() failed: %s\n",
        curl_easy_strerror(res));
        //return 1;
    }

    /* get a curl handle */ 
    curl = curl_easy_init();
    if(curl) 
    {
        /* First set the URL that is about to receive our POST. */ 
        curl_easy_setopt(curl, CURLOPT_URL, "MYURL");

        /* Now specify we want to POST data */ 
        curl_easy_setopt(curl, CURLOPT_POST, 1L);

        /* we want to use our own read function */ 
        curl_easy_setopt(curl, CURLOPT_READFUNCTION, read_callback);

        /* pointer to pass to our read function */ 
        curl_easy_setopt(curl, CURLOPT_READDATA, &pooh);

        curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, &writeJSONURLCallback);//writes the conents of url

        curl_easy_setopt(curl, CURLOPT_VERBOSE, 1L); //output cURL's progress

        curl_easy_setopt(curl, CURLOPT_WRITEDATA, httpData.get());

        /* get verbose debug output please */ 
        curl_easy_setopt(curl, CURLOPT_VERBOSE, 1L);

        /*
          If you use POST to a HTTP 1.1 server, you can send data without knowing
          the size before starting the POST if you use chunked encoding. You
          enable this by adding a header like "Transfer-Encoding: chunked" with
          CURLOPT_HTTPHEADER. With HTTP 1.0 or without chunked transfer, you must
          specify the size in the request.
        */ 
        #ifdef USE_CHUNKED
        {
            struct curl_slist *chunk = NULL;

            chunk = curl_slist_append(chunk, "Transfer-Encoding: chunked");
            res = curl_easy_setopt(curl, CURLOPT_HTTPHEADER, chunk);
            /* use curl_slist_free_all() after the *perform() call to free this
            list again */ 
        }
        #else
        /* Set the expected POST size. If you want to POST large amounts of data,
           consider CURLOPT_POSTFIELDSIZE_LARGE */ 
        curl_easy_setopt(curl, CURLOPT_POSTFIELDSIZE_LARGE, (pooh.sizeleft));
        #endif

        #ifdef DISABLE_EXPECT
        /*
          Using POST with HTTP 1.1 implies the use of a "Expect: 100-continue"
          header.  You can disable this header with CURLOPT_HTTPHEADER as usual.
          NOTE: if you want chunked transfer too, you need to combine these two
          since you can only set one list of headers with CURLOPT_HTTPHEADER. */ 

        /* A less good option would be to enforce HTTP 1.0, but that might also
           have other implications. */ 
        {
          struct curl_slist *chunk = NULL;

          chunk = curl_slist_append(chunk, "Expect:");
          res = curl_easy_setopt(curl, CURLOPT_HTTPHEADER, chunk);
          /* use curl_slist_free_all() after the *perform() call to free this
             list again */ 
        }
        #endif

        /* Perform the request, res will get the return code */ 
        res = curl_easy_perform(curl);
        /* Check for errors */ 
        if(res != CURLE_OK)
            fprintf(stderr, "curl_easy_perform() failed: %s\n",
                curl_easy_strerror(res));

        /* always cleanup */ 
        curl_easy_getinfo(curl, CURLINFO_RESPONSE_CODE, &httpCode);
        //cout<<"Testing_1"<<endl;
        curl_easy_cleanup(curl);
    }
    curl_global_cleanup();

    Json::Reader jsonReader;
    Json::Value jsonData;

    if (jsonReader.parse(cURL_JSON_data, jsonData))
    {   
        cout << "Successfully parsed JSON data" << std::endl;
        cout << "\nJSON data received:" << std::endl;
        jsonTest = jsonData.toStyledString();
        cout << jsonData.toStyledString() << std::endl;

        age_info = jsonData["Age"].asString();
        gender_info = jsonData["Gender"].asString();

    }
    else
    {
        cout<<"Could not parse JSON data"<<endl;
    }

    jsonData.clear();
    return make_tuple(age_info, gender_info);
}

int main()
{
    Mat frame;

    string base64_data,age, gender;
    int count = 0;

    VideoCapture cap(1);
    while(1)
    {
        cap>>frame;
        count++;
        resize(frame, frame, Size(frame.cols / 2, frame.rows / 2));
        if(count % 10 ==0)//Sending the data every 10 frames
        {
            base64_data = image_to_base64(frame);

            tie(age, gender) = send_base64(base64_data);    

            cout<<"age: "<<age<<", gender: "<<gender<<endl;
        }
        imshow("window", frame);
        waitKey(30);    
    }   
    return 0;
}

And the code I use for receiving JSON with only GET request is here:

#include <iostream>
#include <stdlib.h> 
#include <string>
#include <memory>
#include <curl/curl.h>
#include <jsoncpp/json/json.h>

using namespace std;

string cURLdata;

size_t writeURLCallback(char* buf, size_t size, size_t nmemb, void* up)
{ 
    for (int c = 0; c<size*nmemb; c++)
    {
        cURLdata.push_back(buf[c]);
    }
    return size*nmemb; 
}

int main()
{
    int count = 0;
    while(1){   
        CURL* curl; //curl object

        curl_global_init(CURL_GLOBAL_ALL); 
        curl = curl_easy_init();

        curl_easy_setopt(curl, CURLOPT_URL, "MYURL");
        curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, &writeURLCallback);//writes the conents of url
        curl_easy_setopt(curl, CURLOPT_VERBOSE, 1L); //output cURL's progress

        curl_easy_perform(curl);
        curl_easy_cleanup(curl);
        curl_global_cleanup();

        Json::Reader jsonReader;
        Json::Value jsonData;

        bool parsedSuccess = jsonReader.parse(cURLdata,jsonData,false);

        if (parsedSuccess)
        {
            std::cout << "Successfully parsed JSON data" << std::endl;
            std::cout << "\nJSON data received:" << std::endl;
            cout << jsonData.toStyledString() << std::endl;

        }
        else
        {
           std::cout << "Could not parse HTTP data as JSON" << std::endl;
        }
        jsonData.clear();
        cURLdata.clear();
    }
    return 0;
}

Can someone help me point out what I'm doing wrong?

0

There are 0 answers