several http requests and SSL errors

Mar 11, 2015 at 5:55 PM
Edited Mar 11, 2015 at 5:56 PM
Hi, I'm trying my luck with c++ rest sdk, but I'm having some issues, hopefully someone can tell me what I'm doing wrong.
The idea is to request google geocoding API to retrieve GPS coordinates from an address.
I'm compiling/running this on a Fedora distrib.
const std::string api_key = ""; //google geocoding API token

int main()
{
    const std::vector<std::string> addresses = {"25 rue du Louvres, Paris", "12 rue Richelieu, Paris", "255 rue du Cherche Midi, Paris"};
    std::vector<boost::optional<std::string>> results(addresses.size());

    auto task = pplx::create_task([&results, addresses]()
    {
        std::vector<pplx::task<void>> tasks;
        for(std::size_t i = 0; i < addresses.size(); i++)
        {
            const auto& address = addresses[i];
            auto t = pplx::create_task([&results, address, i]()
            {
                // Create http_client to send the request.
                web::http::client::http_client client(U("https://maps.googleapis.com"));
                const auto query = web::uri_builder(U("/maps/api/geocode/json"))
                        .append_query(U("address"), address)
                        .append_query(U("key"), api_key)
                        .to_string();
                return client.request(web::http::methods::GET, query);
            }).then([&results, address, i](web::http::http_response response) // Handle response headers arriving.
            {
                if(response.status_code() != 200)
                    throw std::runtime_error("Unable to complete request: " + response.to_string());

                const auto json = response.extract_json().get();
                assert(json.is_object());
                
                const std::string my_str; //some stuff to extract coordinates from json

                results[i] = my_str;
                return pplx::task_from_result();
            });
            tasks.push_back(std::move(t));
        }
        return pplx::when_all(tasks.begin(), tasks.end());

    });

    try
    {
        task.wait();
        for (const auto &res : results)
        {
            std::cout << (res ? *res : "invalid") << std::endl;
        }
    }
    catch(std::exception& ex)
    {
        std::cerr << "An exception has been thrown: " << ex.what() << std::endl;
        return 1;
    }
    return 0;
}
This works fine with only one request, but when I add several more, it doesn't work most of the time, either I get an exception thrown saying "Error in SSL handshake", or a plain crash deep into boost::asio::ssl.

Btw, I saw that there was some trouble with SSL handshake fixed so I'm using the development branch, but I still have the issue.

So is there something deeply wrong I'm doing here? Let me know if you need any more info

Thanks
Coordinator
Mar 12, 2015 at 1:42 AM
Hi haraelendil,

Yes you are right about using the development branch until our next release, there is an issue with multiple SSL requests that can happen in the 2.4.0 release.

I tried out the code you provided. It didn't initially compile due to the api_key variable not being in the lambda captures but I assume this was just a mistake made when you went to hide your API key. I also fixed up the results vector to actually get filled up with the JSON value serialized to a string since the my_str variable was never assigned to. Here is the final code I used:
    const std::vector<std::string> addresses = {"25 rue du Louvres, Paris", "12 rue Richelieu, Paris", "255 rue du Cherche Midi, Paris"};
    std::vector<boost::optional<std::string>> results(addresses.size());
    
    auto task = pplx::create_task([&results, addresses]()
                                  {
                                      std::vector<pplx::task<void>> tasks;
                                      for(std::size_t i = 0; i < addresses.size(); i++)
                                      {
                                          const auto& address = addresses[i];
                                          auto t = pplx::create_task([&results, address, i]()
                                                                     {
                                                                         // Create http_client to send the request.
                                                                         web::http::client::http_client client(U("https://maps.googleapis.com"));
                                                                         const auto query = web::uri_builder(U("/maps/api/geocode/json"))
                                                                         .append_query(U("address"), address)
                                                                         .append_query(U("key"), "MyKey")
                                                                         .to_string();
                                                                         return client.request(web::http::methods::GET, query);
                                                                     }).then([&results, address, i](web::http::http_response response) // Handle response headers arriving.
                                                                             {
                                                                                 if(response.status_code() != 200)
                                                                                     throw std::runtime_error("Unable to complete request: " + response.to_string());
                                                                                 
                                                                                 const auto json = response.extract_json().get();
                                                                                 assert(json.is_object());
                                                                                 
                                                                                 
                                                                                 results[i] = json.serialize();
                                                                                 return pplx::task_from_result();
                                                                             });
                                          tasks.push_back(std::move(t));
                                      }
                                      return pplx::when_all(tasks.begin(), tasks.end());
                                  });
    
    try
    {
        task.wait();
        for (const auto &res : results)
        {
            std::cout << (res ? *res : "invalid") << std::endl;
        }
    }
    catch(std::exception& ex)
    {
        std::cerr << "An exception has been thrown: " << ex.what() << std::endl;
    }
Using this, with my API key filled in, I'm able to successfully get back the geo coordinates:
{"results":[{"address_components":[{"long_name":"25","short_name":"25","types":["street_number"]},{"long_name":"Rue du Louvre","short_name":"Rue du Louvre","types":["route"]},{"long_name":"Paris","short_name":"Paris","types":["locality","political"]},{"long_name":"Paris","short_name":"75","types":["administrative_area_level_2","political"]},{"long_name":"Île-de-France","short_name":"IDF","types":["administrative_area_level_1","political"]},{"long_name":"France","short_name":"FR","types":["country","political"]},{"long_name":"75001","short_name":"75001","types":["postal_code"]}],"formatted_address":"25 Rue du Louvre, 75001 Paris, France","geometry":{"location":{"lat":48.865119700000008,"lng":2.3428339999999999},"location_type":"ROOFTOP","viewport":{"northeast":{"lat":48.866468680291511,"lng":2.3441829802915022},"southwest":{"lat":48.863770719708512,"lng":2.341485019708498}}},"partial_match":true,"place_id":"ChIJg6uT3SJu5kcRTw1yEHrOuUM","types":["street_address"]}],"status":"OK"}

etc...
I'm running on an OS X machine at the moment but the implementation code is the same. We don't do any testing on Fedora, but do on Ubuntu. Instead of catching std::exception can you try catching http_exception it may contain more information. Specifically look at the error_code() value and see what it is.

Steve
Coordinator
Mar 12, 2015 at 1:45 AM
Hi haraelendil,

Another thing you can do is to try running the http_client test cases. If you see failure there that might be helpful to know. After building go the binaries directory and execute the following to run just the http_client tests:

./test_runner libhttpclient_test.so

Steve
Mar 12, 2015 at 10:33 AM
Edited Mar 12, 2015 at 1:33 PM
Ok, thanks for the help, here's what I got so far:

First of all with http_exception, error_code() returns generic:218910881 (I ran it several times, and when I get an exception, seems to always be this one).

then for the tests:
first of all, I had some trouble compiling them, in Release/tests/functional/http/client/authentication_tests.cpp, I had issues with ambiguous reference to credential (some header included via openssl declares a credential struct which causes trouble). So I had to specify web::credential throughout the file, it may be nice to change it once and for all, maybe other distros might have the same issue.

Then I ran httpclient_test and I had different results, either most tests fail (most of the time with an unhandled exception apparently), or a good amount of tests pass and it ends up crashing.
Let me know if you want me to give more info. (I'm running a fedora 20 with gcc 4.8.3)

I'm currently downloading an ubuntu version to try out on a VM to see what's what.
Mar 12, 2015 at 2:48 PM
Well I finally managed to set up an ubuntu distro on a VM, and even if I still have some tests that fail, the basic example works fine.

I'll see if I can find some time to set up a more recent version of fedora, maybe the one I have here is getting a bit old...