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((int) this.config.getFetchTogglesConnectTimeout().toMillis());
109 connection.setReadTimeout((int) this.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