diff options
Diffstat (limited to 'configserver-flags/src/main/java/com/yahoo/vespa/configserver/flags/http/FlagsHandler.java')
-rw-r--r-- | configserver-flags/src/main/java/com/yahoo/vespa/configserver/flags/http/FlagsHandler.java | 138 |
1 files changed, 138 insertions, 0 deletions
diff --git a/configserver-flags/src/main/java/com/yahoo/vespa/configserver/flags/http/FlagsHandler.java b/configserver-flags/src/main/java/com/yahoo/vespa/configserver/flags/http/FlagsHandler.java new file mode 100644 index 00000000000..40bb69111e0 --- /dev/null +++ b/configserver-flags/src/main/java/com/yahoo/vespa/configserver/flags/http/FlagsHandler.java @@ -0,0 +1,138 @@ +// Copyright 2018 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. +package com.yahoo.vespa.configserver.flags.http; + +import com.google.inject.Inject; +import com.yahoo.container.jdisc.HttpRequest; +import com.yahoo.container.jdisc.HttpResponse; +import com.yahoo.container.jdisc.LoggingRequestHandler; +import com.yahoo.log.LogLevel; +import com.yahoo.restapi.Path; +import com.yahoo.vespa.configserver.flags.FlagsDb; +import com.yahoo.vespa.flags.FlagDefinition; +import com.yahoo.vespa.flags.FlagId; +import com.yahoo.vespa.flags.Flags; +import com.yahoo.vespa.flags.json.FlagData; +import com.yahoo.yolean.Exceptions; + +import java.io.UncheckedIOException; +import java.net.URI; +import java.util.Objects; + +/** + * Handles /flags/v1 requests + * + * @author hakonhall + */ +public class FlagsHandler extends LoggingRequestHandler { + + private final FlagsDb flagsDb; + + @Inject + public FlagsHandler(LoggingRequestHandler.Context context, FlagsDb flagsDb) { + super(context); + this.flagsDb = flagsDb; + } + + @Override + public HttpResponse handle(HttpRequest request) { + try { + switch (request.getMethod()) { + case GET: return handleGET(request); + case DELETE: return handleDELETE(request); + case PUT: return handlePUT(request); + default: return ErrorResponse.methodNotAllowed("Method '" + request.getMethod() + "' is not supported"); + } + } + catch (IllegalArgumentException e) { + return ErrorResponse.badRequest(Exceptions.toMessageString(e)); + } + catch (RuntimeException e) { + log.log(LogLevel.WARNING, "Unexpected error handling '" + request.getUri() + "'", e); + return ErrorResponse.internalServerError(Exceptions.toMessageString(e)); + } + } + + private HttpResponse handleGET(HttpRequest request) { + Path path = new Path(request.getUri()); + if (path.matches("/flags/v1")) return new V1Response(flagsV1Uri(request), "data", "defined"); + if (path.matches("/flags/v1/data")) return getFlagDataList(request); + if (path.matches("/flags/v1/data/{flagId}")) return getFlagData(findFlagId(request, path)); + if (path.matches("/flags/v1/defined")) return new DefinedFlags(Flags.getAllFlags()); + if (path.matches("/flags/v1/defined/{flagId}")) return getDefinedFlag(findFlagId(request, path)); + return ErrorResponse.notFoundError("Nothing at path '" + path + "'"); + } + + private HttpResponse handlePUT(HttpRequest request) { + Path path = new Path(request.getUri()); + if (path.matches("/flags/v1/data/{flagId}")) return putFlagData(request, findFlagId(request, path)); + return ErrorResponse.notFoundError("Nothing at path '" + path + "'"); + } + + private HttpResponse handleDELETE(HttpRequest request) { + Path path = new Path(request.getUri()); + if (path.matches("/flags/v1/data/{flagId}")) return deleteFlagData(findFlagId(request, path)); + return ErrorResponse.notFoundError("Nothing at path '" + path + "'"); + } + + private String flagsV1Uri(HttpRequest request) { + URI uri = request.getUri(); + String port = uri.getPort() < 0 ? "" : ":" + uri.getPort(); + return uri.getScheme() + "://" + uri.getHost() + port + "/flags/v1"; + } + + private HttpResponse getDefinedFlag(FlagId flagId) { + var definedFlag = Flags.getFlag(flagId).map(DefinedFlag::new); + if (definedFlag.isPresent()) { + return definedFlag.get(); + } + return ErrorResponse.notFoundError("Flag " + flagId + " not defined"); + } + + private HttpResponse getFlagDataList(HttpRequest request) { + return new FlagDataListResponse(flagsV1Uri(request), flagsDb.getAllFlags(), + Objects.equals(request.getProperty("recursive"), "true")); + } + + private HttpResponse getFlagData(FlagId flagId) { + var data = flagsDb.getValue(flagId).map(FlagDataResponse::new); + if (data.isPresent()) { + return data.get(); + } + return ErrorResponse.notFoundError("Flag " + flagId + " not set"); + } + + private HttpResponse putFlagData(HttpRequest request, FlagId flagId) { + FlagData data; + try { + data = FlagData.deserialize(request.getData()); + } catch (UncheckedIOException e) { + return ErrorResponse.badRequest("Failed to deserialize request data: " + Exceptions.toMessageString(e)); + } + + if (!isForce(request)) { + FlagDefinition definition = Flags.getFlag(flagId).get(); // FlagId has been validated in findFlagId() + data.validate(definition.getUnboundFlag().serializer()); + } + + flagsDb.setValue(flagId, data); + return new OKResponse(); + } + + private HttpResponse deleteFlagData(FlagId flagId) { + flagsDb.removeValue(flagId); + return new OKResponse(); + } + + private FlagId findFlagId(HttpRequest request, Path path) { + FlagId flagId = new FlagId(path.get("flagId")); + if (!isForce(request) && Flags.getFlag(flagId).isEmpty()) { + throw new IllegalArgumentException("There is no flag '" + flagId + "' (use ?force=true to override)"); + } + return flagId; + } + + private boolean isForce(HttpRequest request) { + return Objects.equals(request.getProperty("force"), "true"); + } + +} |