Skip to content

Commit c5fd12a

Browse files
Support GET on the /vtrack endpoint (prebid#4073)
1 parent f27ec7d commit c5fd12a

26 files changed

Lines changed: 1422 additions & 282 deletions

src/main/java/org/prebid/server/cache/CoreCacheService.java

Lines changed: 91 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@
99
import org.apache.commons.collections4.CollectionUtils;
1010
import org.apache.commons.lang3.ObjectUtils;
1111
import org.apache.commons.lang3.StringUtils;
12+
import org.apache.http.client.utils.URIBuilder;
1213
import org.prebid.server.auction.model.AuctionContext;
1314
import org.prebid.server.auction.model.BidInfo;
1415
import org.prebid.server.auction.model.CachedDebugLog;
@@ -22,6 +23,7 @@
2223
import org.prebid.server.cache.model.DebugHttpCall;
2324
import org.prebid.server.cache.proto.request.bid.BidCacheRequest;
2425
import org.prebid.server.cache.proto.request.bid.BidPutObject;
26+
import org.prebid.server.cache.proto.response.CacheErrorResponse;
2527
import org.prebid.server.cache.proto.response.bid.BidCacheResponse;
2628
import org.prebid.server.cache.proto.response.bid.CacheObject;
2729
import org.prebid.server.cache.utils.CacheServiceUtil;
@@ -45,6 +47,7 @@
4547
import org.prebid.server.vertx.httpclient.HttpClient;
4648
import org.prebid.server.vertx.httpclient.model.HttpClientResponse;
4749

50+
import java.net.URISyntaxException;
4851
import java.net.URL;
4952
import java.time.Clock;
5053
import java.util.ArrayList;
@@ -55,6 +58,7 @@
5558
import java.util.Objects;
5659
import java.util.Set;
5760
import java.util.concurrent.TimeoutException;
61+
import java.util.function.BiConsumer;
5862
import java.util.function.Function;
5963
import java.util.stream.Collectors;
6064
import java.util.stream.Stream;
@@ -66,6 +70,8 @@ public class CoreCacheService {
6670
private static final String BID_WURL_ATTRIBUTE = "wurl";
6771
private static final String TRACE_INFO_SEPARATOR = "-";
6872
private static final int MAX_DATACENTER_REGION_LENGTH = 4;
73+
private static final String UUID_QUERY_PARAMETER = "uuid";
74+
private static final String CH_QUERY_PARAMETER = "ch";
6975

7076
private final HttpClient httpClient;
7177
private final URL externalEndpointUrl;
@@ -186,18 +192,27 @@ private Future<BidCacheResponse> makeRequest(BidCacheRequest bidCacheRequest,
186192
cacheHeaders,
187193
mapper.encodeToString(bidCacheRequest),
188194
remainingTimeout)
189-
.map(response -> toBidCacheResponse(
195+
.map(response -> processVtrackWriteCacheResponse(
190196
response.getStatusCode(), response.getBody(), bidCount, accountId, startTime))
191-
.recover(exception -> failResponse(exception, accountId, startTime));
197+
.recover(exception -> failVtrackCacheWriteResponse(exception, accountId, startTime));
192198
}
193199

194-
private Future<BidCacheResponse> failResponse(Throwable exception, String accountId, long startTime) {
195-
metrics.updateCacheRequestFailedTime(accountId, clock.millis() - startTime);
200+
private BidCacheResponse processVtrackWriteCacheResponse(int statusCode,
201+
String responseBody,
202+
int bidCount,
203+
String accountId,
204+
long startTime) {
196205

197-
logger.warn("Error occurred while interacting with cache service: {}", exception.getMessage());
198-
logger.debug("Error occurred while interacting with cache service", exception);
206+
final BidCacheResponse bidCacheResponse = toBidCacheResponse(statusCode, responseBody, bidCount);
207+
metrics.updateVtrackCacheWriteRequestTime(accountId, clock.millis() - startTime, MetricName.ok);
208+
return bidCacheResponse;
209+
}
199210

200-
return Future.failedFuture(exception);
211+
private <T> Future<T> failVtrackCacheWriteResponse(Throwable exception, String accountId, long startTime) {
212+
if (exception instanceof PreBidException) {
213+
metrics.updateVtrackCacheWriteRequestTime(accountId, clock.millis() - startTime, MetricName.err);
214+
}
215+
return failResponse(exception);
201216
}
202217

203218
public Future<BidCacheResponse> cachePutObjects(List<BidPutObject> bidPutObjects,
@@ -210,7 +225,10 @@ public Future<BidCacheResponse> cachePutObjects(List<BidPutObject> bidPutObjects
210225
final List<CachedCreative> cachedCreatives =
211226
updatePutObjects(bidPutObjects, isEventsEnabled, biddersAllowingVastUpdate, accountId, integration);
212227

213-
updateCreativeMetrics(accountId, cachedCreatives);
228+
updateCreativeMetrics(
229+
cachedCreatives,
230+
(ttl, type) -> metrics.updateVtrackCacheCreativeTtl(accountId, ttl, type),
231+
(size, type) -> metrics.updateVtrackCacheCreativeSize(accountId, size, type));
214232

215233
return makeRequest(toBidCacheRequest(cachedCreatives), cachedCreatives.size(), timeout, accountId);
216234
}
@@ -309,7 +327,10 @@ private Future<CacheServiceResult> doCacheOpenrtb(List<CacheBid> bids,
309327

310328
final BidCacheRequest bidCacheRequest = toBidCacheRequest(cachedCreatives);
311329

312-
updateCreativeMetrics(accountId, cachedCreatives);
330+
updateCreativeMetrics(
331+
cachedCreatives,
332+
(ttl, type) -> metrics.updateCacheCreativeTtl(accountId, ttl, type),
333+
(size, type) -> metrics.updateCacheCreativeSize(accountId, size, type));
313334

314335
final String url = ObjectUtils.firstNonNull(internalEndpointUrl, externalEndpointUrl).toString();
315336
final String body = mapper.encodeToString(bidCacheRequest);
@@ -343,8 +364,8 @@ private CacheServiceResult processResponseOpenrtb(HttpClientResponse response,
343364
externalEndpointUrl.toString(), httpRequest, httpResponse, startTime);
344365
final BidCacheResponse bidCacheResponse;
345366
try {
346-
bidCacheResponse = toBidCacheResponse(
347-
responseStatusCode, response.getBody(), bidCount, accountId, startTime);
367+
bidCacheResponse = toBidCacheResponse(responseStatusCode, response.getBody(), bidCount);
368+
metrics.updateAuctionCacheRequestTime(accountId, clock.millis() - startTime, MetricName.ok);
348369
} catch (PreBidException e) {
349370
return CacheServiceResult.of(httpCall, e, Collections.emptyMap());
350371
}
@@ -361,7 +382,7 @@ private CacheServiceResult failResponseOpenrtb(Throwable exception,
361382
logger.warn("Error occurred while interacting with cache service: {}", exception.getMessage());
362383
logger.debug("Error occurred while interacting with cache service", exception);
363384

364-
metrics.updateCacheRequestFailedTime(accountId, clock.millis() - startTime);
385+
metrics.updateAuctionCacheRequestTime(accountId, clock.millis() - startTime, MetricName.err);
365386

366387
final DebugHttpCall httpCall = makeDebugHttpCall(externalEndpointUrl.toString(), request, null, startTime);
367388
return CacheServiceResult.of(httpCall, exception, Collections.emptyMap());
@@ -460,9 +481,7 @@ private String generateWinUrl(String bidId,
460481

461482
private BidCacheResponse toBidCacheResponse(int statusCode,
462483
String responseBody,
463-
int bidCount,
464-
String accountId,
465-
long startTime) {
484+
int bidCount) {
466485

467486
if (statusCode != 200) {
468487
throw new PreBidException("HTTP status code " + statusCode);
@@ -480,7 +499,6 @@ private BidCacheResponse toBidCacheResponse(int statusCode,
480499
throw new PreBidException("The number of response cache objects doesn't match with bids");
481500
}
482501

483-
metrics.updateCacheRequestSuccessTime(accountId, clock.millis() - startTime);
484502
return bidCacheResponse;
485503
}
486504

@@ -538,17 +556,20 @@ private static String resolveVideoBidUuid(String uuid, String hbCacheId) {
538556
return hbCacheId != null && uuid.endsWith(hbCacheId) ? hbCacheId : uuid;
539557
}
540558

541-
private void updateCreativeMetrics(String accountId, List<CachedCreative> cachedCreatives) {
559+
private void updateCreativeMetrics(List<CachedCreative> cachedCreatives,
560+
BiConsumer<Integer, MetricName> updateCreativeTtlMetric,
561+
BiConsumer<Integer, MetricName> updateCreativeSiseMetric) {
562+
542563
for (CachedCreative cachedCreative : cachedCreatives) {
543564
final BidPutObject payload = cachedCreative.getPayload();
544565
final MetricName creativeType = resolveCreativeTypeName(payload);
545566
final Integer creativeTtl = ObjectUtils.defaultIfNull(payload.getTtlseconds(), payload.getExpiry());
546567

547568
if (creativeTtl != null) {
548-
metrics.updateCacheCreativeTtl(accountId, creativeTtl, creativeType);
569+
updateCreativeTtlMetric.accept(creativeTtl, creativeType);
549570
}
550571

551-
metrics.updateCacheCreativeSize(accountId, cachedCreative.getSize(), creativeType);
572+
updateCreativeSiseMetric.accept(cachedCreative.getSize(), creativeType);
552573
}
553574
}
554575

@@ -627,4 +648,55 @@ private static String normalizeDatacenterRegion(String datacenterRegion) {
627648
? trimmedDatacenterRegion.substring(0, MAX_DATACENTER_REGION_LENGTH)
628649
: trimmedDatacenterRegion;
629650
}
651+
652+
public Future<HttpClientResponse> getCachedObject(String key, String ch, Timeout timeout) {
653+
final long remainingTimeout = timeout.remaining();
654+
if (remainingTimeout <= 0) {
655+
return Future.failedFuture(new TimeoutException("Timeout has been exceeded"));
656+
}
657+
658+
final URL endpointUrl = ObjectUtils.firstNonNull(internalEndpointUrl, externalEndpointUrl);
659+
final String url;
660+
try {
661+
final URIBuilder uriBuilder = new URIBuilder(endpointUrl.toString());
662+
uriBuilder.addParameter(UUID_QUERY_PARAMETER, key);
663+
if (StringUtils.isNotBlank(ch)) {
664+
uriBuilder.addParameter(CH_QUERY_PARAMETER, ch);
665+
}
666+
url = uriBuilder.build().toString();
667+
} catch (URISyntaxException e) {
668+
return Future.failedFuture(new IllegalArgumentException("Configured cache url is malformed", e));
669+
}
670+
671+
final long startTime = clock.millis();
672+
return httpClient.get(url, cacheHeaders, remainingTimeout)
673+
.map(response -> processVtrackReadResponse(response, startTime))
674+
.recover(CoreCacheService::failResponse);
675+
}
676+
677+
private HttpClientResponse processVtrackReadResponse(HttpClientResponse response, long startTime) {
678+
final int statusCode = response.getStatusCode();
679+
final String body = response.getBody();
680+
681+
if (statusCode == 200) {
682+
metrics.updateVtrackCacheReadRequestTime(clock.millis() - startTime, MetricName.ok);
683+
return response;
684+
}
685+
686+
metrics.updateVtrackCacheReadRequestTime(clock.millis() - startTime, MetricName.err);
687+
688+
try {
689+
final CacheErrorResponse errorResponse = mapper.decodeValue(body, CacheErrorResponse.class);
690+
return HttpClientResponse.of(statusCode, response.getHeaders(), errorResponse.getMessage());
691+
} catch (DecodeException e) {
692+
throw new PreBidException("Cannot parse response: " + body, e);
693+
}
694+
}
695+
696+
private static <T> Future<T> failResponse(Throwable exception) {
697+
logger.warn("Error occurred while interacting with cache service: {}", exception.getMessage());
698+
logger.debug("Error occurred while interacting with cache service", exception);
699+
700+
return Future.failedFuture(exception);
701+
}
630702
}
Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
package org.prebid.server.cache.proto.response;
2+
3+
import lombok.Builder;
4+
import lombok.Value;
5+
6+
@Value
7+
@Builder
8+
public class CacheErrorResponse {
9+
10+
String error;
11+
12+
Integer status;
13+
14+
String path;
15+
16+
String message;
17+
18+
Long timestamp;
19+
}
Lines changed: 108 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,108 @@
1+
package org.prebid.server.handler;
2+
3+
import io.netty.handler.codec.http.HttpHeaderValues;
4+
import io.netty.handler.codec.http.HttpResponseStatus;
5+
import io.vertx.core.AsyncResult;
6+
import io.vertx.core.MultiMap;
7+
import io.vertx.core.http.HttpMethod;
8+
import io.vertx.ext.web.RoutingContext;
9+
import org.apache.commons.lang3.StringUtils;
10+
import org.prebid.server.cache.CoreCacheService;
11+
import org.prebid.server.execution.timeout.Timeout;
12+
import org.prebid.server.execution.timeout.TimeoutFactory;
13+
import org.prebid.server.log.Logger;
14+
import org.prebid.server.log.LoggerFactory;
15+
import org.prebid.server.model.Endpoint;
16+
import org.prebid.server.util.HttpUtil;
17+
import org.prebid.server.vertx.httpclient.model.HttpClientResponse;
18+
import org.prebid.server.vertx.verticles.server.HttpEndpoint;
19+
import org.prebid.server.vertx.verticles.server.application.ApplicationResource;
20+
21+
import java.util.Collections;
22+
import java.util.List;
23+
import java.util.Objects;
24+
25+
public class GetVtrackHandler implements ApplicationResource {
26+
27+
private static final Logger logger = LoggerFactory.getLogger(GetVtrackHandler.class);
28+
29+
private static final String UUID_PARAMETER = "uuid";
30+
private static final String CH_PARAMETER = "ch";
31+
32+
private final long defaultTimeout;
33+
private final CoreCacheService coreCacheService;
34+
private final TimeoutFactory timeoutFactory;
35+
36+
public GetVtrackHandler(long defaultTimeout, CoreCacheService coreCacheService, TimeoutFactory timeoutFactory) {
37+
this.defaultTimeout = defaultTimeout;
38+
this.coreCacheService = Objects.requireNonNull(coreCacheService);
39+
this.timeoutFactory = Objects.requireNonNull(timeoutFactory);
40+
}
41+
42+
@Override
43+
public List<HttpEndpoint> endpoints() {
44+
return Collections.singletonList(HttpEndpoint.of(HttpMethod.GET, Endpoint.vtrack.value()));
45+
}
46+
47+
@Override
48+
public void handle(RoutingContext routingContext) {
49+
final String uuid = routingContext.request().getParam(UUID_PARAMETER);
50+
final String ch = routingContext.request().getParam(CH_PARAMETER);
51+
if (StringUtils.isBlank(uuid)) {
52+
respondWith(
53+
routingContext,
54+
HttpResponseStatus.BAD_REQUEST,
55+
"'%s' is a required query parameter and can't be empty".formatted(UUID_PARAMETER));
56+
return;
57+
}
58+
59+
final Timeout timeout = timeoutFactory.create(defaultTimeout);
60+
61+
coreCacheService.getCachedObject(uuid, ch, timeout)
62+
.onComplete(asyncCache -> handleCacheResult(asyncCache, routingContext));
63+
}
64+
65+
private static void respondWithServerError(RoutingContext routingContext, Throwable exception) {
66+
logger.error("Error occurred while sending request to cache", exception);
67+
respondWith(routingContext, HttpResponseStatus.INTERNAL_SERVER_ERROR,
68+
"%s: %s".formatted("Error occurred while sending request to cache", exception.getMessage()));
69+
}
70+
71+
private static void respondWith(RoutingContext routingContext,
72+
HttpResponseStatus status,
73+
MultiMap headers,
74+
String body) {
75+
76+
HttpUtil.executeSafely(
77+
routingContext,
78+
Endpoint.vtrack,
79+
response -> {
80+
headers.forEach(response::putHeader);
81+
response.setStatusCode(status.code()) .end(body);
82+
});
83+
}
84+
85+
private static void respondWith(RoutingContext routingContext, HttpResponseStatus status, String body) {
86+
HttpUtil.executeSafely(
87+
routingContext,
88+
Endpoint.vtrack,
89+
response -> response
90+
.putHeader(HttpUtil.CONTENT_TYPE_HEADER, HttpHeaderValues.APPLICATION_JSON)
91+
.setStatusCode(status.code())
92+
.end(body));
93+
}
94+
95+
private void handleCacheResult(AsyncResult<HttpClientResponse> async, RoutingContext routingContext) {
96+
if (async.failed()) {
97+
respondWithServerError(routingContext, async.cause());
98+
} else {
99+
final HttpClientResponse response = async.result();
100+
final HttpResponseStatus status = HttpResponseStatus.valueOf(response.getStatusCode());
101+
if (status == HttpResponseStatus.OK) {
102+
respondWith(routingContext, status, response.getHeaders(), response.getBody());
103+
} else {
104+
respondWith(routingContext, status, response.getBody());
105+
}
106+
}
107+
}
108+
}

src/main/java/org/prebid/server/handler/VtrackHandler.java renamed to src/main/java/org/prebid/server/handler/PostVtrackHandler.java

Lines changed: 10 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -39,9 +39,9 @@
3939
import java.util.Set;
4040
import java.util.stream.Collectors;
4141

42-
public class VtrackHandler implements ApplicationResource {
42+
public class PostVtrackHandler implements ApplicationResource {
4343

44-
private static final Logger logger = LoggerFactory.getLogger(VtrackHandler.class);
44+
private static final Logger logger = LoggerFactory.getLogger(PostVtrackHandler.class);
4545

4646
private static final String ACCOUNT_PARAMETER = "a";
4747
private static final String INTEGRATION_PARAMETER = "int";
@@ -56,14 +56,14 @@ public class VtrackHandler implements ApplicationResource {
5656
private final TimeoutFactory timeoutFactory;
5757
private final JacksonMapper mapper;
5858

59-
public VtrackHandler(long defaultTimeout,
60-
boolean allowUnknownBidder,
61-
boolean modifyVastForUnknownBidder,
62-
ApplicationSettings applicationSettings,
63-
BidderCatalog bidderCatalog,
64-
CoreCacheService coreCacheService,
65-
TimeoutFactory timeoutFactory,
66-
JacksonMapper mapper) {
59+
public PostVtrackHandler(long defaultTimeout,
60+
boolean allowUnknownBidder,
61+
boolean modifyVastForUnknownBidder,
62+
ApplicationSettings applicationSettings,
63+
BidderCatalog bidderCatalog,
64+
CoreCacheService coreCacheService,
65+
TimeoutFactory timeoutFactory,
66+
JacksonMapper mapper) {
6767

6868
this.defaultTimeout = defaultTimeout;
6969
this.allowUnknownBidder = allowUnknownBidder;

0 commit comments

Comments
 (0)