1 package datasource
2
3 import (
4 "fmt"
5 "io"
6 "net/http"
7
8 "github.com/launchdarkly/go-sdk-common/v3/ldlog"
9 "github.com/launchdarkly/go-server-sdk/v6/internal/endpoints"
10 "github.com/launchdarkly/go-server-sdk/v6/subsystems"
11 "github.com/launchdarkly/go-server-sdk/v6/subsystems/ldstoretypes"
12
13 "github.com/launchdarkly/go-jsonstream/v3/jreader"
14
15 "github.com/gregjones/httpcache"
16 "golang.org/x/exp/maps"
17 )
18
19
20 type requestor interface {
21 requestAll() (data []ldstoretypes.Collection, cached bool, err error)
22 }
23
24
25 type requestorImpl struct {
26 httpClient *http.Client
27 baseURI string
28 headers http.Header
29 loggers ldlog.Loggers
30 }
31
32 type malformedJSONError struct {
33 innerError error
34 }
35
36 func (e malformedJSONError) Error() string {
37 return e.innerError.Error()
38 }
39
40 func newRequestorImpl(
41 context subsystems.ClientContext,
42 httpClient *http.Client,
43 baseURI string,
44 ) requestor {
45 if httpClient == nil {
46 httpClient = context.GetHTTP().CreateHTTPClient()
47 }
48
49 modifiedClient := *httpClient
50 modifiedClient.Transport = &httpcache.Transport{
51 Cache: httpcache.NewMemoryCache(),
52 MarkCachedResponses: true,
53 Transport: httpClient.Transport,
54 }
55
56 return &requestorImpl{
57 httpClient: &modifiedClient,
58 baseURI: baseURI,
59 headers: context.GetHTTP().DefaultHeaders,
60 loggers: context.GetLogging().Loggers,
61 }
62 }
63
64 func (r *requestorImpl) requestAll() ([]ldstoretypes.Collection, bool, error) {
65 if r.loggers.IsDebugEnabled() {
66 r.loggers.Debug("Polling LaunchDarkly for feature flag updates")
67 }
68
69 body, cached, err := r.makeRequest(endpoints.PollingRequestPath)
70 if err != nil {
71 return nil, false, err
72 }
73 if cached {
74 return nil, true, nil
75 }
76
77 reader := jreader.NewReader(body)
78 data := parseAllStoreDataFromJSONReader(&reader)
79 if err := reader.Error(); err != nil {
80 return nil, false, malformedJSONError{err}
81 }
82 return data, cached, nil
83 }
84
85 func (r *requestorImpl) makeRequest(resource string) ([]byte, bool, error) {
86 req, reqErr := http.NewRequest("GET", endpoints.AddPath(r.baseURI, resource), nil)
87 if reqErr != nil {
88 reqErr = fmt.Errorf(
89 "unable to create a poll request; this is not a network problem, most likely a bad base URI: %w",
90 reqErr,
91 )
92 return nil, false, reqErr
93 }
94 url := req.URL.String()
95 if r.headers != nil {
96 req.Header = maps.Clone(r.headers)
97 }
98
99 res, resErr := r.httpClient.Do(req)
100
101 if resErr != nil {
102 return nil, false, resErr
103 }
104
105 defer func() {
106 _, _ = io.ReadAll(res.Body)
107 _ = res.Body.Close()
108 }()
109
110 if err := checkForHTTPError(res.StatusCode, url); err != nil {
111 return nil, false, err
112 }
113
114 cached := res.Header.Get(httpcache.XFromCache) != ""
115
116 body, ioErr := io.ReadAll(res.Body)
117
118 if ioErr != nil {
119 return nil, false, ioErr
120 }
121 return body, cached, nil
122 }
123
View as plain text