microsoft / cpprestsdk

The C++ REST SDK is a Microsoft project for cloud-based client-server communication in native code using a modern asynchronous C++ API design. This project aims to help C++ developers connect to and interact with services.
Other
7.97k stars 1.65k forks source link

clients to maintain HTTP session ID and reuse for every interaction #753

Open balamcsd opened 6 years ago

balamcsd commented 6 years ago

Developed POC in CPP using cpprest to call rest services (HTTPS) The communication followed is JSON - POST method, using basic authentication. Please suggest how to make it session aware: (ie) The client has to exchange HTTP Session ID via standard HTTP protocol

Similar to cookiecontainer in C#

Below code works, but does not have session management

#include <string>
#include <codecvt>
#include "stdafx.h"

#include <cpprest/http_listener.h>              // HTTP server
#include <cpprest/json.h>                       // JSON library
#include <cpprest/uri.h>                        // URI library
#include <cpprest/ws_client.h>                  // WebSocket client
#include <cpprest/containerstream.h>            // Async streams backed by STL containers
#include <cpprest/interopstream.h>              // Bridges for integrating Async streams with STL and WinRT streams
#include <cpprest/rawptrstream.h>               // Async streams backed by raw pointer to memory
#include <cpprest/producerconsumerstream.h>     // Async streams for producer consumer scenar
#include <sstream>
#include <locale>
#include <cpprest/http_client.h>
#include <cereal/types/unordered_map.hpp>
#include <cereal/types/memory.hpp>
#include <cereal/archives/binary.hpp>
#include <fstream>

typedef web::json::value JsonValue;
typedef web::json::value::value_type JsonValueType;
typedef std::wstring String;
typedef std::wstringstream StringStream;

using namespace std;
using namespace utility;                    // Common utilities like string conversions
using namespace web;                        // Common features like URIs.
using namespace web::http;                  // Common HTTP functionality
using namespace web::http::client;          // HTTP client features
using namespace concurrency::streams;       // Asynchronous streams
using namespace utility::conversions;
using namespace web::http::client;

JsonValue CreateJSONObject();
void Post();
static std::string base64_encode(const std::string &in);
utility::string_t m_szSessionToken = L"";

int main()
{
    Post();
}

JsonValue CreateJSONObject()
{
    // Create a JSON object.
    json::value obj1;
    obj1[L"dataType"] = json::value::number(1);
    obj1[L"source"] = json::value::string(L"xyz");
    obj1[L"timestamp"] = json::value::number(1519693200000);
    obj1[L"value"] = json::value::number(1.0);

    json::value obj2;
    obj2[L"dataType"] = json::value::number(1);
    obj2[L"source"] = json::value::string(L"xyz");
    obj2[L"timestamp"] = json::value::number(1519693200000);
    obj2[L"value"] = json::value::number(-1.0);

    json::value ConVal;

    ConVal[L"ConVal"] = json::value::array({ obj1, obj2 });

    return ConVal;
}

void Post()
{
    json::value postData;

    postData = CreateJSONObject();

    http_client_config config;
    credentials creds(L"userid", L"password");

    config.set_credentials(creds);

    http_client client(U("https://serviceurl/endpoint"), config);

    http_request req(methods::POST);

    std::string rest_original = "userid:password";

    std::string rest_encoded = "Basic " + base64_encode(rest_original); 

    std::wstring widestr = std::wstring(rest_encoded.begin(), rest_encoded.end());

    // Add base64 result to header
    req.headers().add(L"Authorization", widestr);

    if (!m_szSessionToken.empty())
    req.headers().add(L"Cookie", m_szSessionToken);

    req.headers().set_content_type(U("application/json"));
    req.set_body(postData);

    client.request(req).then([](http_response response)
    {
        std::wcout << response.status_code() << std::endl;

        if (response.status_code() == status_codes::OK)
        {
            auto body = response.extract_string();

    auto headers = response.headers();

    auto it = headers.find(U("Set-Cookie"));

    if (it != headers.end()) {
        m_szSessionToken = it->second;
    }

            std::wcout << body.get().c_str();

            getchar();
        }
    });

    getchar();
}

static std::string base64_encode(const std::string &in) {

    std::string out;

    int val = 0, valb = -6;
    for (unsigned char c : in) {
        val = (val << 8) + c;
        valb += 8;
        while (valb >= 0) {
            out.push_back("ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"[(val >> valb) & 0x3F]);
            valb -= 6;
        }
    }
    if (valb>-6) out.push_back("ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"[((val << 8) >> (valb + 8)) & 0x3F]);
    while (out.size() % 4) out.push_back('=');
    return out;
}  
balamcsd commented 6 years ago

Anyone to help! urgent

garethsb commented 6 years ago

If your server uses cookies to implement session tracking your client can examine the Set-Cookie response header and set the Cookie request header using cpprestsdk usual http_headers mechanisms.

There do appear to currently be some platform inconsistencies and may be bugs in the handling of cookie headers. See https://github.com/Microsoft/cpprestsdk/search?q=cookie&type=Issues

balamcsd commented 6 years ago

Yes, i examined the response header and noticed the "Set-Cookie" and found JSESSIONID=25EFC1E34E649D5A0D9E9DC05FF06E57; Path=/; Secure I dont know how to fetch this jessionid from response header, so i can use it in request header I want the client to use the same SESSIONID for future requests. I tried below code, throws compile error

if(m_cookie != "") request.headers().add("Cookie", m_cookie);

m_cookie = response.headers()["set-cookie"];

Could you please help me with sample code

balamcsd commented 6 years ago

I found the answer Define one class level variable

        utility::string_t m_szSessionToken = L"";

Wherever you are adding other request headers, add cookie header

        if (!m_szSessionToken.empty())
        req.headers().add(L"Cookie", m_szSessionToken);

Once you get response

        auto headers = response.headers();

        auto it = headers.find(U("Set-Cookie"));

        if (it != headers.end()) {
            m_szSessionToken = it->second;
        }

Please note that m_szSessionToken will be set only once (after the first response) which we store it and add it to request headers (from the second request onwards)

This way client will contact with same jessionid for every call

garethsb commented 6 years ago

Yes, that looks right, you are implementing the client-side contract for session-management cookies. Glad you got it working.

balamcsd commented 6 years ago

Thanks for giving right direction towards simple solution