Skip to content
Sirma edited this page Feb 25, 2016 · 1 revision

In this document, we'll write a basic exchange connector that connects to a mock exchange. The main use of the exchange connector is to provide an interface between the exchange and the router.

This code is used in the demo stack to provide support to the mock exchange.

The annotated source code for the full exchange connector that we will present in this example is available in rtbkit's repository.

Exchange Connector

In general, to build an exchange connector, you need to extend the ExchangeConnector class and implement a few methods. Those methods enable you to specify how to react to a bid request.

Here, we'll inherit from HttpExchangeConnector instead of directly from ExchangeConnector to reuse the built-in HTTP interface that most exchanges will require.

struct MockExchangeConnector : public HttpExchangeConnector
{
    MockExchangeConnector(ServiceBase & owner, const std::string & name) :
    	HttpExchangeConnector(name, owner) {
    	
    	// setup the HTTP endpoint
        this->auctionResource = "/bids";
        this->auctionVerb = "POST";
}

Then, we need to supply a unique exchange name that will be used by to router to do its configuration.

std::string exchangeName() const {
    return "mock";
}

Parsing request

Now it's time to do actual work and parse the incoming bid request from the HTTP exchange. The parseBidRequest function will simply handle an HTTP request where the headers are already parsed and the actual body can be processed so that it returns a nice exchange neutral BidRequest object.

Here, we cheat a little bit and use the internal BidRequest JSON parsing in "datacratic" format since this is exacly what the MockExchange puts on the wire.

std::shared_ptr<BidRequest> parseBidRequest(HttpAuctionHandler & handler,
                                            Datacratic::HttpHeader const & header,
                                            std::string const & payload) {
    std::shared_ptr<BidRequest> request;
    request.reset(BidRequest::parse("datacratic", payload));
    return request;
}

Processing time

Next are 2 functions that handle the real-time side of processing bids. When a bid request is emitted by the exchange, it expects an answer within a certain amount of time. The router is going to make sure that this condition is met whatever happens within the stack. Thus, the exchange connector needs to specify how much time is allowed to process the bid request and answer with an actual bid. This is done using 2 functions to return:

  • the total amount of time available
  • an estimation of the round trip time to account for the network latency

Therefore, the maximum amount of time available is: time_available - round_trip_time

Here, we return simple hardcoded values i.e. 35ms and 5ms. Thus, the router will now be thinking that each bid request must be answered within 30ms. Of course, more elaborate schemes are possible...

double getTimeAvailableMs(HttpAuctionHandler & handler,
                          const HttpHeader & header,
                          const std::string & payload) {
    return 35.0;
}

double getRoundTripTimeMs(HttpAuctionHandler & handler,
                          const HttpHeader & header) {
    return 5.0;
}

Bid response

Last but not least, the exchange connector is also responsible for sending back the bid response to the exchange in the proper format.

There isn't much to it. The auction object contains data that represent responses of agents that chose to bid on this particular auction. For each impression in the bid request, we simply select the winning response and build the JSON string that will be sent back via HTTP.

Datacratic::HttpResponse getResponse(HttpAuctionHandler const & handler,
                                     const HttpHeader & header,
                                     Auction const & auction) const {
    std::string result;
    
    // get the auction's result data
    const Auction::Data * current = auction.getCurrentData();
    if (current->hasError())
        return getErrorResponse(handler, auction, current->error + ": " + current->details);
    
    // begin the JSON string that will be returned
    result = "{\\"imp\\":[";
    
    bool first = true;
    for (unsigned spotNum = 0; spotNum < current->responses.size(); ++spotNum) {

        // only process impression with at least one valid response
        if (!current->hasValidResponse(spotNum))
            continue;

        if (!first) result += ",";
        first = false;

        // simply take the winning response and append it to the JSON string
        auto & resp = current->winningResponse(spotNum);
        result += ML::format("{\\"id\":\\"%s\",\\"max_price\\":%lld,\\"account\\":\\"%s\\"}",
                             ML::jsonEscape(auction.request->spots.at(spotNum).id.toString()).c_str(),
                             (long long) (MicroUSD_CPM(resp.price.maxPrice)),
                             resp.account.toString('.'));
        }

    // we're done, send the response back
    result += "]}";
    return HttpResponse(200, "application/json", result);
}

Router

And that's it. We now have a fully functional exchange connector.

What's left is to integrate in to an actual router. One way is to use the default router_runner and simply supply a configuration file. Make sure your exchange connector lives in a shared library based on its actual name:

"lib" + name + "_exchange.so"

For example, the OpenRTB exchange connector lives in libopenrtb_exchange.so.

To create your libNAME_exchange.so, you can create your connector at /examples directory with the name you prefer, for example, myname_exchange_connector.h and myname_exchange_connector.cc, and add the following line to file /examples/examples.mk

$(eval $(call library,myname_exchange,myname_exchange_connector.cc,exchange))

After that, return to root directory /home/rtbkit and do make. It must create libmyname_exchange.so.

Then, simply start the whole thing and specify a JSON configuration file. The actual configuration parameters are specific to the type of exchange and handled in the configure function of the ExchangeConnector. Here, we're simply relying on the existing configuration handling of the HttpExchangeConnector where those parameters are defined and documented. For example, this is where the number of worker threads is defined.

./build/x86_64/bin/router_runner --exchange-configuration=./examples/router-config.json

And you're done.

Contribute!

Be famous and add the name of your exchange here: Exchange Connectors

Clone this wiki locally