![]() |
RTBKit
0.9
Open-source framework to create real-time ad bidding systems.
|
00001 /* bid_request.cc 00002 Jeremy Barnes, 1 February 2012 00003 Copyright (c) 2012 Datacratic. All rights reserved. 00004 00005 */ 00006 00007 #include "rtbkit/common/bid_request.h" 00008 #include "jml/arch/exception.h" 00009 #include "jml/arch/format.h" 00010 #include "jml/arch/spinlock.h" 00011 00012 #include <dlfcn.h> 00013 #include <boost/thread/locks.hpp> 00014 #include <boost/algorithm/string.hpp> 00015 #include <unordered_map> 00016 00017 #include "jml/db/persistent.h" 00018 #include "openrtb/openrtb_parsing.h" 00019 #include "soa/types/json_printing.h" 00020 #include "soa/service/json_codec.h" 00021 00022 00023 using namespace std; 00024 using namespace ML; 00025 using namespace ML::DB; 00026 00027 namespace Datacratic { 00028 00029 using namespace RTBKIT; 00030 00031 DefaultDescription<Location>:: 00032 DefaultDescription() 00033 { 00034 this->nullAccepted = true; 00035 addField("countryCode", &Location::countryCode, 00036 "Country code of location"); 00037 addField("regionCode", &Location::regionCode, 00038 "Region code of location"); 00039 addField("cityName", &Location::cityName, 00040 "City name of location"); 00041 addField("postalCode", &Location::postalCode, 00042 "Postal code of location"); 00043 addField("dma", &Location::dma, 00044 "DMA code of location"); 00045 addField("timezoneOffsetMinutes", &Location::timezoneOffsetMinutes, 00046 "Timezone offset of location in minutes"); 00047 } 00048 00049 DefaultDescription<Format>:: 00050 DefaultDescription() 00051 { 00052 } 00053 00054 struct HistoricalPositionDescriptor 00055 : public ValueDescriptionT<OpenRTB::AdPosition> { 00056 00057 virtual void parseJsonTyped(OpenRTB::AdPosition * val, 00058 JsonParsingContext & context) const 00059 { 00060 if (context.isString()) { 00061 string s = context.expectStringAscii(); 00062 if (s == "NONE" || s == "none") { 00063 val->val = OpenRTB::AdPosition::UNKNOWN; 00064 } 00065 else if (s == "ABOVE_FOLD" || s == "above") { 00066 val->val = OpenRTB::AdPosition::ABOVE; 00067 } 00068 else if (s == "BELOW_FOLD" || s == "below") { 00069 val->val = OpenRTB::AdPosition::BELOW; 00070 } 00071 else throw ML::Exception("invalid ad position " + s); 00072 } 00073 else if (context.isNumber()) { 00074 int i = context.expectInt(); 00075 val->val = i; 00076 } 00077 else throw ML::Exception("can't parse historical ad position " 00078 + context.expectJson().toString()); 00079 } 00080 00081 virtual void printJsonTyped(const OpenRTB::AdPosition * val, 00082 JsonPrintingContext & context) const 00083 { 00084 context.writeInt(val->val); 00085 } 00086 00087 virtual bool isDefaultTyped(const OpenRTB::AdPosition * val) const 00088 { 00089 return val->val == -1; 00090 } 00091 }; 00092 00093 DefaultDescription<AdSpot>:: 00094 DefaultDescription() 00095 { 00096 addParent<OpenRTB::Impression>(); 00097 00098 addField("formats", &AdSpot::formats, "Impression formats"); 00099 addField<OpenRTB::AdPosition>("position", &AdSpot::position, "Impression fold position", 00100 new HistoricalPositionDescriptor()); 00101 addField("reservePrice", &AdSpot::reservePrice, "Impression reserve price"); 00102 00103 //throw ML::Exception("Need to sync formats with the bid request"); 00104 } 00105 00106 DefaultDescription<BidRequest>:: 00107 DefaultDescription() 00108 { 00109 onUnknownField = [=] (BidRequest * br, JsonParsingContext & context) 00110 { 00111 //context.skip(); 00112 if(context.printPath().find("!!CV") != std::string::npos) 00113 { 00114 context.skip(); 00115 } 00116 else 00117 { 00118 cerr << "(default description)got unknown field " << context.printPath() << endl; 00119 std::function<Json::Value & (int, Json::Value &)> getEntry 00120 = [&] (int n, Json::Value & curr) -> Json::Value & 00121 { 00122 if (n == context.path.size()) 00123 return curr; 00124 else if (context.path[n].index != -1) 00125 return getEntry(n + 1, curr[context.path[n].index]); 00126 else return getEntry(n + 1, curr[context.path[n].key]); 00127 }; 00128 00129 getEntry(0, br->unparseable) 00130 = context.expectJson(); 00131 } 00132 }; 00133 addField("id", &BidRequest::auctionId, "Exchange auction ID"); 00134 addField("timestamp", &BidRequest::timestamp, "Bid request timestamp"); 00135 addField("isTest", &BidRequest::isTest, "Is bid request a test?"); 00136 addField("url", &BidRequest::url, "Site URL"); 00137 addField("ipAddress", &BidRequest::ipAddress, "IP address of user"); 00138 addField("userAgent", &BidRequest::userAgent, "User agent of device"); 00139 addField("language", &BidRequest::language, "User language code"); 00140 addField("protocolVersion", &BidRequest::protocolVersion, 00141 "bid request protocol version"); 00142 addField("exchange", &BidRequest::exchange, "Original bid request exchagne"); 00143 addField("provider", &BidRequest::provider, "Bid request provider"); 00144 addField("winSurcharges", &BidRequest::winSurcharges, 00145 "extra amounts paid on win"); 00146 addField("meta", &BidRequest::meta, 00147 "extra metadata about the bid request"); 00148 addField("location", &BidRequest::location, 00149 "location of user"); 00150 addField("segments", &BidRequest::segments, 00151 "segments active for user"); 00152 addField("restrictions", &BidRequest::restrictions, 00153 "restrictions active for bid request"); 00154 addField("userIds", &BidRequest::userIds, "User IDs for this user"); 00155 addField("imp", &BidRequest::imp, "Ad imp in this request"); 00156 addField("spots", &BidRequest::imp, "Ad imp in this request"); 00157 addField("site", &BidRequest::site, "OpenRTB site object"); 00158 addField("app", &BidRequest::app, "OpenRTB app object"); 00159 addField("device", &BidRequest::device, "OpenRTB device object"); 00160 addField("user", &BidRequest::user, "OpenRTB user object"); 00161 addField("unparseable", &BidRequest::unparseable, "Unparseable fields are stored here"); 00162 addField("bidCurrency", &BidRequest::bidCurrency, "Currency we're bidding in"); 00163 } 00164 00165 } // namespace Datacratic 00166 00167 00168 namespace RTBKIT { 00169 00170 using Datacratic::jsonDecode; 00171 using Datacratic::jsonEncode; 00172 00173 /*****************************************************************************/ 00174 /* FORMAT */ 00175 /*****************************************************************************/ 00176 00177 void 00178 Format:: 00179 fromJson(const Json::Value & val) 00180 { 00181 if (val.isString()) { 00182 string s = val.asString(); 00183 fromString(s); 00184 #if 0 00185 int width, height; 00186 int nchars = -1; 00187 int res = sscanf(s.c_str(), "%dx%d%n", &width, &height, &nchars); 00188 if ((res != 2 && res != 3) || nchars != s.length()) 00189 throw ML::Exception("couldn't parse format string " 00190 + s); 00191 this->width = width; 00192 this->height = height; 00193 #endif 00194 } else if (val.isArray()) { 00195 throw ML::Exception("array format parsing not done yet"); 00196 } 00197 else if (val.isObject()) { 00198 width = val["width"].asInt(); 00199 height = val["height"].asInt(); 00200 } 00201 else throw ML::Exception("couldn't parse format string " + val.toString()); 00202 } 00203 00204 void 00205 Format:: 00206 fromString(const string &s) 00207 { 00208 int width, height; 00209 int nchars = -1; 00210 int res = sscanf(s.c_str(), "%dx%d%n", &width, &height, &nchars); 00211 if ((res != 2 && res != 3) || nchars != s.length()) 00212 throw ML::Exception("couldn't parse format string " 00213 + s); 00214 this->width = width; 00215 this->height = height; 00216 } 00217 00218 Json::Value 00219 Format:: 00220 toJson() const 00221 { 00222 return print(); 00223 } 00224 00225 std::string 00226 Format:: 00227 toJsonStr() const 00228 { 00229 return print(); 00230 } 00231 00232 std::string 00233 Format:: 00234 print() const 00235 { 00236 return ML::format("%dx%d", width, height); 00237 } 00238 00239 void 00240 Format:: 00241 serialize(ML::DB::Store_Writer & store) const 00242 { 00243 store << compact_size_t(width) << compact_size_t(height); 00244 } 00245 00246 void 00247 Format:: 00248 reconstitute(ML::DB::Store_Reader & store) 00249 { 00250 width = compact_size_t(store); 00251 height = compact_size_t(store); 00252 } 00253 00254 00255 /*****************************************************************************/ 00256 /* FORMAT SET */ 00257 /*****************************************************************************/ 00258 00259 void 00260 FormatSet:: 00261 fromJson(const Json::Value & val) 00262 { 00263 clear(); 00264 if (val.isString()) { 00265 Format f; 00266 f.fromJson(val); 00267 push_back(f); 00268 return; 00269 } 00270 else if (val.isArray()) { 00271 for (unsigned i = 0; i < val.size(); ++i) { 00272 Format f; 00273 f.fromJson(val[i]); 00274 push_back(f); 00275 } 00276 } 00277 else throw ML::Exception("couldn't parse format set from JSON " 00278 + val.toString()); 00279 } 00280 00281 Json::Value 00282 FormatSet:: 00283 toJson() const 00284 { 00285 Json::Value result; 00286 if (empty()) return result; 00287 //if (size() == 1) 00288 // return result = at(0).toJson(); 00289 for (unsigned i = 0; i < size(); ++i) 00290 result[i] = at(i).toJson(); 00291 return result; 00292 } 00293 00294 std::string 00295 FormatSet:: 00296 toJsonStr() const 00297 { 00298 return boost::trim_copy(toJson().toString()); 00299 } 00300 00301 std::string 00302 FormatSet:: 00303 print() const 00304 { 00305 if (empty()) return "[]"; 00306 else if (size() == 1) 00307 return at(0).print(); 00308 else { 00309 string result = "["; 00310 for (unsigned i = 0; i < size(); ++i) { 00311 if (i != 0) result += ", "; 00312 result += at(i).print(); 00313 } 00314 result += ']'; 00315 return result; 00316 } 00317 } 00318 00319 void 00320 FormatSet:: 00321 sort() 00322 { 00323 std::sort(begin(), end()); 00324 } 00325 00326 bool isEmpty(const std::string & str) 00327 { 00328 return str.empty(); 00329 } 00330 00331 bool isEmpty(const Json::Value & val) 00332 { 00333 return val.isNull(); 00334 } 00335 00336 bool isEmpty(const Utf8String &str) 00337 { 00338 return (str.rawLength() == 0) ; 00339 } 00340 00341 template<typename T> 00342 void addIfNotEmpty(Json::Value & obj, const std::string & key, 00343 const T & val) 00344 { 00345 if (isEmpty(val)) return; 00346 obj[key] = val; 00347 } 00348 00349 template<typename T> 00350 void addIfNotEmpty(Json::Value & obj, const std::string & key, 00351 const T & val, const T & emptyVal) 00352 { 00353 if (val == emptyVal) return; 00354 obj[key] = val; 00355 } 00356 00357 inline void addIfNotEmpty(Json::Value & obj, const std::string & key, 00358 const Url & url) 00359 { 00360 if (url.empty()) return; 00361 obj[key] = url.toString(); 00362 } 00363 00364 inline void addIfNotEmpty(Json::Value & obj, const std::string & key, 00365 const std::vector<CurrencyCode> & curr) 00366 { 00367 if (curr.empty()) 00368 return; 00369 00370 for (unsigned i = 0; i < curr.size(); ++i) 00371 obj[key][i] = Amount::getCurrencyStr(curr[i]); 00372 } 00373 00374 void 00375 FormatSet:: 00376 serialize(ML::DB::Store_Writer & store) const 00377 { 00378 store << ML::compact_vector<Format, 3, uint16_t>(*this); 00379 } 00380 00381 void 00382 FormatSet:: 00383 reconstitute(ML::DB::Store_Reader & store) 00384 { 00385 ML::compact_vector<Format, 3, uint16_t> & v = *this; 00386 store >> v; 00387 } 00388 00389 struct FormatSetDescription 00390 : public ValueDescriptionT<FormatSet> { 00391 00392 virtual void parseJsonTyped(FormatSet * val, 00393 JsonParsingContext & context) const 00394 { 00395 val->fromJson(context.expectJson()); 00396 } 00397 00398 virtual void printJsonTyped(const FormatSet * val, 00399 JsonPrintingContext & context) const 00400 { 00401 context.writeJson(val->toJson()); 00402 } 00403 00404 virtual bool isDefaultTyped(const FormatSet * val) const 00405 { 00406 return val->empty(); 00407 } 00408 }; 00409 00410 ValueDescriptionT<RTBKIT::FormatSet> * 00411 getDefaultDescription(RTBKIT::FormatSet *) 00412 { 00413 return new FormatSetDescription(); 00414 } 00415 00416 00417 00418 /*****************************************************************************/ 00419 /* LOCATION */ 00420 /*****************************************************************************/ 00421 00422 Utf8String 00423 Location:: 00424 fullLocationString() const 00425 { 00426 Utf8String result(countryCode + ":" + regionCode + ":") ; 00427 result += cityName ; 00428 result += (":" + postalCode + ":" + boost::lexical_cast<string>(dma)); 00429 return result; 00430 //Utf8String result(countryCode +":"+ regionCode +":" + 00431 // return ML::format("%s:%s:%s:%s:%d", 00432 // countryCode.c_str(), regionCode.c_str(), 00433 // cityName.c_str(), postalCode.c_str(), dma); 00434 } 00435 00436 Json::Value 00437 Location:: 00438 toJson() const 00439 { 00440 Json::Value result; 00441 addIfNotEmpty(result, "countryCode", countryCode); 00442 addIfNotEmpty(result, "regionCode", regionCode); 00443 addIfNotEmpty(result, "cityName", std::string(cityName.rawData(),cityName.rawLength())); 00444 addIfNotEmpty(result, "postalCode", postalCode); 00445 addIfNotEmpty(result, "dma", dma, -1); 00446 addIfNotEmpty(result, "timezoneOffsetMinutes", timezoneOffsetMinutes, -1); 00447 return result; 00448 } 00449 00450 std::string 00451 Location:: 00452 toJsonStr() const 00453 { 00454 return boost::trim_copy(toJson().toString()); 00455 } 00456 00457 Location 00458 Location:: 00459 createFromJson(const Json::Value & json) 00460 { 00461 Location result; 00462 00463 for (auto it = json.begin(), end = json.end(); it != end; ++it) { 00464 if (it.memberName() == "countryCode") 00465 result.countryCode = it->asString(); 00466 else if (it.memberName() == "regionCode") 00467 result.regionCode = it->asString(); 00468 else if (it.memberName() == "cityName") 00469 result.cityName = it->asString(); 00470 else if (it.memberName() == "postalCode") 00471 result.postalCode = it->asString(); 00472 else if (it.memberName() == "dma") 00473 result.dma = it->asInt(); 00474 else if (it.memberName() == "timezoneOffsetMinutes") 00475 result.timezoneOffsetMinutes = it->asInt(); 00476 else throw ML::Exception("unknown location field " + it.memberName()); 00477 } 00478 return result; 00479 } 00480 00481 void 00482 Location:: 00483 serialize(ML::DB::Store_Writer & store) const 00484 { 00485 unsigned char version = 0; 00486 store << version << countryCode << regionCode << cityName << postalCode 00487 << compact_size_t(dma) << compact_size_t(timezoneOffsetMinutes); 00488 } 00489 00490 void 00491 Location:: 00492 reconstitute(ML::DB::Store_Reader & store) 00493 { 00494 unsigned char version; 00495 store >> version; 00496 if (version != 0) 00497 throw ML::Exception("invalid Location version"); 00498 store >> countryCode >> regionCode >> cityName >> postalCode; 00499 dma = compact_size_t(store); 00500 timezoneOffsetMinutes = compact_size_t(store); 00501 } 00502 00503 00504 /*****************************************************************************/ 00505 /* AD SPOT */ 00506 /*****************************************************************************/ 00507 00508 namespace { 00509 00510 SmallIntVector getDims(const Json::Value & val) 00511 { 00512 SmallIntVector result; 00513 00514 if (val.isArray()) { 00515 for (unsigned i = 0; i < val.size(); ++i) 00516 result.push_back(val[i].asInt()); 00517 } 00518 else result.push_back(val.asInt()); 00519 00520 return result; 00521 } 00522 00523 } // file scope 00524 00525 #if 0 00526 AdSpot::Position 00527 AdSpot::stringToPosition(const std::string &pos) 00528 { 00529 if (pos == "None" || pos == "NONE" || pos == "none") 00530 return AdSpot::Position::NONE; 00531 else if (pos == "ABOVE_FOLD" || pos == "above") 00532 return AdSpot::Position::ABOVE_FOLD; 00533 else if (pos == "BELOW_FOLD" || pos == "below") 00534 return AdSpot::Position::BELOW_FOLD; 00535 else 00536 throw ML::Exception(" Unknown value for AdSpot::Position ==>" + pos); 00537 } 00538 #endif 00539 00540 void 00541 AdSpot:: 00542 fromJson(const Json::Value & val) 00543 { 00544 *this = AdSpot(); 00545 00546 // Parse openrtb 00547 static DefaultDescription<AdSpot> desc; 00548 StructuredJsonParsingContext context(val); 00549 00550 // Rather than barf on unknown fields, for forwards compatibility we put them 00551 // in the unparseable array via this function 00552 auto onUnknownField = [&] () 00553 { 00554 cerr << "(adspot)got unknown field " << context.printPath() 00555 << context.expectJson() << endl; 00556 00557 #if 0 00558 std::function<Json::Value & (int, Json::Value &)> getEntry 00559 = [&] (int n, Json::Value & curr) -> Json::Value & 00560 { 00561 if (n == context.path.size()) 00562 return curr; 00563 else if (context.path[n].index != -1) 00564 return getEntry(n + 1, curr[context.path[n].index]); 00565 else return getEntry(n + 1, curr[context.path[n].key]); 00566 }; 00567 00568 getEntry(0, unparseable[key]) = context.expectJson(); 00569 #endif 00570 }; 00571 00572 context.onUnknownFieldHandlers.push_back(onUnknownField); 00573 00574 desc.parseJsonTyped(this, context); 00575 00576 return; 00577 00578 try { 00579 id = Id(val["id"].asString()); 00580 if (val.isMember("formats")) { 00581 formats.fromJson(val["formats"]); 00582 } 00583 else { 00584 SmallIntVector widths = getDims(val["width"]); 00585 SmallIntVector heights = getDims(val["height"]); 00586 formats.clear(); 00587 if (widths.size() != heights.size()) 00588 throw ML::Exception("widths and heights must have same size"); 00589 for (unsigned i = 0; i < widths.size(); ++i) 00590 formats.push_back(Format(widths[i], heights[i])); 00591 } 00592 const Json::Value & rpj = val["reservePrice"]; 00593 if (rpj.isNumeric()) { 00594 reservePrice = USD_CPM(rpj.asDouble()); 00595 } 00596 else if (!rpj.isNull()) { 00597 reservePrice = Amount::fromJson(rpj); 00598 } 00599 if (val.isMember("position")) { 00600 auto & pj = val["position"]; 00601 if (pj.isString()) { 00602 string s = pj.asString(); 00603 if (s == "NONE" || s == "none") { 00604 position.val = OpenRTB::AdPosition::UNKNOWN; 00605 } 00606 else if (s == "ABOVE_FOLD" || s == "above") { 00607 position.val = OpenRTB::AdPosition::ABOVE; 00608 } 00609 else if (s == "BELOW_FOLD" || s == "below") { 00610 position.val = OpenRTB::AdPosition::BELOW; 00611 } 00612 } 00613 else if (pj.isIntegral()) { 00614 position.val = pj.asInt(); 00615 } 00616 else throw ML::Exception("can't parse position " + val["position"].toString()); 00617 //position = stringToPosition(val["position"].asString()); 00618 } 00619 else 00620 position.val = -1;//OpenRTB::AdPosition::UNKNOWN; 00621 } catch (const std::exception & exc) { 00622 cerr << "parsing AdSpot " << val << ": " << exc.what() << endl; 00623 throw; 00624 } 00625 } 00626 00627 Json::Value 00628 AdSpot:: 00629 toJson() const 00630 { 00631 static DefaultDescription<AdSpot> desc; 00632 StructuredJsonPrintingContext context; 00633 desc.printJsonTyped(this, context); 00634 return std::move(context.output); 00635 } 00636 00637 std::string formatDims(const SmallIntVector & dims) 00638 { 00639 if (dims.size() == 1) 00640 return ML::format("%d", (int)dims[0]); 00641 00642 string result = "["; 00643 for (unsigned i = 0; i < dims.size(); ++i) { 00644 result += ML::format("%d", (int)dims[i]); 00645 if (i != dims.size() - 1) 00646 result += ','; 00647 } 00648 return result + "]"; 00649 } 00650 00651 std::string 00652 AdSpot:: 00653 format() const 00654 { 00655 return formats.print(); 00656 } 00657 00658 std::string 00659 AdSpot:: 00660 firstFormat() const 00661 { 00662 return formats[0].print(); 00663 } 00664 00665 AdSpot 00666 AdSpot:: 00667 createFromJson(const Json::Value & json) 00668 { 00669 AdSpot result; 00670 result.fromJson(json); 00671 return result; 00672 } 00673 00674 void 00675 AdSpot:: 00676 serialize(ML::DB::Store_Writer & store) const 00677 { 00678 unsigned char version = 2; 00679 store << version << toJson().toString(); 00680 } 00681 00682 void 00683 AdSpot:: 00684 reconstitute(ML::DB::Store_Reader & store) 00685 { 00686 unsigned char version; 00687 store >> version; 00688 if (version != 2) 00689 throw ML::Exception("unknown AdSpot serialization version"); 00690 string s; 00691 store >> s; 00692 fromJson(Json::parse(s)); 00693 } 00694 00695 00696 /*****************************************************************************/ 00697 /* USER IDS */ 00698 /*****************************************************************************/ 00699 00700 void 00701 UserIds:: 00702 add(const Id & id, IdDomain domain) 00703 { 00704 if (!insert(make_pair(domainToString(domain), id)).second) 00705 throw ML::Exception("attempt to double add id %s for %s", 00706 id.toString().c_str(), domainToString(domain)); 00707 setStatic(id, domain); 00708 } 00709 00710 void 00711 UserIds:: 00712 add(const Id & id, const std::string & domain1, IdDomain domain2) 00713 { 00714 add(id, domain1); 00715 add(id, domain2); 00716 } 00717 00718 void 00719 UserIds:: 00720 add(const Id & id, const std::string & domain) 00721 { 00722 if (!insert(make_pair(domain, id)).second) 00723 throw ML::Exception("attempt to double add id " + id.toString() +" for " + domain); 00724 setStatic(id, domain); 00725 } 00726 00727 const char * 00728 UserIds:: 00729 domainToString(IdDomain domain) 00730 { 00731 switch (domain) { 00732 case ID_PROVIDER: return "prov"; 00733 case ID_EXCHANGE: return "xchg"; 00734 default: return "<<<UNKNOWN>>>"; 00735 } 00736 } 00737 00738 void 00739 UserIds:: 00740 setStatic(const Id & id, const std::string & domain) 00741 { 00742 if (domain == "prov") 00743 providerId = id; 00744 else if (domain == "xchg") 00745 exchangeId = id; 00746 } 00747 00748 void 00749 UserIds:: 00750 setStatic(const Id & id, IdDomain domain) 00751 { 00752 switch (domain) { 00753 case ID_PROVIDER: providerId = id; break; 00754 case ID_EXCHANGE: exchangeId = id; break; 00755 default: break; 00756 } 00757 } 00758 00759 void 00760 UserIds:: 00761 set(const Id & id, const std::string & domain) 00762 { 00763 (*this)[domain] = id; 00764 } 00765 00766 Json::Value 00767 UserIds:: 00768 toJson() const 00769 { 00770 Json::Value result; 00771 for (auto it = begin(), end = this->end(); it != end; ++it) 00772 result[it->first] = it->second.toString(); 00773 return result; 00774 } 00775 00776 std::string 00777 UserIds:: 00778 toJsonStr() const 00779 { 00780 return boost::trim_copy(toJson().toString()); 00781 } 00782 00783 UserIds 00784 UserIds:: 00785 createFromJson(const Json::Value & json) 00786 { 00787 UserIds result; 00788 00789 for (auto it = json.begin(), end = json.end(); it != end; ++it) { 00790 Id id(it->asString()); 00791 result.add(id, it.memberName()); 00792 } 00793 00794 return result; 00795 } 00796 00797 std::string 00798 UserIds:: 00799 serializeToString() const 00800 { 00801 // TODO: do a better job... 00802 return toJsonStr(); 00803 } 00804 00805 UserIds 00806 UserIds:: 00807 createFromString(const std::string & str) 00808 { 00809 // TODO: do a better job... 00810 return createFromJson(Json::parse(str)); 00811 } 00812 00813 void 00814 UserIds:: 00815 serialize(ML::DB::Store_Writer & store) const 00816 { 00817 unsigned char version = 0; 00818 store << version << (map<std::string, Id> &)(*this); 00819 } 00820 00821 void 00822 UserIds:: 00823 reconstitute(ML::DB::Store_Reader & store) 00824 { 00825 unsigned char version; 00826 store >> version; 00827 if (version != 0) 00828 throw ML::Exception("invalid UserIds version"); 00829 store >> (map<std::string, Id> &)*this; 00830 } 00831 00832 struct UserIdsDescription 00833 : public ValueDescriptionT<UserIds> { 00834 00835 virtual void parseJsonTyped(UserIds * val, 00836 JsonParsingContext & context) const 00837 { 00838 auto onMember = [&] () 00839 { 00840 string key = context.path.fieldName(); 00841 Id value(context.expectStringAscii()); 00842 val->add(value, key); 00843 }; 00844 00845 context.forEachMember(onMember); 00846 } 00847 00848 virtual void printJsonTyped(const UserIds * val, 00849 JsonPrintingContext & context) const 00850 { 00851 context.startObject(); 00852 00853 for (auto & id: *val) { 00854 context.startMember(id.first); 00855 context.writeString(id.second.toString()); 00856 } 00857 00858 context.endObject(); 00859 } 00860 00861 virtual bool isDefaultTyped(const UserIds * val) const 00862 { 00863 return val->empty(); 00864 } 00865 00866 }; 00867 00868 ValueDescriptionT<RTBKIT::UserIds> * 00869 getDefaultDescription(RTBKIT::UserIds *) 00870 { 00871 return new UserIdsDescription(); 00872 } 00873 00874 00875 /*****************************************************************************/ 00876 /* BID REQUEST */ 00877 /*****************************************************************************/ 00878 00879 void 00880 BidRequest:: 00881 sortAll() 00882 { 00883 restrictions.sortAll(); 00884 segments.sortAll(); 00885 } 00886 00887 template<typename T> 00888 void toJsonValue(Json::Value & v, const T & val) 00889 { 00890 static DefaultDescription<T> desc; 00891 StructuredJsonPrintingContext printContext; 00892 desc.printJson(&val, printContext); 00893 v = std::move(printContext.output); 00894 } 00895 00896 Json::Value 00897 BidRequest:: 00898 toJson() const 00899 { 00900 Json::Value result; 00901 result["!!CV"] = "RTBKIT-JSON-1.0"; 00902 result["id"] = auctionId.toString(); 00903 result["timestamp"] = timestamp.secondsSinceEpoch(); 00904 addIfNotEmpty(result, "isTest", isTest, false); 00905 addIfNotEmpty(result, "url", url); 00906 addIfNotEmpty(result, "ipAddress", ipAddress); 00907 addIfNotEmpty(result, "userAgent", userAgent); 00908 addIfNotEmpty(result, "language", language); 00909 addIfNotEmpty(result, "protocolVersion", protocolVersion); 00910 addIfNotEmpty(result, "exchange", exchange); 00911 addIfNotEmpty(result, "provider", provider); 00912 addIfNotEmpty(result, "meta", meta); 00913 addIfNotEmpty(result, "unparseable", unparseable); 00914 if (!bidCurrency.empty()) 00915 result["bidCurrency"] = jsonEncode(bidCurrency); 00916 00917 if (site) { 00918 toJsonValue(result["site"], *site); 00919 } 00920 if (app) 00921 toJsonValue(result["app"], *app); 00922 if (device) 00923 toJsonValue(result["device"], *device); 00924 if (user) 00925 toJsonValue(result["user"], *user); 00926 00927 if (!winSurcharges.empty()) 00928 result["winSurcharges"] = winSurcharges.toJson(); 00929 00930 result["location"] = location.toJson(); 00931 00932 if (!imp.empty()) { 00933 for (unsigned i = 0; i < imp.size(); ++i) { 00934 result["imp"][i] = imp[i].toJson(); 00935 result["spots"][i] = imp[i].toJson(); 00936 } 00937 } 00938 00939 if (!segments.empty()) 00940 result["segments"] = segments.toJson(); 00941 if (!restrictions.empty()) 00942 result["restrictions"] = restrictions.toJson(); 00943 if (!userIds.empty()) 00944 result["userIds"] = userIds.toJson(); 00945 00946 return result; 00947 } 00948 00949 std::string 00950 BidRequest:: 00951 toJsonStr() const 00952 { 00953 static const DefaultDescription<BidRequest> BidRequestDesc; 00954 00955 std::ostringstream stream; 00956 StreamJsonPrintingContext context(stream); 00957 BidRequestDesc.printJson(this, context); 00958 return stream.str(); 00959 00960 //return boost::trim_copy(toJson().toString()); 00961 } 00962 00963 template<typename T> 00964 void fromJsonOptional(const Json::Value & val, 00965 std::unique_ptr<T> & ptr, 00966 Json::Value & unparseable, 00967 std::string key) 00968 { 00969 static DefaultDescription<T> desc; 00970 StructuredJsonParsingContext context(val); 00971 std::unique_ptr<T> res(new T()); 00972 00973 // Rather than barf on unknown fields, for forwards compatibility we put them 00974 // in the unparseable array via this function 00975 auto onUnknownField = [&] () 00976 { 00977 std::function<Json::Value & (int, Json::Value &)> getEntry 00978 = [&] (int n, Json::Value & curr) -> Json::Value & 00979 { 00980 if (n == context.path.size()) 00981 return curr; 00982 else if (context.path[n].index != -1) 00983 return getEntry(n + 1, curr[context.path[n].index]); 00984 else return getEntry(n + 1, curr[context.path[n].key]); 00985 }; 00986 00987 getEntry(0, unparseable[key]) = context.expectJson(); 00988 }; 00989 00990 context.onUnknownFieldHandlers.push_back(onUnknownField); 00991 00992 desc.parseJson(res.get(), context); 00993 00994 ptr.swap(res); 00995 } 00996 00997 BidRequest 00998 BidRequest:: 00999 createFromJson(const Json::Value & json) 01000 { 01001 BidRequest result; 01002 01003 string canonicalVersion; 01004 01005 //cerr << "parsing " << json << endl; 01006 01007 for (auto it = json.begin(), end = json.end(); it != end; ++it) { 01008 01009 //cerr << "got member " << it.memberName() << endl; 01010 01011 if (it.memberName() == "!!CV") { 01012 canonicalVersion = it->asString(); 01013 if (canonicalVersion != "0.1" && canonicalVersion != "RTBKIT-JSON-1.0") 01014 throw ML::Exception("can't parse BidRequest with CV " 01015 + canonicalVersion); 01016 } 01017 else if (it.memberName() == "id") 01018 result.auctionId.parse(it->asString()); 01019 else if (it.memberName() == "timestamp") 01020 result.timestamp = Date::fromSecondsSinceEpoch(it->asDouble()); 01021 else if (it.memberName() == "isTest") 01022 result.isTest = it->asBool(); 01023 else if (it.memberName() == "url") 01024 result.url = Url(it->asString()); 01025 else if (it.memberName() == "ipAddress") 01026 result.ipAddress = it->asString(); 01027 else if (it.memberName() == "userAgent") 01028 result.userAgent = it->asString(); 01029 else if (it.memberName() == "language") 01030 result.language = it->asString(); 01031 else if (it.memberName() == "protocolVersion") 01032 result.protocolVersion = it->asString(); 01033 else if (it.memberName() == "exchange") 01034 result.exchange = it->asString(); 01035 else if (it.memberName() == "provider") 01036 result.provider = it->asString(); 01037 else if (it.memberName() == "winSurchageMicros") 01038 result.winSurcharges["surcharge"] += MicroUSD(it->asInt()); 01039 else if (it.memberName() == "winSurcharges") 01040 result.winSurcharges += LineItems::fromJson(*it); 01041 else if (it.memberName() == "meta") 01042 result.meta = *it; 01043 else if (it.memberName() == "creative") 01044 result.meta["creative"] = *it; 01045 else if (it.memberName() == "location") 01046 result.location = Location::createFromJson(*it); 01047 else if (it.memberName() == "segments") 01048 result.segments = SegmentsBySource::createFromJson(*it); 01049 else if (it.memberName() == "restrictions") 01050 result.restrictions = SegmentsBySource::createFromJson(*it); 01051 else if (it.memberName() == "userIds") 01052 result.userIds = UserIds::createFromJson(*it); 01053 else if (it.memberName() == "imp" || it.memberName() == "spots") { 01054 const Json::Value & json = *it; 01055 if (!json.empty() && !json.isArray()) 01056 throw ML::Exception("imp is not an array"); 01057 result.imp.clear(); 01058 for (unsigned i = 0; i < json.size(); ++i) { 01059 result.imp.push_back(AdSpot::createFromJson(json[i])); 01060 } 01061 } 01062 else if (it.memberName() == "site") { 01063 fromJsonOptional(*it, result.site, result.unparseable, "site"); 01064 } 01065 else if (it.memberName() == "app") { 01066 fromJsonOptional(*it, result.app, result.unparseable, "app"); 01067 } 01068 else if (it.memberName() == "device") { 01069 fromJsonOptional(*it, result.device, result.unparseable, "device"); 01070 } 01071 else if (it.memberName() == "user") { 01072 fromJsonOptional(*it, result.user, result.unparseable, "user"); 01073 } 01074 else if (it.memberName() == "unparseable") 01075 result.unparseable = *it; 01076 else if (it.memberName() == "bidCurrency") 01077 jsonDecode(*it, result.bidCurrency); 01078 else throw ML::Exception("unknown canonical bid request field " 01079 + it.memberName()); 01080 } 01081 01082 return result; 01083 } 01084 01085 namespace { 01086 typedef std::unordered_map<std::string, BidRequest::Parser> Parsers; 01087 static Parsers parsers; 01088 typedef boost::lock_guard<ML::Spinlock> Guard; 01089 static ML::Spinlock lock; 01090 01091 BidRequest::Parser getParser(std::string const & source) { 01092 // see if it's already existing 01093 { 01094 Guard guard(lock); 01095 auto i = parsers.find(source); 01096 if (i != parsers.end()) return i->second; 01097 } 01098 01099 // else, try to load the parser library 01100 std::string path = "lib" + source + "_bid_request.so"; 01101 void * handle = dlopen(path.c_str(), RTLD_NOW); 01102 if (!handle) { 01103 throw ML::Exception("couldn't find bid request parser library " + path); 01104 } 01105 01106 // if it went well, it should be registered now 01107 Guard guard(lock); 01108 auto i = parsers.find(source); 01109 if (i != parsers.end()) return i->second; 01110 01111 throw ML::Exception("couldn't find bid request parser for source " + source); 01112 } 01113 01114 } // file scope 01115 01116 void 01117 BidRequest:: 01118 registerParser(const std::string & source, Parser parser) 01119 { 01120 Guard guard(lock); 01121 if (!parsers.insert(make_pair(source, parser)).second) 01122 throw ML::Exception("already had a bid request parser registered"); 01123 } 01124 01125 namespace { 01126 01127 static const DefaultDescription<BidRequest> BidRequestDesc; 01128 01129 struct CanonicalParser { 01130 01131 static BidRequest * parse(const std::string & str) 01132 { 01133 #if 0 // old and slow 01134 auto j = Json::parse(str); 01135 return new BidRequest(BidRequest::createFromJson(j)); 01136 #endif 01137 01138 //cerr << "parsing " << str << endl; 01139 StreamingJsonParsingContext context; 01140 context.init("bid request", str.c_str(), str.size()); 01141 auto_ptr<BidRequest> result(new BidRequest()); 01142 BidRequestDesc.parseJsonTyped(result.get(), context); 01143 01144 //cerr << "result->url = " << result->url << endl; 01145 //cerr << "result->userAgent = " << result->userAgent << endl; 01146 01147 return result.release(); 01148 } 01149 }; 01150 01151 struct AtInit { 01152 AtInit() 01153 { 01154 BidRequest::registerParser("recoset", CanonicalParser::parse); 01155 BidRequest::registerParser("datacratic", CanonicalParser::parse); 01156 BidRequest::registerParser("rtbkit", CanonicalParser::parse); 01157 } 01158 } atInit; 01159 } // file scope 01160 01161 BidRequest * 01162 BidRequest:: 01163 parse(const std::string & source, const std::string & bidRequest) 01164 { 01165 if (source.empty()) { 01166 throw ML::Exception("'source' parameter cannot be empty"); 01167 } 01168 01169 if (source == "datacratic" || strncmp(bidRequest.c_str(), "{\"!!CV\":", 8) == 0) 01170 { 01171 return CanonicalParser::parse(bidRequest); 01172 } 01173 Parser parser = getParser(source); 01174 01175 //cerr << "got parser for source " << source << endl; 01176 01177 auto result = parser(bidRequest); 01178 01179 if (false) { 01180 cerr << bidRequest << endl; 01181 StreamJsonPrintingContext context(cerr); 01182 BidRequestDesc.printJsonTyped(result, context); 01183 } 01184 01185 return result; 01186 } 01187 01188 BidRequest * 01189 BidRequest:: 01190 parse(const std::string & source, const Utf8String & bidRequest) 01191 { 01192 return BidRequest::parse(source, string(bidRequest.rawData(), bidRequest.rawLength())); 01193 } 01194 01195 SegmentResult 01196 BidRequest:: 01197 segmentPresent(const std::string & source, 01198 const std::string & segment) const 01199 { 01200 auto it = segments.find(source); 01201 if (it == segments.end()) 01202 return SEG_MISSING; 01203 return (it->second->contains(segment) 01204 ? SEG_PRESENT : SEG_NOT_PRESENT); 01205 } 01206 01207 SegmentResult 01208 BidRequest:: 01209 segmentPresent(const std::string & source, int segment) const 01210 { 01211 auto it = segments.find(source); 01212 if (it == segments.end()) 01213 return SEG_MISSING; 01214 return (it->second->contains(segment) 01215 ? SEG_PRESENT : SEG_NOT_PRESENT); 01216 } 01217 01218 Id 01219 BidRequest:: 01220 getUserId(IdDomain domain) const 01221 { 01222 switch (domain) { 01223 case ID_PROVIDER: return userIds.providerId; 01224 case ID_EXCHANGE: return userIds.exchangeId; 01225 default: throw ML::Exception("unknown ID for getUserId"); 01226 } 01227 } 01228 01229 Id 01230 BidRequest:: 01231 getUserId(const std::string & domain) const 01232 { 01233 auto it = userIds.find(domain); 01234 if (it == userIds.end()) 01235 return Id(); 01236 return it->second; 01237 } 01238 01239 std::string 01240 BidRequest:: 01241 serializeToString() const 01242 { 01243 ostringstream stream; 01244 DB::Store_Writer store(stream); 01245 serialize(store); 01246 return stream.str(); 01247 } 01248 01249 BidRequest 01250 BidRequest:: 01251 createFromString(const std::string & str) 01252 { 01253 DB::Store_Reader store(str.c_str(), str.size()); 01254 BidRequest result; 01255 result.reconstitute(store); 01256 return result; 01257 } 01258 01259 inline ML::DB::Store_Writer & 01260 operator << (ML::DB::Store_Writer & store, const Json::Value & val) 01261 { 01262 return store << val.toString(); 01263 } 01264 01265 inline ML::DB::Store_Reader & 01266 operator >> (ML::DB::Store_Reader & store, Json::Value & val) 01267 { 01268 string s; 01269 store >> s; 01270 val = Json::parse(s); 01271 return store; 01272 } 01273 01274 01275 void 01276 BidRequest:: 01277 serialize(ML::DB::Store_Writer & store) const 01278 { 01279 using namespace ML::DB; 01280 unsigned char version = 2; 01281 store << version << auctionId << language << protocolVersion 01282 << exchange << provider << timestamp << isTest 01283 << location << userIds << imp << url << ipAddress << userAgent 01284 << restrictions << segments << meta 01285 << winSurcharges; 01286 } 01287 01288 void 01289 BidRequest:: 01290 reconstitute(ML::DB::Store_Reader & store) 01291 { 01292 using namespace ML::DB; 01293 01294 unsigned char version; 01295 01296 store >> version; 01297 01298 if (version != 2) 01299 throw ML::Exception("problem reconstituting BidRequest: " 01300 "invalid version"); 01301 01302 store >> auctionId >> language >> protocolVersion 01303 >> exchange >> provider >> timestamp >> isTest 01304 >> location >> userIds >> imp >> url >> ipAddress >> userAgent 01305 >> restrictions >> segments >> meta >> winSurcharges; 01306 } 01307 01308 } // namespace RTBKIT 01309