diff --git a/go.mod b/go.mod index 68a423b..0ecb093 100644 --- a/go.mod +++ b/go.mod @@ -3,10 +3,11 @@ module github.com/devcyclehq/sdk-proxy go 1.20 require ( - github.com/devcyclehq/go-server-sdk/v2 v2.17.0 + github.com/devcyclehq/go-server-sdk/v2 v2.18.1 github.com/gin-gonic/gin v1.10.0 github.com/kelseyhightower/envconfig v1.4.0 github.com/kr/pretty v0.3.1 + github.com/launchdarkly/eventsource v1.7.1 github.com/stretchr/testify v1.9.0 ) @@ -27,7 +28,6 @@ require ( github.com/json-iterator/go v1.1.12 // indirect github.com/klauspost/cpuid/v2 v2.2.7 // indirect github.com/kr/text v0.2.0 // indirect - github.com/launchdarkly/eventsource v1.7.1 // indirect github.com/leodido/go-urn v1.4.0 // indirect github.com/matryer/try v0.0.0-20161228173917-9ac251b645a2 // indirect github.com/mattn/go-isatty v0.0.20 // indirect diff --git a/go.sum b/go.sum index 244a1ee..fb1e0ac 100644 --- a/go.sum +++ b/go.sum @@ -11,10 +11,14 @@ github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ3 github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= -github.com/devcyclehq/go-server-sdk/v2 v2.16.1 h1:NZ0vHZhhrPOb2hbU/r5sIIDSG4tPc+TiO/ovJaP0Gk8= -github.com/devcyclehq/go-server-sdk/v2 v2.16.1/go.mod h1:DzKrJ4s2apfphFwB/Aq8YDf7brB+NDr6IxX0TNi2c24= -github.com/devcyclehq/go-server-sdk/v2 v2.17.0 h1:eBvoJesVPYvy7htJBjhIWRGbQq5fsmlcv4nvsrHqPDU= -github.com/devcyclehq/go-server-sdk/v2 v2.17.0/go.mod h1:DzKrJ4s2apfphFwB/Aq8YDf7brB+NDr6IxX0TNi2c24= +github.com/devcyclehq/go-server-sdk/v2 v2.18.0 h1:9STdu/bTnrjM1cFh3OJddO4nxAaAUqlOAO180x6SWpk= +github.com/devcyclehq/go-server-sdk/v2 v2.18.0/go.mod h1:DzKrJ4s2apfphFwB/Aq8YDf7brB+NDr6IxX0TNi2c24= +github.com/devcyclehq/go-server-sdk/v2 v2.18.1-0.20240822213335-a3201264237c h1:HABqfIWonwH731k8kd+yYLExjsuVH5S+xL9Ed3Kywhw= +github.com/devcyclehq/go-server-sdk/v2 v2.18.1-0.20240822213335-a3201264237c/go.mod h1:DzKrJ4s2apfphFwB/Aq8YDf7brB+NDr6IxX0TNi2c24= +github.com/devcyclehq/go-server-sdk/v2 v2.18.1-0.20240823134625-106c09774905 h1:tOTq2TbgeYtdFuaOl71Sups5p8setOA5VHan6DRGOnU= +github.com/devcyclehq/go-server-sdk/v2 v2.18.1-0.20240823134625-106c09774905/go.mod h1:DzKrJ4s2apfphFwB/Aq8YDf7brB+NDr6IxX0TNi2c24= +github.com/devcyclehq/go-server-sdk/v2 v2.18.1 h1:4Rwxr0Sx4S56zGgSqm0C06Xj88038iYVbWZinpPx8C4= +github.com/devcyclehq/go-server-sdk/v2 v2.18.1/go.mod h1:DzKrJ4s2apfphFwB/Aq8YDf7brB+NDr6IxX0TNi2c24= github.com/gabriel-vasile/mimetype v1.4.3 h1:in2uUcidCuFcDKtdcBxlR0rJ1+fsokWf+uqxgUFjbI0= github.com/gabriel-vasile/mimetype v1.4.3/go.mod h1:d8uq/6HKRL6CGdk+aubisF/M5GcPfT7nKyLpA0lbSSk= github.com/gin-contrib/sse v0.1.0 h1:Y/yl/+YNO8GZSjAhjMsSuLt29uWRFHdHYUb5lYOV9qE= diff --git a/http_endpoints.go b/http_endpoints.go index d3ce2f5..465f2e6 100644 --- a/http_endpoints.go +++ b/http_endpoints.go @@ -111,53 +111,58 @@ func BatchEvents() gin.HandlerFunc { } } -func GetConfig() gin.HandlerFunc { +func GetConfig(client *devcycle.Client, version ...string) gin.HandlerFunc { return func(c *gin.Context) { instance := c.Value("instance").(*ProxyInstance) - client := c.Value("devcycle").(*devcycle.Client) if c.Param("sdkKey") == "" || !strings.HasSuffix(c.Param("sdkKey"), ".json") { c.AbortWithStatus(http.StatusForbidden) return } - var ret []byte - rawConfig, etag, err := client.GetRawConfig() - if err != nil { - c.JSON(http.StatusInternalServerError, gin.H{}) - return - } - if instance.SSEEnabled { - config := map[string]interface{}{} - err = json.Unmarshal(rawConfig, &config) + var ret, rawConfig []byte + var etag, lm string + var err error + if client != nil { + rawConfig, etag, lm, err = client.GetRawConfig() if err != nil { c.JSON(http.StatusInternalServerError, gin.H{}) return } - hostname := fmt.Sprintf("http://%s:%d", instance.SSEHostname, instance.HTTPPort) - // This is the only indicator that a unix socket request was made - if c.Request.RemoteAddr == "" { - hostname = fmt.Sprintf("unix:%s", instance.UnixSocketPath) - } - fmt.Println(c.Request) - if val, ok := config["sse"]; ok { - path := val.(map[string]interface{})["path"].(string) - - config["sse"] = devcycle_api.SSEHost{ - Hostname: hostname, - Path: path, + if instance.SSEEnabled { + config := map[string]interface{}{} + err = json.Unmarshal(rawConfig, &config) + if err != nil { + c.JSON(http.StatusInternalServerError, gin.H{}) + return + } + hostname := fmt.Sprintf("http://%s:%d", instance.SSEHostname, instance.HTTPPort) + // This is the only indicator that a unix socket request was made + if c.Request.RemoteAddr == "" { + hostname = fmt.Sprintf("unix:%s", instance.UnixSocketPath) + } + fmt.Println(c.Request) + if val, ok := config["sse"]; ok { + path := val.(map[string]interface{})["path"].(string) + + config["sse"] = devcycle_api.SSEHost{ + Hostname: hostname, + Path: path, + } } - } - - ret, err = json.Marshal(config) - } else { - ret = rawConfig - } - if err != nil { - c.JSON(http.StatusInternalServerError, gin.H{}) - return + ret, err = json.Marshal(config) + } else { + ret = rawConfig + } + if err != nil { + c.JSON(http.StatusInternalServerError, gin.H{}) + return + } + } else if client == nil && len(version) > 0 { + ret, etag, lm = instance.BypassSDKConfig(version[0]) } c.Header("ETag", etag) + c.Header("Last-Modified", lm) c.Data(http.StatusOK, "application/json", ret) } } diff --git a/options.go b/options.go index f94c333..21a9de8 100644 --- a/options.go +++ b/options.go @@ -5,7 +5,9 @@ import ( "fmt" "github.com/devcyclehq/go-server-sdk/v2/api" "github.com/launchdarkly/eventsource" + "io" "log" + "net/http" "os" "runtime" "time" @@ -38,6 +40,7 @@ type ProxyInstance struct { dvcClient *devcycle.Client sseServer *eventsource.Server sseEvents chan api.ClientEvent + bypassConfig []byte } type SDKConfig struct { @@ -73,6 +76,7 @@ func (i *ProxyInstance) BuildDevCycleOptions() *devcycle.Options { EnableBetaRealtimeUpdates: i.SSEEnabled, AdvancedOptions: devcycle.AdvancedOptions{ OverridePlatformData: &i.PlatformData, + OverrideConfigWithV1: false, }, ClientEventHandler: i.sseEvents, } @@ -80,6 +84,37 @@ func (i *ProxyInstance) BuildDevCycleOptions() *devcycle.Options { return &options } +func (i *ProxyInstance) BypassSDKConfig(version string) (config []byte, etag, lastModified string) { + + request, err := http.NewRequest("GET", fmt.Sprintf("https://config-cdn.devcycle.com/config/%s/server/%s.json", version, i.SDKKey), nil) + if err != nil { + return i.bypassConfig, "", "" + } + + resp, err := http.DefaultClient.Do(request) + + if err != nil { + return i.bypassConfig, "", "" + } + + if resp.StatusCode == http.StatusNotModified { + return i.bypassConfig, resp.Header.Get("ETag"), resp.Header.Get("Last-Modified") + } + + if resp.StatusCode != http.StatusOK { + return i.bypassConfig, "", "" + } + + body, err := io.ReadAll(resp.Body) + defer resp.Body.Close() + if err != nil { + return i.bypassConfig, "", "" + } + + i.bypassConfig = body + return i.bypassConfig, resp.Header.Get("ETag"), resp.Header.Get("Last-Modified") +} + func (i *ProxyInstance) EventRebroadcaster() { for event := range i.sseEvents { if event.EventType == api.ClientEventType_RealtimeUpdates { diff --git a/proxy.go b/proxy.go index c14c36e..5036c76 100644 --- a/proxy.go +++ b/proxy.go @@ -35,6 +35,9 @@ func NewBucketingProxyInstance(instance *ProxyInstance) (*ProxyInstance, error) options := instance.BuildDevCycleOptions() client, err := devcycle.NewClient(instance.SDKKey, options) + if err != nil { + return nil, fmt.Errorf("error creating DevCycle client: %v", err) + } instance.dvcClient = client r := newRouter(client, instance) @@ -116,9 +119,12 @@ func newRouter(client *devcycle.Client, instance *ProxyInstance) *gin.Engine { } configCDNv1 := r.Group("/config/v1") { - configCDNv1.GET("/server/:sdkKey", GetConfig()) + configCDNv1.GET("/server/:sdkKey", GetConfig(nil, "v1")) + } + configCDNv2 := r.Group("/config/v2") + { + configCDNv2.GET("/server/:sdkKey", GetConfig(client)) } - r.GET("/event-stream", SSE()) return r