diff --git a/.gitignore b/.gitignore index 7d8575a..d8b8350 100644 --- a/.gitignore +++ b/.gitignore @@ -1,2 +1,4 @@ go.sum .agollo +.idea +sandbox \ No newline at end of file diff --git a/agollo.go b/agollo.go index bf3fbf0..5726cee 100644 --- a/agollo.go +++ b/agollo.go @@ -77,6 +77,7 @@ func NewWithConfigFile(configFilePath string, opts ...Option) (Agollo, error) { Cluster string `json:"cluster,omitempty"` NamespaceNames []string `json:"namespaceNames,omitempty"` IP string `json:"ip,omitempty"` + AccessKey string `json:"accessKey,omitempty"` } if err := json.NewDecoder(f).Decode(&conf); err != nil { return nil, err @@ -89,6 +90,7 @@ func NewWithConfigFile(configFilePath string, opts ...Option) (Agollo, error) { []Option{ Cluster(conf.Cluster), PreloadNamespaces(conf.NamespaceNames...), + AccessKey(conf.AccessKey), }, opts..., )..., diff --git a/apollo_client.go b/apollo_client.go index 0a8969f..3b484d5 100644 --- a/apollo_client.go +++ b/apollo_client.go @@ -1,6 +1,9 @@ package agollo import ( + "crypto/hmac" + "crypto/sha1" + "encoding/base64" "encoding/json" "fmt" "io/ioutil" @@ -74,6 +77,7 @@ type apolloClient struct { Doer Doer IP string ConfigType string // 默认properties不需要在namespace后加后缀名,其他情况例如application.json {xml,yml,yaml,json,...} + AccessKey string } type ApolloClientOption func(*apolloClient) @@ -96,6 +100,12 @@ func WithConfigType(configType string) ApolloClientOption { } } +func WithAccessKey(accessKey string) ApolloClientOption { + return func(a *apolloClient) { + a.AccessKey = accessKey + } +} + func NewApolloClient(opts ...ApolloClientOption) ApolloClient { c := &apolloClient{} for _, opt := range opts { @@ -119,16 +129,50 @@ func NewApolloClient(opts ...ApolloClientOption) ApolloClient { return c } +const ( + AUTHORIZATION_FORMAT = "Apollo %s:%s" + DELIMITER = "\n" + HTTP_HEADER_AUTHORIZATION = "Authorization" + HTTP_HEADER_TIMESTAMP = "Timestamp" +) + +func signature(timestamp, url, accessKey string) string { + + stringToSign := timestamp + DELIMITER + url + + key := []byte(accessKey) + mac := hmac.New(sha1.New, key) + mac.Write([]byte(stringToSign)) + return base64.StdEncoding.EncodeToString(mac.Sum(nil)) +} + +func (c *apolloClient) httpHeader(appID, uri string) map[string]string { + + headers := map[string]string{} + if "" == c.AccessKey { + return headers + } + + timestamp := fmt.Sprintf("%v", time.Now().UnixNano()/int64(time.Millisecond)) + signature := signature(timestamp, uri, c.AccessKey) + + headers[HTTP_HEADER_AUTHORIZATION] = fmt.Sprintf(AUTHORIZATION_FORMAT, appID, signature) + headers[HTTP_HEADER_TIMESTAMP] = timestamp + + return headers +} + func (c *apolloClient) Notifications(configServerURL, appID, cluster string, notifications []Notification) (status int, result []Notification, err error) { configServerURL = normalizeURL(configServerURL) - url := fmt.Sprintf("%s/notifications/v2?appId=%s&cluster=%s¬ifications=%s", - configServerURL, + requestURI := fmt.Sprintf("/notifications/v2?appId=%s&cluster=%s¬ifications=%s", url.QueryEscape(appID), url.QueryEscape(cluster), url.QueryEscape(Notifications(notifications).String()), ) + apiURL := fmt.Sprintf("%s%s", configServerURL, requestURI) - status, err = c.do("GET", url, &result) + headers := c.httpHeader(appID, requestURI) + status, err = c.do("GET", apiURL, headers, &result) return } @@ -139,51 +183,60 @@ func (c *apolloClient) GetConfigsFromNonCache(configServerURL, appID, cluster, n } configServerURL = normalizeURL(configServerURL) - url := fmt.Sprintf("%s/configs/%s/%s/%s?releaseKey=%s&ip=%s", - configServerURL, + requestURI := fmt.Sprintf("/configs/%s/%s/%s?releaseKey=%s&ip=%s", url.QueryEscape(appID), url.QueryEscape(cluster), url.QueryEscape(c.getNamespace(namespace)), options.ReleaseKey, c.IP, ) + apiURL := fmt.Sprintf("%s%s", configServerURL, requestURI) + headers := c.httpHeader(appID, requestURI) config = new(Config) - status, err = c.do("GET", url, config) + status, err = c.do("GET", apiURL, headers, config) return } func (c *apolloClient) GetConfigsFromCache(configServerURL, appID, cluster, namespace string) (config Configurations, err error) { configServerURL = normalizeURL(configServerURL) - url := fmt.Sprintf("%s/configfiles/json/%s/%s/%s?ip=%s", - configServerURL, + requestURI := fmt.Sprintf("/configfiles/json/%s/%s/%s?ip=%s", url.QueryEscape(appID), url.QueryEscape(cluster), url.QueryEscape(c.getNamespace(namespace)), c.IP, ) + apiURL := fmt.Sprintf("%s%s", configServerURL, requestURI) + headers := c.httpHeader(appID, requestURI) config = make(Configurations) - _, err = c.do("GET", url, config) + _, err = c.do("GET", apiURL, headers, config) return } func (c *apolloClient) GetConfigServers(metaServerURL, appID string) (int, []ConfigServer, error) { metaServerURL = normalizeURL(metaServerURL) - url := fmt.Sprintf("%s/services/config?id=%s&appId=%s", metaServerURL, c.IP, appID) + requestURI := fmt.Sprintf("/services/config?id=%s&appId=%s", c.IP, appID) + apiURL := fmt.Sprintf("%s%s", metaServerURL, requestURI) + + headers := c.httpHeader(appID, requestURI) var cfs []ConfigServer - status, err := c.do("GET", url, &cfs) + status, err := c.do("GET", apiURL, headers, &cfs) return status, cfs, err } -func (c *apolloClient) do(method, url string, v interface{}) (status int, err error) { +func (c *apolloClient) do(method, url string, headers map[string]string, v interface{}) (status int, err error) { var req *http.Request req, err = http.NewRequest(method, url, nil) if err != nil { return } + for key, val := range headers { + req.Header.Set(key, val) + } + var body []byte status, body, err = parseResponseBody(c.Doer, req) if err != nil { diff --git a/options.go b/options.go index 77f22da..d47a52a 100644 --- a/options.go +++ b/options.go @@ -29,6 +29,7 @@ type Options struct { Balancer Balancer // ConfigServer负载均衡 EnableSLB bool // 启用ConfigServer负载均衡 RefreshIntervalInSecond time.Duration // ConfigServer刷新间隔 + AccessKey string } func newOptions(configServerURL, appID string, opts ...Option) (Options, error) { @@ -36,7 +37,7 @@ func newOptions(configServerURL, appID string, opts ...Option) (Options, error) AppID: appID, Cluster: defaultCluster, DefaultNamespace: defaultNamespace, - ApolloClient: NewApolloClient(), + // ApolloClient: NewApolloClient(), Logger: NewLogger(), AutoFetchOnCacheMiss: defaultAutoFetchOnCacheMiss, LongPollerInterval: defaultLongPollInterval, @@ -48,6 +49,8 @@ func newOptions(configServerURL, appID string, opts ...Option) (Options, error) opt(&options) } + options.ApolloClient = NewApolloClient(WithAccessKey(options.AccessKey)) + if options.Balancer == nil { var b Balancer configServerURLs := getConfigServers(configServerURL) @@ -168,6 +171,12 @@ func ConfigServerRefreshIntervalInSecond(refreshIntervalInSecond time.Duration) } } +func AccessKey(accessKey string) Option { + return func(o *Options) { + o.AccessKey = accessKey + } +} + type GetOptions struct { DefaultValue string Namespace string