RTBKit  0.9
Open-source framework to create real-time ad bidding systems.
plugins/exchange/testing/rubicon_exchange_connector_test.cc
00001 /* rubicon_exchange_connector_test.cc
00002    Jeremy Barnes, 12 March 2013
00003    Copyright (c) 2013 Datacratic Inc.  All rights reserved.
00004 
00005    Exchange connector for Rubicon.
00006 */
00007 
00008 #include "rtbkit/plugins/exchange/rubicon_exchange_connector.h"
00009 #include "rtbkit/plugins/exchange/http_auction_handler.h"
00010 #include <boost/lexical_cast.hpp>
00011 
00012 #include "rtbkit/core/router/router.h"
00013 #include "rtbkit/core/agent_configuration/agent_configuration_service.h"
00014 #include "rtbkit/core/banker/null_banker.h"
00015 #include "rtbkit/testing/test_agent.h"
00016 
00017 // for generation of dynamic creative
00018 #include "cairomm/surface.h"
00019 #include "cairomm/context.h"
00020 
00021 #include <type_traits>
00022 
00023 
00024 using namespace std;
00025 using namespace RTBKIT;
00026 
00027 // http://web.archiveorange.com/archive/v/QVfaOb8fEnu9f52fo4y2
00028 // This is needed to allow a std::function to bind into the sigc library
00029 namespace sigc
00030 {
00031     template <typename Functor>
00032     struct functor_trait<Functor, false>
00033     {
00034         typedef decltype (::sigc::mem_fun (std::declval<Functor&> (),
00035                                            &Functor::operator())) _intermediate;
00036 
00037         typedef typename _intermediate::result_type result_type;
00038         typedef Functor functor_type;
00039     };
00040 }
00041 
00042 
00043 /*****************************************************************************/
00044 /* TEST RUBICON EXCHANGE CONNECTOR                                           */
00045 /*****************************************************************************/
00046 
00054 struct TestRubiconExchangeConnector: public RubiconExchangeConnector {
00055 
00056     TestRubiconExchangeConnector(const std::string & name,
00057                              std::shared_ptr<ServiceProxies> proxies)
00058         : RubiconExchangeConnector(name, proxies)
00059     {
00060     }
00061     
00066     HttpResponse getCreative(int width, int height, float winPrice,
00067                              string format) const
00068     {
00069         string imageData;
00070         string contentType;
00071 
00072         {
00073             auto writeData = [&] (const unsigned char * data,
00074                                   unsigned length) -> Cairo::ErrorStatus
00075                 {
00076                     //cerr << "wrote " << length << " bytes" << endl;
00077                     imageData.append((const char *)data, length);
00078                     return CAIRO_STATUS_SUCCESS;
00079                 };
00080 
00081             static const Cairo::RefPtr<Cairo::Surface> logo
00082                 = Cairo::ImageSurface::create_from_png("static/rtbkit-logo-256x50.png");
00083 
00084             Cairo::RefPtr<Cairo::Surface> surface;
00085             if (format == "svg") {
00086                 surface = Cairo::SvgSurface::create_for_stream(Cairo::Surface::SlotWriteFunc(writeData), width, height);
00087                 contentType = "image/svg+xml";
00088             }
00089             else if (format == "png") {
00090                 surface = Cairo::ImageSurface::create(Cairo::FORMAT_ARGB32, width, height);
00091                 contentType = "image/png";
00092             }
00093             else throw ML::Exception("unknown creative format");
00094 
00095             Cairo::RefPtr<Cairo::Context> cr = Cairo::Context::create(surface);
00096 
00097             cr->save(); // save the state of the context
00098             cr->set_source_rgb(0.86, 0.85, 0.47);
00099             cr->paint(); // fill image with the color
00100             cr->restore(); // color is back to black now
00101 
00102             cr->save(); // save the state of the context
00103             cr->set_source(logo, 0.10, 0.10);
00104             cr->paint();
00105             cr->restore();
00106 
00107             cr->save();
00108             // draw a border around the image
00109             cr->set_line_width(5.0); // make the line wider
00110             cr->rectangle(0.0, 0.0, cairo_image_surface_get_width(surface->cobj()), height);
00111             cr->stroke();
00112 
00113             cr->set_source_rgba(0.0, 0.0, 0.0, 0.7);
00114             // draw a circle in the center of the image
00115             cr->arc(width / 2.0, height / 2.0,
00116                     height / 4.0, 0.0, 2.0 * M_PI);
00117             cr->stroke();
00118 
00119             // draw a diagonal line
00120             cr->move_to(width / 4.0, height / 4.0);
00121             cr->line_to(width * 3.0 / 4.0, height * 3.0 / 4.0);
00122             cr->stroke();
00123             cr->restore();
00124             
00125             Cairo::RefPtr<Cairo::ToyFontFace> font =
00126                 Cairo::ToyFontFace::create("Bitstream Charter",
00127                                            Cairo::FONT_SLANT_ITALIC,
00128                                            Cairo::FONT_WEIGHT_BOLD);
00129             cr->set_font_face(font);
00130             cr->set_font_size(48.0);
00131             cr->move_to(width / 4.0, height / 2.0);
00132             cr->show_text("Win Price: " + to_string(winPrice));
00133 
00134             cr->show_page();
00135 
00136             if (format == "png")
00137                 surface->write_to_png_stream(writeData);
00138         }
00139         
00140         return HttpResponse(200, contentType, imageData);
00141     }
00142 
00143     virtual void
00144     handleUnknownRequest(HttpAuctionHandler & connection,
00145                          const HttpHeader & header,
00146                          const std::string & payload) const
00147     {
00148         // Redirect to the actual ad, but lets us get the price
00149         if (header.resource == "/creative.png"
00150             || header.resource == "/creative.svg") {
00151             int width = boost::lexical_cast<int>(header.queryParams.getValue("width"));
00152             int height = boost::lexical_cast<int>(header.queryParams.getValue("height"));
00153             string encodedPrice = header.queryParams.getValue("price");
00154 
00155             //cerr << "encodedPrice = " << encodedPrice << endl;
00156             
00157             float decodedPrice = decodeWinPrice("hheehhee", encodedPrice);
00158             auto response = getCreative(width, height, decodedPrice,
00159                                         header.resource == "/creative.png" ? "png" : "svg");
00160             connection.putResponseOnWire(response);
00161             return;
00162         }
00163         else if (header.resource == "/redirect.js") {
00164             //cerr << "redirect to " << header << endl;
00165 
00166             RestParams params;
00167             string price;
00168             string redir;
00169 
00170             for (auto & p: header.queryParams) {
00171                 if (p.first == "price")
00172                     price = p.second;
00173                 if (p.first == "redir") {
00174                     redir = p.second;
00175                     continue;
00176                 }
00177                 params.push_back(p);
00178             }
00179 
00180             string redirLocation = redir + params.uriEscaped();
00181             
00182             HttpResponse response(302, "none", "", { {"Location", redirLocation} });
00183             connection.putResponseOnWire(response);
00184             return;
00185         }
00186         connection.sendErrorResponse("unknown resource " + header.resource);
00187     }
00188 };
00189 
00190 int main(int argc, char ** argv)
00191 {
00192     RubiconExchangeConnector::decodeWinPrice("hheehhee", "386C13726472656E");
00193 
00194     std::shared_ptr<ServiceProxies> proxies(new ServiceProxies());
00195 
00196     // The agent config service lets the router know how our agent is configured
00197     AgentConfigurationService agentConfig(proxies, "config");
00198     agentConfig.init();
00199     agentConfig.bindTcp();
00200     agentConfig.start();
00201 
00202     // We need a router for our exchange connector to work
00203     Router router(proxies, "router");
00204     router.unsafeDisableMonitor();  // Don't require a monitor service
00205     router.init();
00206 
00207     // Set a null banker that blindly approves all bids so that we can
00208     // bid.
00209     router.setBanker(std::make_shared<NullBanker>(true));
00210 
00211     // Start the router up
00212     router.bindTcp();
00213     router.start();
00214 
00215     // Create our exchange connector and configure it to listen on port
00216     // 10002.  Note that we need to ensure that port 10002 is open on
00217     // our firewall.
00218     std::unique_ptr<TestRubiconExchangeConnector> connector
00219         (new TestRubiconExchangeConnector("connector", proxies));
00220 
00221     connector->configureHttp(1, 10002, "0.0.0.0");
00222     connector->start();
00223     connector->enableUntil(Date::positiveInfinity());
00224 
00225     // Tell the router about the new exchange connector
00226     router.addExchange(connector.release());
00227 
00228     // This is our bidding agent, that actually calculates the bid price
00229     TestAgent agent(proxies, "agent");
00230 
00231     string hostName = "dev2.recoset.com:10002";
00232 
00233     agent.config.providerConfig["rubicon"]["seat"] = "123";
00234 
00235     // Configure the agent for bidding
00236     for (auto & c: agent.config.creatives) {
00237         c.providerConfig["rubicon"]["adomain"][0] = "rtbkit.org";
00238         c.providerConfig["rubicon"]["adm"]
00239             = "<img src=\"http://"
00240             + hostName
00241             + "/creative.png?width="
00242             + to_string(c.format.width)
00243             + "&height="
00244             + to_string(c.format.height)
00245             + "&price=${AUCTION_PRICE:BF}\"/>";
00246         c.providerConfig["rubicon"]["crid"] = c.name;
00247         c.providerConfig["rubicon"]["attr"][0] = 0;
00248     }
00249 
00250     agent.onBidRequest = [&] (
00251             double timestamp,
00252             const Id & id,
00253             std::shared_ptr<BidRequest> br,
00254             Bids bids,
00255             double timeLeftMs,
00256             const Json::Value & augmentations)
00257         {
00258             Bid& bid = bids[0];
00259 
00260             bid.bid(bid.availableCreatives[0], USD_CPM(1.234));
00261 
00262             agent.doBid(id, bids, Json::Value());
00263             ML::atomic_inc(agent.numBidRequests);
00264         };
00265 
00266     agent.init();
00267     agent.start();
00268     agent.configure();
00269 
00270     cerr << agent.config.toJson() << endl;
00271 
00272     for (;;) {
00273         // Make the agent send its configuration to the router
00274         ML::sleep(10.0);
00275         //proxies->events->dump(cerr);
00276     }
00277 }
 All Classes Namespaces Functions Variables Typedefs Enumerations Enumerator