ofxHttpUtils form.addFormField value format

Hi! I’m using @arturo’s ofxHttpUtils for tracking the usage of my app.

I’m sending POST requests to Segment.io using its Http API and it requires some properties to be set in a dictionary format like this.

"properties": {
    "name": "Something",
    "value": "nothing"
  }

So, with ofxHttpUtils I’m doing this

ofxHttpForm form;
form.action = "https://api.segment.io/v1/track";
form.method = OFX_HTTP_POST;     
form.addFormField("properties", "{ \"name\": \"Something\",\"value\": \"nothing\"}";
...

(I’m actually using ofxJson to format the properties value)

The problem I have is that my output looks like this
Note the quotes before and after the brackets

"properties": "{
        "name": "Something",
        "value": "nothing"
      }"

And this makes the request to be dismissed.

How can I avoid the quotes? I’been trying to digg into the addon’s code and Poco’s library with no success. Any simple solution?

Thanks in advance. Any help will be appreciated.

I end up using libcurl wrapped with ofThread.
The final class looks something like this:

vector<Track> analyticsController::queue;

void analyticsController::setup()
{
    trackInit();
    trackInit(); //just for testing...
    trackInit();
}

void analyticsController::update()
{
    if(queue.size() > 0)
        if(!isThreadRunning())
        {
            track(queue[0].eventName, queue[0].propertyName, queue[0].properties);
            queue.erase(queue.begin());
        }
}

void analyticsController::addTrack(string eventName, string propertyName , ofxJSONElement properties)
{
    struct Track newTrack;
    newTrack.eventName = eventName;
    newTrack.propertyName = propertyName;
    newTrack.properties = properties;
    
    queue.push_back(newTrack);
}

void analyticsController::track(string eventName, string propertyName , ofxJSONElement properties)
{
    ofxJSONElement data;
    data["userId"] ="UniqueUserId";
    data["event"] = eventName.c_str();
    if(propertyName!="" && properties!= ofxJSONElement::null)
        data[propertyName] = properties;
    
    curl_global_init(CURL_GLOBAL_ALL);
    handle = curl_easy_init();
    
    content = "";
    
    //verbose
    curl_easy_setopt(handle, CURLOPT_FAILONERROR, 1);
    curl_easy_setopt(handle, CURLOPT_NOPROGRESS, 1);
    curl_easy_setopt(handle, CURLOPT_VERBOSE, false);
    curl_easy_setopt(handle, CURLOPT_NOPROGRESS, 0);
    
    curl_easy_setopt(handle, CURLOPT_POST, 1);
    curl_easy_setopt(handle, CURLOPT_HTTPAUTH, CURLAUTH_BASIC);
    curl_easy_setopt(handle, CURLOPT_WRITEFUNCTION, writer);
    curl_easy_setopt(handle, CURLOPT_WRITEDATA, &content);
    curl_easy_setopt(handle, CURLOPT_HEADERFUNCTION, writer);
    curl_easy_setopt(handle, CURLOPT_HEADERDATA, &header);
    curl_easy_setopt(handle, CURLOPT_SSL_VERIFYPEER, false);
    curl_easy_setopt(handle, CURLOPT_URL, "https://api.segment.io/v1/track" );
    curl_easy_setopt(handle, CURLOPT_USERPWD, "user:password");
    curl_easy_setopt(handle, CURLOPT_TIMEOUT_MS, 2000);
    curl_easy_setopt(handle, CURLOPT_COPYPOSTFIELDS, jsonToFields(data).c_str());
  
     startThread(true, false);
}

void analyticsController::trackInit()
{
    ofxJSONElement  properties;
    properties["Model Name"] = "machineName";
    properties["OS"] = "something";
    properties["Processor Name"] = "intel";
    
    addTrack("Init", "properties", properties);
}

size_t analyticsController::writer(char *ptr, size_t size, size_t nmemb, string userdata) {
    
    //should lock this? can't lock an static function :/
    string header(static_cast<const char*>(ptr), size * nmemb);
    userdata += header;
    return size * nmemb;
}

string analyticsController::jsonToFields(ofxJSONElement json) //just 2 levels deep
{
    stringstream fields;
    
    for (const auto& key : json.getMemberNames())
        if(json[key].isObject())
            for (const auto& key2 : json[key].getMemberNames())
               fields << key.c_str() << "[" << key2 << "]=" << json[key][key2].asString() << "&";
        else
            fields << key.c_str() << "=" << json[key].asString() << "&";
    
    return fields.str();
}

void analyticsController::threadedFunction() {
    string error;
    while(isThreadRunning())
    {
        CURLcode ret = curl_easy_perform(handle);
        if(ret != CURLE_OK)
           error = curl_easy_strerror(ret);
        else
            error = "";
        stopThread();
    }
    
    onResponse(error);
}

void analyticsController::onResponse(string error)
{
    lock();
    curl_easy_cleanup(handle);
    unlock();
    ofxJSONElement response(content);
    cout << (response.toStyledString());
}

Aloha!

I was reading through their documentation as I just built something like this using a linode server and Python Flask/MongoDB. But Segment looks pretty neat with the integrated dashboards and added features.

You can also use it without the JSON. Look at the properties section and select CURL.

https://segment.com/docs/api/tracking/track/

Example:
curl https://api.segment.io/v1/track
-d “event=Product Added”
-d “properties[name]=Ron Livingston”
-d properties[industry]=Technology
-d properties[value]=5

You can also create a thread and do
string reply = ofSystem(“curl …”);
Thats what I do. Don’t have to link any libraries and it will return a string depending on the actual reply and any added things you want Curl to say back like the header and such. Pretty easy. But then again, I pretty much always use Macs so most likely different per OS.

Hi @ryanww,
I didn’t thought of using ofSystem(). Its way mor hassle free.
I’m only tragetting Mac for now so that was a pretty ideal solution.
Thanks!

Yea, its absolutely great. You don’t have to link anything and its just default with Mac OS X. And it works!

Depending on some of the arguments, it will return whatever you want along with the actual reply from the server if any. I’m using it a couple different ways. One to upload to my tracking system which is simply an HTTP post, another to upload videos to a clients server system with a special API, and another to upload to Wistia which is like an enterprise YouTube. For the tracking app, I just use the standard method of sending it just as I mentioned with a single reply after it sends. For both of the video uploads, I use a file to open the command with. It has own while loop that returns data while its uploading so that I can see upload percentages and file sizes and such. Wouldn’t be applicable in this scenario I suppose.

Also to throw in yet another option, ofxHTTP has a sophisticated client capable of POST requests etc. It also has a sophisticated server system that can easily handle POST requests.

Just check out the examples.

CB

I have taken a look at ofxHTTP many times. But I really never could figure out how to get it to push a post out to an external server just like the curl example I posted above. I always figured it was mostly an actual server not a client. Is there somewhere that has some documentation on more of the post side like this usage? I have looked at the examples but most are more server based which I haven’t needed yet. Seems like it has a ton of cool features at least.

1 Like

ofxHTTP is well developed on the server side as well as the client side.

For example, for synchronous calls you can replace the GetRequest with a PostRequest

https://github.com/bakercp/ofxHTTP/blob/master/example_basic_client/src/ofApp.cpp#L38

Then you can fill in all of the parameters etc in the PostRequest – here is the constructor:

If you want to do a multi-part post (to upload a chunk of json) you can save the json in a buffer or add a json file as a form part here (note you can also set the content type as "application/json"):

If you want to make async calls using a task queue, you can simply replace get

https://github.com/bakercp/ofxHTTP/blob/master/example_basic_client_loader/src/ofApp.cpp#L46

with post

https://github.com/bakercp/ofxHTTP/blob/master/libs/ofxHTTP/include/ofx/HTTP/DefaultClientTaskQueue.h#L64-L76

And fill in the parameters you like. Everything will then be fed back to you asynchronously using the the event system.

ofxHTTP is still in active development so I haven’t fully documented everything in tutorial format, but I’ve tried to at least document most of the methods with doxygen etc.

If you wanted to generate documentation, you could run doxygen on the

2 Likes

Awesome! I’ll give it a whirl when I have a little time to spare! Thanks!

Thanks @bakercp I actually tried ofxHttp but with a couldn’t figure out how to do it.
After trying ofxHttpUtils I thought it was a POCO limitation. I’ll give it another shot later. Thanks!

hi bakercp, thanks for sharing ofxHTTP and other related codes. i’ve tried the example_basic_ip_video_server and it works great. however, could you kindly provide a simple example about posting a chunk of json to the server? http post and json things are confusing for me.

@yangyangcv sure – do you have a server I could post to? or did you want to use an ofxHTTP server to receive the JSON?

i think one ofxHTTP server plus one ofxHTTP client would be the best :slight_smile: