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

com.yahoo.container.jdisc.VespaHeaders Maven / Gradle / Ivy

// Copyright 2017 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
package com.yahoo.container.jdisc;


import static com.yahoo.container.protect.Error.BACKEND_COMMUNICATION_ERROR;
import static com.yahoo.container.protect.Error.BAD_REQUEST;
import static com.yahoo.container.protect.Error.FORBIDDEN;
import static com.yahoo.container.protect.Error.ILLEGAL_QUERY;
import static com.yahoo.container.protect.Error.INSUFFICIENT_STORAGE;
import static com.yahoo.container.protect.Error.INTERNAL_SERVER_ERROR;
import static com.yahoo.container.protect.Error.INVALID_QUERY_PARAMETER;
import static com.yahoo.container.protect.Error.NOT_FOUND;
import static com.yahoo.container.protect.Error.NO_BACKENDS_IN_SERVICE;
import static com.yahoo.container.protect.Error.TIMEOUT;
import static com.yahoo.container.protect.Error.UNAUTHORIZED;

import java.util.Iterator;

import com.yahoo.collections.Tuple2;
import com.yahoo.container.handler.Coverage;
import com.yahoo.container.handler.Timing;
import com.yahoo.container.http.BenchmarkingHeaders;
import com.yahoo.container.logging.HitCounts;
import com.yahoo.jdisc.HeaderFields;
import com.yahoo.jdisc.Response;
import com.yahoo.processing.request.ErrorMessage;

/**
 * Static helper methods which implement the mapping between the ErrorMessage
 * API and HTTP headers and return codes.
 *
 * @author Einar M R Rosenvinge
 * @author Steinar Knutsen
 * @author Simon Thoresen Hult
 * @author bratseth
 */
public final class VespaHeaders {

    // response not directly supported by JDisc core
    private static final int GATEWAY_TIMEOUT = 504;
    private static final int BAD_GATEWAY = 502;
    private static final int PRECONDITION_REQUIRED = 428;
    private static final int TOO_MANY_REQUESTS = 429;
    private static final int REQUEST_HEADER_FIELDS_TOO_LARGE = 431;
    private static final int NETWORK_AUTHENTICATION_REQUIRED = 511;

    private static final Tuple2 NO_MATCH = new Tuple2<>(false, Response.Status.OK);

    public static boolean benchmarkCoverage(boolean benchmarkOutput, HeaderFields headers) {
        return benchmarkOutput && headers.get(BenchmarkingHeaders.REQUEST_COVERAGE) != null;
    }

    /** Returns true if this is a benchmarking request, according to headers */
    public static boolean benchmarkOutput(com.yahoo.container.jdisc.HttpRequest request) {
        return request.getHeader(BenchmarkingHeaders.REQUEST) != null;
    }

    /**
     * Add search benchmark output to the HTTP getHeaders.
     *
     * @param responseHeaders   the response to write the headers to
     * @param benchmarkCoverage true to include coverage headers
     * @param t                 the Timing to read data from
     * @param c                 the Counts to read data from
     * @param errorCount        the error count
     * @param coverage          the Coverage to read data from
     */
    public static void benchmarkOutput(HeaderFields responseHeaders, boolean benchmarkCoverage,
                                       Timing t, HitCounts c, int errorCount, Coverage coverage) {
        long renderStartTime = System.currentTimeMillis();
        if (c != null) {
            // Fill inn response getHeaders
            responseHeaders.add(BenchmarkingHeaders.NUM_HITS, String.valueOf(c.getRetrievedHitCount()));
            responseHeaders.add(BenchmarkingHeaders.NUM_FASTHITS, String.valueOf(c.getSummaryCount()));
            responseHeaders.add(BenchmarkingHeaders.TOTAL_HIT_COUNT, String.valueOf(c.getTotalHitCount()));
            responseHeaders.add(BenchmarkingHeaders.QUERY_HITS, String.valueOf(c.getRequestedHits()));
            responseHeaders.add(BenchmarkingHeaders.QUERY_OFFSET, String.valueOf(c.getRequestedOffset()));
        }
        responseHeaders.add(BenchmarkingHeaders.NUM_ERRORS, String.valueOf(errorCount));
        if (t != null) {
            if (t.getSummaryStartTime() != 0) {
                responseHeaders.add(BenchmarkingHeaders.SEARCH_TIME,
                                    String.valueOf(t.getSummaryStartTime() - t.getQueryStartTime()));
                responseHeaders.add(BenchmarkingHeaders.ATTR_TIME, "0");
                responseHeaders.add(BenchmarkingHeaders.FILL_TIME,
                                    String.valueOf(renderStartTime - t.getSummaryStartTime()));
            } else {
                responseHeaders.add(BenchmarkingHeaders.SEARCH_TIME,
                                    String.valueOf(renderStartTime - t.getQueryStartTime()));
                responseHeaders.add(BenchmarkingHeaders.ATTR_TIME, "0");
                responseHeaders.add(BenchmarkingHeaders.FILL_TIME, "0");
            }
        }

        if (benchmarkCoverage && coverage != null) {
            responseHeaders.add(BenchmarkingHeaders.DOCS_SEARCHED, String.valueOf(coverage.getDocs()));
            responseHeaders.add(BenchmarkingHeaders.NODES_SEARCHED, String.valueOf(coverage.getNodes()));
            responseHeaders.add(BenchmarkingHeaders.FULL_COVERAGE, String.valueOf(coverage.getFull() ? 1 : 0));
        }
    }

    /**
     * (during normal execution) return 200 unless this is not a success or a 4xx error is requested.
     *
     * @param isSuccess whether or not the response represents a success
     * @param mainError the main error of the response, if any
     * @param allErrors all the errors of the response, if any
     * @return the status code of the given response
     */
    public static int getStatus(boolean isSuccess, ErrorMessage mainError, Iterator allErrors) {
        // Do note, SearchResponse has its own implementation of isSuccess()
        if (isSuccess) {
            Tuple2 status = webServiceCodes(mainError, allErrors);
            if (status.first) {
                return status.second;
            } else {
                return Response.Status.OK;
            }
        }
        return getEagerErrorStatus(mainError, allErrors);
    }

    private static Tuple2 webServiceCodes(ErrorMessage mainError, Iterator allErrors) {
        if (mainError == null) return NO_MATCH;

        Iterator errorIterator = allErrors;
        if (errorIterator != null && errorIterator.hasNext()) {
            while (errorIterator.hasNext()) {
                ErrorMessage error = errorIterator.next();
                Tuple2 status = chooseWebServiceStatus(error);
                if (status.first) {
                    return status;
                }
            }
        } else {
            Tuple2 status = chooseWebServiceStatus(mainError);
            if (status.first) {
                return status;
            }
        }
        return NO_MATCH;
    }


    private static Tuple2 chooseWebServiceStatus(ErrorMessage error) {
        if (isHttpStatusCode(error.getCode()))
            return new Tuple2<>(true, error.getCode());
        if (error.getCode() == FORBIDDEN.code)
            return new Tuple2<>(true, Response.Status.FORBIDDEN);
        if (error.getCode() == UNAUTHORIZED.code)
            return new Tuple2<>(true, Response.Status.UNAUTHORIZED);
        if (error.getCode() == NOT_FOUND.code)
            return new Tuple2<>(true, Response.Status.NOT_FOUND);
        if (error.getCode() == BAD_REQUEST.code)
            return new Tuple2<>(true, Response.Status.BAD_REQUEST);
        if (error.getCode() == INTERNAL_SERVER_ERROR.code)
            return new Tuple2<>(true, Response.Status.INTERNAL_SERVER_ERROR);
        if (error.getCode() == INSUFFICIENT_STORAGE.code)
            return new Tuple2<>(true, Response.Status.INSUFFICIENT_STORAGE);
        return NO_MATCH;
    }

    // TODO: The status codes in jDisc should be an ENUM so we can enumerate the values
    private static boolean isHttpStatusCode(int code) {
        switch (code) {
            case Response.Status.OK :
            case Response.Status.MOVED_PERMANENTLY :
            case Response.Status.FOUND :
            case Response.Status.TEMPORARY_REDIRECT :
            case Response.Status.BAD_REQUEST :
            case Response.Status.UNAUTHORIZED  :
            case Response.Status.FORBIDDEN :
            case Response.Status.NOT_FOUND :
            case Response.Status.METHOD_NOT_ALLOWED :
            case Response.Status.NOT_ACCEPTABLE :
            case Response.Status.REQUEST_TIMEOUT :
            case Response.Status.INTERNAL_SERVER_ERROR :
            case Response.Status.NOT_IMPLEMENTED :
            case Response.Status.SERVICE_UNAVAILABLE :
            case Response.Status.VERSION_NOT_SUPPORTED :
            case GATEWAY_TIMEOUT :
            case BAD_GATEWAY :
            case PRECONDITION_REQUIRED :
            case TOO_MANY_REQUESTS :
            case REQUEST_HEADER_FIELDS_TOO_LARGE :
            case NETWORK_AUTHENTICATION_REQUIRED :
                return true;
            default:
                return false;
        }
    }


    /**
     * Returns 5xx or 4xx if there is any error present in the result, 200 otherwise
     *
     * @param mainError The main error of the response.
     * @param allErrors All the errors of the response, if any.
     * @return The error status code of the given response.
     */
    public static int getEagerErrorStatus(ErrorMessage mainError, Iterator allErrors) {
        if (mainError == null ) return Response.Status.OK;

        // Iterate over all errors
        if (allErrors != null && allErrors.hasNext()) {
            for (; allErrors.hasNext();) {
                ErrorMessage error = allErrors.next();
                Tuple2 status = chooseStatusFromError(error);
                if (status.first) {
                    return status.second;
                }
            }
        } else {
            Tuple2 status = chooseStatusFromError(mainError);
            if (status.first) {
                return status.second;
            }
        }

        // Default return code for errors
        return Response.Status.INTERNAL_SERVER_ERROR;
    }

    private static Tuple2 chooseStatusFromError(ErrorMessage error) {

        Tuple2 webServiceStatus = chooseWebServiceStatus(error);
        if (webServiceStatus.first) {
            return webServiceStatus;
        }
        if (error.getCode() == NO_BACKENDS_IN_SERVICE.code)
            return new Tuple2<>(true, Response.Status.SERVICE_UNAVAILABLE);
        if (error.getCode() == TIMEOUT.code)
            return new Tuple2<>(true, Response.Status.GATEWAY_TIMEOUT);
        if (error.getCode() == BACKEND_COMMUNICATION_ERROR.code)
            return new Tuple2<>(true, Response.Status.SERVICE_UNAVAILABLE);
        if (error.getCode() == ILLEGAL_QUERY.code)
            return new Tuple2<>(true, Response.Status.BAD_REQUEST);
        if (error.getCode() == INVALID_QUERY_PARAMETER.code)
            return new Tuple2<>(true, Response.Status.BAD_REQUEST);
        return NO_MATCH;
    }

}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy