All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.yahoo.vespa.configserver.flags.http.FlagsHandler Maven / Gradle / Ivy

There is a newer version: 8.444.18
Show newest version
// Copyright Vespa.ai. 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.yahoo.component.annotation.Inject;
import com.yahoo.container.jdisc.HttpRequest;
import com.yahoo.container.jdisc.HttpResponse;
import com.yahoo.container.jdisc.ThreadedHttpRequestHandler;
import com.yahoo.restapi.ErrorResponse;
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;
import java.util.logging.Level;

/**
 * Handles /flags/v1 requests
 *
 * @author hakonhall
 */
public class FlagsHandler extends ThreadedHttpRequestHandler {

    private final FlagsDb flagsDb;

    @Inject
    public FlagsHandler(ThreadedHttpRequestHandler.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(Level.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.getAllFlagData(),
                                        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");
    }

}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy