RTBKit
0.9
Open-source framework to create real-time ad bidding systems.
|
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 }