aboutsummaryrefslogtreecommitdiffstats
path: root/configserver-client/src/main/java/ai/vespa/hosted/client/ConfigServerClient.java
blob: 3b24f1a568e09130dfec578f93efec819411873a (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
// Copyright Verizon Media. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
package ai.vespa.hosted.client;

import org.apache.hc.client5.http.config.RequestConfig;
import org.apache.hc.core5.http.ClassicHttpResponse;
import org.apache.hc.core5.http.HttpEntity;
import org.apache.hc.core5.http.Method;

import java.io.IOException;
import java.io.InputStream;
import java.io.UncheckedIOException;
import java.net.URI;
import java.time.Duration;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
import java.util.function.Function;
import java.util.stream.IntStream;

import static java.util.Objects.requireNonNull;
import static java.util.stream.Collectors.toUnmodifiableList;

/**
 * @author jonmv
 */
public interface ConfigServerClient extends AutoCloseable {

    /** Creates a builder for sending the given method, using the specified host strategy. */
    RequestBuilder send(HostStrategy hosts, Method method);

    /** Builder for a request against a given set of hosts, using this config server client. */
    interface RequestBuilder {

        /** Sets the request path. */
        default RequestBuilder at(String... pathSegments) { return at(List.of(pathSegments)); }

        /** Sets the request path. */
        RequestBuilder at(List<String> pathSegments);

        /** Sets the request body as UTF-8 application/json. */
        RequestBuilder body(byte[] json);

        /** Sets the request body. */
        RequestBuilder body(HttpEntity entity);

        /** Sets the parameter key/values for the request. Number of arguments must be even. */
        default RequestBuilder parameters(String... pairs) {
            return parameters(Arrays.asList(pairs));
        }

        /** Sets the parameter key/values for the request. Number of arguments must be even. */
        RequestBuilder parameters(List<String> pairs);

        /** Overrides the default socket read timeout of the request. {@code Duration.ZERO} gives infinite timeout. */
        RequestBuilder timeout(Duration timeout);

        /** Overrides the default request config of the request. */
        RequestBuilder config(RequestConfig config);

        /**
         * Sets custom retry/failure logic for this.
         * <p>
         * Exactly one of the callbacks will be invoked, with a non-null argument.
         * Return a value to have that returned to the caller;
         * throw a {@link RetryException} to have the request retried; or
         * throw any other unchecked exception to have this propagate out to the caller.
         * The caller must close the provided response, if any.
         */
        <T> T handle(Function<ClassicHttpResponse, T> handler, Function<IOException, T> catcher) throws UncheckedIOException;

        /** Sets the response body mapper for this, for successful requests. */
        <T> T read(Function<byte[], T> mapper) throws UncheckedIOException, ResponseException;

        /** Discards the response, but throws if the response is unsuccessful. */
        void discard() throws UncheckedIOException, ResponseException;

        /** Returns the raw input stream of the response, if successful. The caller must close the returned stream. */
        InputStream stream() throws UncheckedIOException, ResponseException;

    }

    /** Exception wrapper that signals retries should be attempted. */
    final class RetryException extends RuntimeException {

        public RetryException(IOException cause) {
            super(requireNonNull(cause));
        }

        public RetryException(RuntimeException cause) {
            super(requireNonNull(cause));
        }

    }

    /** What host(s) to try for a request, in what order. A host may be specified multiple times, for retries.  */
    @FunctionalInterface
    interface HostStrategy extends Iterable<URI> {

        /** Attempts each request once against each listed host. */
        static HostStrategy ordered(List<URI> hosts) {
            return List.copyOf(hosts)::iterator;
        }

        /** Attempts each request once against each listed host, in random order. */
        static HostStrategy shuffling(List<URI> hosts) {
            return () -> {
                List<URI> copy = new ArrayList<>(hosts);
                Collections.shuffle(copy);
                return copy.iterator();
            };
        }

        /** Attempts each request against the host the specified number of times. */
        static HostStrategy repeating(URI host, int count) {
            return ordered(IntStream.range(0, count).mapToObj(__ -> host).collect(toUnmodifiableList()));
        }

    }

    /** An exception due to server error, a bad request, or similar, which resulted in a non-OK HTTP response. */
    class ResponseException extends RuntimeException {

        private final int code;
        private final String body;

        public ResponseException(int code, String body, String context) {
            super(context + ": " + body);
            this.code = code;
            this.body = body;
        }

        public int code() { return code; }

        public String body() { return body; }

    }

}