1 package io.getunleash.repository;
2
3 import io.getunleash.UnleashException;
4 import io.getunleash.util.UnleashConfig;
5 import java.io.BufferedReader;
6 import java.io.IOException;
7 import java.io.InputStream;
8 import java.io.InputStreamReader;
9 import java.net.HttpURLConnection;
10 import java.net.URL;
11 import java.nio.charset.StandardCharsets;
12 import java.util.Optional;
13 import org.slf4j.Logger;
14 import org.slf4j.LoggerFactory;
15
16 public class HttpFeatureFetcher implements FeatureFetcher {
17     private static final Logger LOG = LoggerFactory.getLogger(HttpFeatureFetcher.class);
18     private Optional<String> etag = Optional.empty();
19
20     private final UnleashConfig config;
21
22     private final URL toggleUrl;
23
24     public HttpFeatureFetcher(UnleashConfig config) {
25         this.config = config;
26         this.toggleUrl =
27                 config.getUnleashURLs()
28                         .getFetchTogglesURL(config.getProjectName(), config.getNamePrefix());
29     }
30
31     @Override
32     public ClientFeaturesResponse fetchFeatures() throws UnleashException {
33         HttpURLConnection connection = null;
34         try {
35             connection = openConnection(this.toggleUrl);
36             connection.connect();
37
38             return getFeatureResponse(connection, true);
39         } catch (IOException e) {
40             throw new UnleashException("Could not fetch toggles", e);
41         } catch (IllegalStateException e) {
42             throw new UnleashException(e.getMessage(), e);
43         } finally {
44             if (connection != null) {
45                 connection.disconnect();
46             }
47         }
48     }
49
50     private ClientFeaturesResponse getFeatureResponse(
51             HttpURLConnection request, boolean followRedirect) throws IOException {
52         int responseCode = request.getResponseCode();
53
54         if (responseCode < 300) {
55             etag = Optional.ofNullable(request.getHeaderField("ETag"));
56
57             try (BufferedReader reader =
58                     new BufferedReader(
59                             new InputStreamReader(
60                                     (InputStream) request.getContent(), StandardCharsets.UTF_8))) {
61
62                 FeatureCollection features = JsonFeatureParser.fromJson(reader);
63                 return new ClientFeaturesResponse(
64                         ClientFeaturesResponse.Status.CHANGED,
65                         features.getToggleCollection(),
66                         features.getSegmentCollection());
67             }
68         } else if (followRedirect
69                 && (responseCode == HttpURLConnection.HTTP_MOVED_TEMP
70                         || responseCode == HttpURLConnection.HTTP_MOVED_PERM
71                         || responseCode == HttpURLConnection.HTTP_SEE_OTHER)) {
72             return followRedirect(request);
73         } else if (responseCode == HttpURLConnection.HTTP_NOT_MODIFIED) {
74             return new ClientFeaturesResponse(
75                     ClientFeaturesResponse.Status.NOT_CHANGED, responseCode);
76         } else {
77             return new ClientFeaturesResponse(
78                     ClientFeaturesResponse.Status.UNAVAILABLE,
79                     responseCode,
80                     getLocationHeader(request));
81         }
82     }
83
84     private ClientFeaturesResponse followRedirect(HttpURLConnection request) throws IOException {
85         String newUrl = getLocationHeader(request);
86
87         request = openConnection(new URL(newUrl));
88         request.connect();
89         LOG.info(
90                 "Redirecting from {} to {}. Please consider updating your config.",
91                 this.toggleUrl,
92                 newUrl);
93
94         return getFeatureResponse(request, false);
95     }
96
97     private String getLocationHeader(HttpURLConnection connection) {
98         return connection.getHeaderField("Location");
99     }
100
101     private HttpURLConnection openConnection(URL url) throws IOException {
102         HttpURLConnection connection;
103         if (this.config.getProxy() != null) {
104             connection = (HttpURLConnection) url.openConnection(this.config.getProxy());
105         } else {
106             connection = (HttpURLConnection) url.openConnection();
107         }
108         connection.setConnectTimeout((intthis.config.getFetchTogglesConnectTimeout().toMillis());
109         connection.setReadTimeout((intthis.config.getFetchTogglesReadTimeout().toMillis());
110         connection.setRequestProperty("Accept""application/json");
111         connection.setRequestProperty("Content-Type""application/json");
112         UnleashConfig.setRequestProperties(connection, this.config);
113
114         etag.ifPresent(val -> connection.setRequestProperty("If-None-Match", val));
115
116         connection.setUseCaches(true);
117
118         return connection;
119     }
120 }
121