aboutsummaryrefslogtreecommitdiffstats
path: root/container-core/src/main/java/com/yahoo/container/jdisc/VespaHeaders.java
blob: 935a522e1a4c1803a7c80b66c1981684d3d886ad (plain) (blame)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
// Copyright Vespa.ai. 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<Boolean, Integer> 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<? extends ErrorMessage> allErrors) {
        // Do note, SearchResponse has its own implementation of isSuccess()
        if (isSuccess) {
            Tuple2<Boolean, Integer> status = webServiceCodes(mainError, allErrors);
            if (status.first) {
                return status.second;
            } else {
                return Response.Status.OK;
            }
        }
        return getEagerErrorStatus(mainError, allErrors);
    }

    private static Tuple2<Boolean, Integer> webServiceCodes(ErrorMessage mainError, Iterator<? extends ErrorMessage> allErrors) {
        if (mainError == null) return NO_MATCH;

        Iterator<? extends ErrorMessage> errorIterator = allErrors;
        if (errorIterator != null && errorIterator.hasNext()) {
            while (errorIterator.hasNext()) {
                ErrorMessage error = errorIterator.next();
                Tuple2<Boolean, Integer> status = chooseWebServiceStatus(error);
                if (status.first) {
                    return status;
                }
            }
        } else {
            Tuple2<Boolean, Integer> status = chooseWebServiceStatus(mainError);
            if (status.first) {
                return status;
            }
        }
        return NO_MATCH;
    }


    private static Tuple2<Boolean, Integer> 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<? extends ErrorMessage> 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<Boolean, Integer> status = chooseStatusFromError(error);
                if (status.first) {
                    return status.second;
                }
            }
        } else {
            Tuple2<Boolean, Integer> status = chooseStatusFromError(mainError);
            if (status.first) {
                return status.second;
            }
        }

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

    private static Tuple2<Boolean, Integer> chooseStatusFromError(ErrorMessage error) {

        Tuple2<Boolean, Integer> 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;
    }

}