-
Notifications
You must be signed in to change notification settings - Fork 187
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Event endpoint to handle notifications was implemented #296
Changes from 2 commits
949065d
d4fc771
4c27649
39bc5e2
26d5ba6
ac14263
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,14 @@ | ||
package org.prebid.server.analytics.model; | ||
|
||
import lombok.AllArgsConstructor; | ||
import lombok.Value; | ||
|
||
@AllArgsConstructor(staticName = "of") | ||
@Value | ||
public class NotificationEvent { | ||
String type; | ||
|
||
String bidId; | ||
|
||
String bidder; | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,143 @@ | ||
package org.prebid.server.handler; | ||
|
||
import io.netty.handler.codec.http.HttpResponseStatus; | ||
import io.vertx.core.Handler; | ||
import io.vertx.core.MultiMap; | ||
import io.vertx.core.buffer.Buffer; | ||
import io.vertx.core.http.HttpHeaders; | ||
import io.vertx.ext.web.RoutingContext; | ||
import lombok.AllArgsConstructor; | ||
import lombok.Value; | ||
import org.apache.commons.lang3.ObjectUtils; | ||
import org.apache.commons.lang3.StringUtils; | ||
import org.prebid.server.analytics.AnalyticsReporter; | ||
import org.prebid.server.analytics.model.HttpContext; | ||
import org.prebid.server.analytics.model.NotificationEvent; | ||
import org.prebid.server.exception.InvalidRequestException; | ||
import org.prebid.server.util.ResourceUtil; | ||
|
||
import java.io.IOException; | ||
import java.util.HashMap; | ||
import java.util.Map; | ||
import java.util.Objects; | ||
|
||
/** | ||
* Accepts notifications from browsers and mobile application for further processing by {@link AnalyticsReporter} | ||
* and responding with tracking pixel when requested. | ||
*/ | ||
public class NotificationEventHandler implements Handler<RoutingContext> { | ||
|
||
private static final String TRACKING_PIXEL_PNG = "static/tracking-pixel.png"; | ||
private static final String TRACKING_PIXEL_JPG = "static/tracking-pixel.jpg"; | ||
private static final String VIEW_TYPE = "view"; | ||
private static final String WIN_TYPE = "win"; | ||
private static final String FORMAT_PARAMETER = "format"; | ||
private static final String TYPE_PARAMETER = "type"; | ||
private static final String BID_ID_PARAMETER = "bidid"; | ||
private static final String BIDDER_PARAMETER = "bidder"; | ||
private static final String JPG_FORMAT = "jpg"; | ||
private static final String PNG_FORMAT = "png"; | ||
private static final String JPG_CONTENT_TYPE = "image/jpeg"; | ||
private static final String PNG_CONTENT_TYPE = "image/png"; | ||
|
||
private final AnalyticsReporter analyticsReporter; | ||
private final Map<String, TrackingPixel> trackingPixels; | ||
|
||
private NotificationEventHandler(AnalyticsReporter analyticsReporter, Map<String, TrackingPixel> trackingPixels) { | ||
this.analyticsReporter = analyticsReporter; | ||
this.trackingPixels = trackingPixels; | ||
} | ||
|
||
public static NotificationEventHandler create(AnalyticsReporter analyticsReporter) { | ||
final Map<String, TrackingPixel> trackingPixels = new HashMap<>(); | ||
trackingPixels.put(JPG_FORMAT, TrackingPixel.of(JPG_CONTENT_TYPE, readTrackingPixel(TRACKING_PIXEL_JPG))); | ||
trackingPixels.put(PNG_FORMAT, TrackingPixel.of(PNG_CONTENT_TYPE, readTrackingPixel(TRACKING_PIXEL_PNG))); | ||
return new NotificationEventHandler(Objects.requireNonNull(analyticsReporter), trackingPixels); | ||
} | ||
|
||
@Override | ||
public void handle(RoutingContext context) { | ||
final MultiMap queryParameters = context.request().params(); | ||
|
||
final NotificationEvent notificationEvent; | ||
try { | ||
notificationEvent = makeNotificationEvent(queryParameters); | ||
} catch (InvalidRequestException ex) { | ||
respondWithBadStatus(context, ex.getMessage()); | ||
return; | ||
} | ||
|
||
analyticsReporter.processEvent(notificationEvent); | ||
|
||
final Map<String, String> queryParams = HttpContext.from(context).getQueryParams(); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. There is not need to create There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Has this been addressed somehow? |
||
final String format = queryParams.get(FORMAT_PARAMETER); | ||
|
||
try { | ||
validateEventRequestQueryParams(format); | ||
} catch (InvalidRequestException ex) { | ||
respondWithBadStatus(context, ex.getMessage()); | ||
return; | ||
} | ||
|
||
respondWithOkStatus(context, format); | ||
} | ||
|
||
private static byte[] readTrackingPixel(String path) { | ||
try { | ||
return ResourceUtil.readByteArrayFromClassPath(path); | ||
} catch (IOException e) { | ||
throw new IllegalArgumentException(String.format("Failed to load pixel image at %s", path), e); | ||
} | ||
} | ||
|
||
private NotificationEvent makeNotificationEvent(MultiMap queryParameters) { | ||
final String type = queryParameters.get(TYPE_PARAMETER); | ||
if (ObjectUtils.notEqual(type, VIEW_TYPE) && ObjectUtils.notEqual(type, WIN_TYPE)) { | ||
throw new InvalidRequestException(String.format( | ||
"Type is required query parameter. Possible values are win and view, but was %s", type)); | ||
} | ||
|
||
final String bidId = queryParameters.get(BID_ID_PARAMETER); | ||
if (StringUtils.isBlank(bidId)) { | ||
throw new InvalidRequestException("bidid is required query parameter and can't be empty."); | ||
} | ||
|
||
final String bidder = queryParameters.get(BIDDER_PARAMETER); | ||
if (StringUtils.isBlank(bidder)) { | ||
throw new InvalidRequestException("bidder is required query parameter and can't be empty."); | ||
} | ||
return NotificationEvent.of(type, bidId, bidder); | ||
} | ||
|
||
private void validateEventRequestQueryParams(String format) { | ||
if (format != null && ObjectUtils.notEqual(format, JPG_FORMAT) && ObjectUtils.notEqual(format, PNG_FORMAT)) { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This is better to be replaced with |
||
throw new InvalidRequestException("format when defined should has value of png or jpg."); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. It's better to pull format parameter values from |
||
} | ||
} | ||
|
||
private void respondWithBadStatus(RoutingContext context, String message) { | ||
context.response().setStatusCode(HttpResponseStatus.BAD_REQUEST.code()) | ||
.end(String.format("Request is invalid: %s", message)); | ||
} | ||
|
||
private void respondWithOkStatus(RoutingContext context, String format) { | ||
if (format != null) { | ||
final TrackingPixel trackingPixel = trackingPixels.get(format); | ||
context.response() | ||
.putHeader(HttpHeaders.CONTENT_TYPE, trackingPixel.getContentType()) | ||
.end(Buffer.buffer(trackingPixel.getContent())); | ||
} else { | ||
context.response().end(); | ||
} | ||
} | ||
|
||
/** | ||
* Internal class for holding pixels content type to its value | ||
*/ | ||
@AllArgsConstructor(staticName = "of") | ||
@Value | ||
private static class TrackingPixel { | ||
String contentType; | ||
byte[] content; | ||
} | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Let's use static factory
of
method for consistency