{"action":"created","comment":{"url":"https://api.github.com/repos/segmentio/consul-go/pulls/comments/146432044","pull_request_review_id":71372955,"id":146432044,"diff_hunk":"@@ -0,0 +1,141 @@\n+package consul\n+\n+import (\n+\t\"context\"\n+\t\"errors\"\n+\t\"net\"\n+\t\"net/http\"\n+\t\"strconv\"\n+\t\"time\"\n+)\n+\n+// WatcherFunc is the function signature for the callback from watch.  It\n+// passes a list of KeyData representing the most recent value or values stored\n+// at the key or everything below the prefix. It can be nil.  error is passed\n+// through to the handler if it is not temporary. Caller is responsible for\n+// stopping the watch if error is determined to be unrecoverable\n+type WatcherFunc func([]KeyData, error)\n+\n+// Watcher is the struct upon which Watch and WatchPrefix are built.\n+type Watcher struct {\n+\tClient *Client\n+\t// MaxAttempts limits the number of subsequent failed API calls before\n+\t// bailing out of the watch.\n+\tMaxAttempts int\n+}\n+\n+var (\n+\tDefaultWatcher = &Watcher{}\n+\n+\t// ErrMaxAttempts gets passed to the handler when the number of subsequent\n+\t// attempts reaches Watcher.MaxAttempts\n+\tErrMaxAttempts = errors.New(\"watcher: max attempts reached. giving up\")\n+)\n+\n+func (w *Watcher) client() *Client {\n+\tif client := w.Client; client != nil {\n+\t\treturn client\n+\t}\n+\t// This is copied from DefaultTransport.\n+\t// we don't do an actual copy because the Trasnport uses mutexes.\n+\t// copied mutexes are ineffective.\n+\t// You will panic on concurrent map access if running\n+\t// with other clients without instantiating a new transport here.\n+\tts := &http.Transport{\n+\t\tDialContext: (&net.Dialer{\n+\t\t\tTimeout: 5 * time.Second,\n+\t\t}).DialContext,\n+\t\tDisableCompression:    true,\n+\t\tMaxIdleConns:          5,\n+\t\tMaxIdleConnsPerHost:   2,\n+\t\tIdleConnTimeout:       90 * time.Second,\n+\t\tTLSHandshakeTimeout:   5 * time.Second,\n+\t\tResponseHeaderTimeout: 10 * time.Minute,\n+\t\tExpectContinueTimeout: 5 * time.Second,\n+\t}\n+\tc := &Client{\n+\t\tTransport: ts,\n+\t}\n+\treturn c\n+}\n+\n+// Watch is the package-level Watch defintion which is called on\n+// DefaultWatcher.\n+func Watch(ctx context.Context, key string, handler WatcherFunc) {\n+\tDefaultWatcher.Watch(ctx, key, handler)\n+}\n+\n+// WatchPrefix is the package-level WatchPrefix defintion which is called on\n+// DefaultWatcher.\n+func WatchPrefix(ctx context.Context, prefix string, handler WatcherFunc) {\n+\tDefaultWatcher.WatchPrefix(ctx, prefix, handler)\n+}\n+\n+// Watch executes a long poll for changes to the given key.  handler will be\n+// called immediately upon registration to initialize the watch and returns\n+// the initial value (as a list, this is what Consul API returns).  There is\n+// a chance that Watch can miss updates due to the way the API is implemented.\n+// See https://github.com/hashicorp/consul/issues/1761 for details.\n+func (w *Watcher) Watch(ctx context.Context, key string, handler WatcherFunc) {\n+\tw.watching(ctx, key, handler, nil)\n+}\n+\n+// WatchPrefix executes a long poll for changes to anything under the given\n+// prefix.  handler will be called immediately upon registration to initialize\n+// the watch and returns the initial value (as a list, this is what Consul API\n+// returns).  There is a chance that WatchPrefix can miss updates due to the\n+// way the API is implemented.  See https://github.com/hashicorp/consul/issues/1761\n+// for details.\n+func (w *Watcher) WatchPrefix(ctx context.Context, prefix string, handler WatcherFunc) {\n+\tq := Query{Param{\n+\t\tName:  \"recurse\",\n+\t\tValue: \"\",\n+\t}}\n+\tw.watching(ctx, prefix, handler, q)\n+}\n+\n+func (w *Watcher) watching(ctx context.Context, key string, handler WatcherFunc, q Query) {\n+\tif w.MaxAttempts <= 0 {\n+\t\tw.MaxAttempts = 10\n+\t}\n+\n+\tpath := \"/v1/kv/\" + key\n+\tvalue := \"0\"\n+\n+\tattempt := 0\n+\tfor {\n+\t\tq = updateParam(q, Param{Name: \"index\", Value: value})\n+\t\tresp := []KeyData{}\n+\t\thdr, err := w.client().do(ctx, \"GET\", path, q, nil, &resp)\n+\t\tif err != nil {\n+\t\t\tattempt += 1\n+\t\t\tif attempt >= w.MaxAttempts {\n+\t\t\t\thandler(nil, ErrMaxAttempts)\n+\t\t\t\treturn\n+\t\t\t}\n+\t\t\tif v, ok := err.(interface {\n+\t\t\t\tTemporary() bool\n+\t\t\t}); ok {\n+\t\t\t\tif v.Temporary() {\n+\t\t\t\t\tcontinue\n+\t\t\t\t}\n+\t\t\t}\n+\t\t} else {\n+\t\t\tattempt = 0\n+\t\t}\n+\t\thandler(resp, err)\n+\t\tvalue = strconv.FormatUint(hdr.index, 10)\n+\t}\n+}\n+\n+func updateParam(q Query, new Param) Query {","path":"watcher.go","position":130,"original_position":131,"commit_id":"76a8267eb8d715397b2c43c54b8d51237f96db9f","original_commit_id":"980161e03b452f2b9d3f459b1cec5354838bbe8e","user":{"login":"achille-roussel","id":865510,"avatar_url":"https://avatars3.githubusercontent.com/u/865510?v=4","gravatar_id":"","url":"https://api.github.com/users/achille-roussel","html_url":"https://github.com/achille-roussel","followers_url":"https://api.github.com/users/achille-roussel/followers","following_url":"https://api.github.com/users/achille-roussel/following{/other_user}","gists_url":"https://api.github.com/users/achille-roussel/gists{/gist_id}","starred_url":"https://api.github.com/users/achille-roussel/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/achille-roussel/subscriptions","organizations_url":"https://api.github.com/users/achille-roussel/orgs","repos_url":"https://api.github.com/users/achille-roussel/repos","events_url":"https://api.github.com/users/achille-roussel/events{/privacy}","received_events_url":"https://api.github.com/users/achille-roussel/received_events","type":"User","site_admin":false},"body":"Is that a problem?","created_at":"2017-10-24T01:03:14Z","updated_at":"2017-10-24T01:03:15Z","html_url":"https://github.com/segmentio/consul-go/pull/33#discussion_r146432044","pull_request_url":"https://api.github.com/repos/segmentio/consul-go/pulls/33","author_association":"CONTRIBUTOR","_links":{"self":{"href":"https://api.github.com/repos/segmentio/consul-go/pulls/comments/146432044"},"html":{"href":"https://github.com/segmentio/consul-go/pull/33#discussion_r146432044"},"pull_request":{"href":"https://api.github.com/repos/segmentio/consul-go/pulls/33"}},"in_reply_to_id":146425847},"pull_request":{"url":"https://api.github.com/repos/segmentio/consul-go/pulls/33","id":146892477,"html_url":"https://github.com/segmentio/consul-go/pull/33","diff_url":"https://github.com/segmentio/consul-go/pull/33.diff","patch_url":"https://github.com/segmentio/consul-go/pull/33.patch","issue_url":"https://api.github.com/repos/segmentio/consul-go/issues/33","number":33,"state":"open","locked":false,"title":"Add 👀r","user":{"login":"abraithwaite","id":3791413,"avatar_url":"https://avatars0.githubusercontent.com/u/3791413?v=4","gravatar_id":"","url":"https://api.github.com/users/abraithwaite","html_url":"https://github.com/abraithwaite","followers_url":"https://api.github.com/users/abraithwaite/followers","following_url":"https://api.github.com/users/abraithwaite/following{/other_user}","gists_url":"https://api.github.com/users/abraithwaite/gists{/gist_id}","starred_url":"https://api.github.com/users/abraithwaite/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/abraithwaite/subscriptions","organizations_url":"https://api.github.com/users/abraithwaite/orgs","repos_url":"https://api.github.com/users/abraithwaite/repos","events_url":"https://api.github.com/users/abraithwaite/events{/privacy}","received_events_url":"https://api.github.com/users/abraithwaite/received_events","type":"User","site_admin":false},"body":"What's left?\r\n\r\n- [x] Error handling\r\n- [x] `http.Client` header timeout setting\r\n- [x] Retries?\r\n- [x] Cleaner tests\r\n\r\nLooking for any tips/suggestions on the above.","created_at":"2017-10-16T21:25:07Z","updated_at":"2017-10-24T01:03:15Z","closed_at":null,"merged_at":null,"merge_commit_sha":"9693b06fb98f0ed8f395146d66689239bcbf2f3e","assignee":null,"assignees":[],"requested_reviewers":[{"login":"dfuentes","id":388569,"avatar_url":"https://avatars2.githubusercontent.com/u/388569?v=4","gravatar_id":"","url":"https://api.github.com/users/dfuentes","html_url":"https://github.com/dfuentes","followers_url":"https://api.github.com/users/dfuentes/followers","following_url":"https://api.github.com/users/dfuentes/following{/other_user}","gists_url":"https://api.github.com/users/dfuentes/gists{/gist_id}","starred_url":"https://api.github.com/users/dfuentes/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/dfuentes/subscriptions","organizations_url":"https://api.github.com/users/dfuentes/orgs","repos_url":"https://api.github.com/users/dfuentes/repos","events_url":"https://api.github.com/users/dfuentes/events{/privacy}","received_events_url":"https://api.github.com/users/dfuentes/received_events","type":"User","site_admin":false},{"login":"systemizer","id":625343,"avatar_url":"https://avatars1.githubusercontent.com/u/625343?v=4","gravatar_id":"","url":"https://api.github.com/users/systemizer","html_url":"https://github.com/systemizer","followers_url":"https://api.github.com/users/systemizer/followers","following_url":"https://api.github.com/users/systemizer/following{/other_user}","gists_url":"https://api.github.com/users/systemizer/gists{/gist_id}","starred_url":"https://api.github.com/users/systemizer/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/systemizer/subscriptions","organizations_url":"https://api.github.com/users/systemizer/orgs","repos_url":"https://api.github.com/users/systemizer/repos","events_url":"https://api.github.com/users/systemizer/events{/privacy}","received_events_url":"https://api.github.com/users/systemizer/received_events","type":"User","site_admin":false}],"milestone":null,"commits_url":"https://api.github.com/repos/segmentio/consul-go/pulls/33/commits","review_comments_url":"https://api.github.com/repos/segmentio/consul-go/pulls/33/comments","review_comment_url":"https://api.github.com/repos/segmentio/consul-go/pulls/comments{/number}","comments_url":"https://api.github.com/repos/segmentio/consul-go/issues/33/comments","statuses_url":"https://api.github.com/repos/segmentio/consul-go/statuses/76a8267eb8d715397b2c43c54b8d51237f96db9f","head":{"label":"segmentio:add-👀r","ref":"add-👀r","sha":"76a8267eb8d715397b2c43c54b8d51237f96db9f","user":{"login":"segmentio","id":819518,"avatar_url":"https://avatars0.githubusercontent.com/u/819518?v=4","gravatar_id":"","url":"https://api.github.com/users/segmentio","html_url":"https://github.com/segmentio","followers_url":"https://api.github.com/users/segmentio/followers","following_url":"https://api.github.com/users/segmentio/following{/other_user}","gists_url":"https://api.github.com/users/segmentio/gists{/gist_id}","starred_url":"https://api.github.com/users/segmentio/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/segmentio/subscriptions","organizations_url":"https://api.github.com/users/segmentio/orgs","repos_url":"https://api.github.com/users/segmentio/repos","events_url":"https://api.github.com/users/segmentio/events{/privacy}","received_events_url":"https://api.github.com/users/segmentio/received_events","type":"Organization","site_admin":false},"repo":{"id":90301546,"name":"consul-go","full_name":"segmentio/consul-go","owner":{"login":"segmentio","id":819518,"avatar_url":"https://avatars0.githubusercontent.com/u/819518?v=4","gravatar_id":"","url":"https://api.github.com/users/segmentio","html_url":"https://github.com/segmentio","followers_url":"https://api.github.com/users/segmentio/followers","following_url":"https://api.github.com/users/segmentio/following{/other_user}","gists_url":"https://api.github.com/users/segmentio/gists{/gist_id}","starred_url":"https://api.github.com/users/segmentio/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/segmentio/subscriptions","organizations_url":"https://api.github.com/users/segmentio/orgs","repos_url":"https://api.github.com/users/segmentio/repos","events_url":"https://api.github.com/users/segmentio/events{/privacy}","received_events_url":"https://api.github.com/users/segmentio/received_events","type":"Organization","site_admin":false},"private":false,"html_url":"https://github.com/segmentio/consul-go","description":"Go package providing building blocks for interacting with Consul.","fork":false,"url":"https://api.github.com/repos/segmentio/consul-go","forks_url":"https://api.github.com/repos/segmentio/consul-go/forks","keys_url":"https://api.github.com/repos/segmentio/consul-go/keys{/key_id}","collaborators_url":"https://api.github.com/repos/segmentio/consul-go/collaborators{/collaborator}","teams_url":"https://api.github.com/repos/segmentio/consul-go/teams","hooks_url":"https://api.github.com/repos/segmentio/consul-go/hooks","issue_events_url":"https://api.github.com/repos/segmentio/consul-go/issues/events{/number}","events_url":"https://api.github.com/repos/segmentio/consul-go/events","assignees_url":"https://api.github.com/repos/segmentio/consul-go/assignees{/user}","branches_url":"https://api.github.com/repos/segmentio/consul-go/branches{/branch}","tags_url":"https://api.github.com/repos/segmentio/consul-go/tags","blobs_url":"https://api.github.com/repos/segmentio/consul-go/git/blobs{/sha}","git_tags_url":"https://api.github.com/repos/segmentio/consul-go/git/tags{/sha}","git_refs_url":"https://api.github.com/repos/segmentio/consul-go/git/refs{/sha}","trees_url":"https://api.github.com/repos/segmentio/consul-go/git/trees{/sha}","statuses_url":"https://api.github.com/repos/segmentio/consul-go/statuses/{sha}","languages_url":"https://api.github.com/repos/segmentio/consul-go/languages","stargazers_url":"https://api.github.com/repos/segmentio/consul-go/stargazers","contributors_url":"https://api.github.com/repos/segmentio/consul-go/contributors","subscribers_url":"https://api.github.com/repos/segmentio/consul-go/subscribers","subscription_url":"https://api.github.com/repos/segmentio/consul-go/subscription","commits_url":"https://api.github.com/repos/segmentio/consul-go/commits{/sha}","git_commits_url":"https://api.github.com/repos/segmentio/consul-go/git/commits{/sha}","comments_url":"https://api.github.com/repos/segmentio/consul-go/comments{/number}","issue_comment_url":"https://api.github.com/repos/segmentio/consul-go/issues/comments{/number}","contents_url":"https://api.github.com/repos/segmentio/consul-go/contents/{+path}","compare_url":"https://api.github.com/repos/segmentio/consul-go/compare/{base}...{head}","merges_url":"https://api.github.com/repos/segmentio/consul-go/merges","archive_url":"https://api.github.com/repos/segmentio/consul-go/{archive_format}{/ref}","downloads_url":"https://api.github.com/repos/segmentio/consul-go/downloads","issues_url":"https://api.github.com/repos/segmentio/consul-go/issues{/number}","pulls_url":"https://api.github.com/repos/segmentio/consul-go/pulls{/number}","milestones_url":"https://api.github.com/repos/segmentio/consul-go/milestones{/number}","notifications_url":"https://api.github.com/repos/segmentio/consul-go/notifications{?since,all,participating}","labels_url":"https://api.github.com/repos/segmentio/consul-go/labels{/name}","releases_url":"https://api.github.com/repos/segmentio/consul-go/releases{/id}","deployments_url":"https://api.github.com/repos/segmentio/consul-go/deployments","created_at":"2017-05-04T19:21:57Z","updated_at":"2017-10-16T11:24:44Z","pushed_at":"2017-10-24T00:56:15Z","git_url":"git://github.com/segmentio/consul-go.git","ssh_url":"git@github.com:segmentio/consul-go.git","clone_url":"https://github.com/segmentio/consul-go.git","svn_url":"https://github.com/segmentio/consul-go","homepage":null,"size":144,"stargazers_count":15,"watchers_count":15,"language":"Go","has_issues":true,"has_projects":true,"has_downloads":true,"has_wiki":true,"has_pages":false,"forks_count":0,"mirror_url":null,"archived":false,"open_issues_count":4,"forks":0,"open_issues":4,"watchers":15,"default_branch":"master"}},"base":{"label":"segmentio:master","ref":"master","sha":"81d2f1b920948030e5cdff1210b3e6bac6b40472","user":{"login":"segmentio","id":819518,"avatar_url":"https://avatars0.githubusercontent.com/u/819518?v=4","gravatar_id":"","url":"https://api.github.com/users/segmentio","html_url":"https://github.com/segmentio","followers_url":"https://api.github.com/users/segmentio/followers","following_url":"https://api.github.com/users/segmentio/following{/other_user}","gists_url":"https://api.github.com/users/segmentio/gists{/gist_id}","starred_url":"https://api.github.com/users/segmentio/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/segmentio/subscriptions","organizations_url":"https://api.github.com/users/segmentio/orgs","repos_url":"https://api.github.com/users/segmentio/repos","events_url":"https://api.github.com/users/segmentio/events{/privacy}","received_events_url":"https://api.github.com/users/segmentio/received_events","type":"Organization","site_admin":false},"repo":{"id":90301546,"name":"consul-go","full_name":"segmentio/consul-go","owner":{"login":"segmentio","id":819518,"avatar_url":"https://avatars0.githubusercontent.com/u/819518?v=4","gravatar_id":"","url":"https://api.github.com/users/segmentio","html_url":"https://github.com/segmentio","followers_url":"https://api.github.com/users/segmentio/followers","following_url":"https://api.github.com/users/segmentio/following{/other_user}","gists_url":"https://api.github.com/users/segmentio/gists{/gist_id}","starred_url":"https://api.github.com/users/segmentio/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/segmentio/subscriptions","organizations_url":"https://api.github.com/users/segmentio/orgs","repos_url":"https://api.github.com/users/segmentio/repos","events_url":"https://api.github.com/users/segmentio/events{/privacy}","received_events_url":"https://api.github.com/users/segmentio/received_events","type":"Organization","site_admin":false},"private":false,"html_url":"https://github.com/segmentio/consul-go","description":"Go package providing building blocks for interacting with Consul.","fork":false,"url":"https://api.github.com/repos/segmentio/consul-go","forks_url":"https://api.github.com/repos/segmentio/consul-go/forks","keys_url":"https://api.github.com/repos/segmentio/consul-go/keys{/key_id}","collaborators_url":"https://api.github.com/repos/segmentio/consul-go/collaborators{/collaborator}","teams_url":"https://api.github.com/repos/segmentio/consul-go/teams","hooks_url":"https://api.github.com/repos/segmentio/consul-go/hooks","issue_events_url":"https://api.github.com/repos/segmentio/consul-go/issues/events{/number}","events_url":"https://api.github.com/repos/segmentio/consul-go/events","assignees_url":"https://api.github.com/repos/segmentio/consul-go/assignees{/user}","branches_url":"https://api.github.com/repos/segmentio/consul-go/branches{/branch}","tags_url":"https://api.github.com/repos/segmentio/consul-go/tags","blobs_url":"https://api.github.com/repos/segmentio/consul-go/git/blobs{/sha}","git_tags_url":"https://api.github.com/repos/segmentio/consul-go/git/tags{/sha}","git_refs_url":"https://api.github.com/repos/segmentio/consul-go/git/refs{/sha}","trees_url":"https://api.github.com/repos/segmentio/consul-go/git/trees{/sha}","statuses_url":"https://api.github.com/repos/segmentio/consul-go/statuses/{sha}","languages_url":"https://api.github.com/repos/segmentio/consul-go/languages","stargazers_url":"https://api.github.com/repos/segmentio/consul-go/stargazers","contributors_url":"https://api.github.com/repos/segmentio/consul-go/contributors","subscribers_url":"https://api.github.com/repos/segmentio/consul-go/subscribers","subscription_url":"https://api.github.com/repos/segmentio/consul-go/subscription","commits_url":"https://api.github.com/repos/segmentio/consul-go/commits{/sha}","git_commits_url":"https://api.github.com/repos/segmentio/consul-go/git/commits{/sha}","comments_url":"https://api.github.com/repos/segmentio/consul-go/comments{/number}","issue_comment_url":"https://api.github.com/repos/segmentio/consul-go/issues/comments{/number}","contents_url":"https://api.github.com/repos/segmentio/consul-go/contents/{+path}","compare_url":"https://api.github.com/repos/segmentio/consul-go/compare/{base}...{head}","merges_url":"https://api.github.com/repos/segmentio/consul-go/merges","archive_url":"https://api.github.com/repos/segmentio/consul-go/{archive_format}{/ref}","downloads_url":"https://api.github.com/repos/segmentio/consul-go/downloads","issues_url":"https://api.github.com/repos/segmentio/consul-go/issues{/number}","pulls_url":"https://api.github.com/repos/segmentio/consul-go/pulls{/number}","milestones_url":"https://api.github.com/repos/segmentio/consul-go/milestones{/number}","notifications_url":"https://api.github.com/repos/segmentio/consul-go/notifications{?since,all,participating}","labels_url":"https://api.github.com/repos/segmentio/consul-go/labels{/name}","releases_url":"https://api.github.com/repos/segmentio/consul-go/releases{/id}","deployments_url":"https://api.github.com/repos/segmentio/consul-go/deployments","created_at":"2017-05-04T19:21:57Z","updated_at":"2017-10-16T11:24:44Z","pushed_at":"2017-10-24T00:56:15Z","git_url":"git://github.com/segmentio/consul-go.git","ssh_url":"git@github.com:segmentio/consul-go.git","clone_url":"https://github.com/segmentio/consul-go.git","svn_url":"https://github.com/segmentio/consul-go","homepage":null,"size":144,"stargazers_count":15,"watchers_count":15,"language":"Go","has_issues":true,"has_projects":true,"has_downloads":true,"has_wiki":true,"has_pages":false,"forks_count":0,"mirror_url":null,"archived":false,"open_issues_count":4,"forks":0,"open_issues":4,"watchers":15,"default_branch":"master"}},"_links":{"self":{"href":"https://api.github.com/repos/segmentio/consul-go/pulls/33"},"html":{"href":"https://github.com/segmentio/consul-go/pull/33"},"issue":{"href":"https://api.github.com/repos/segmentio/consul-go/issues/33"},"comments":{"href":"https://api.github.com/repos/segmentio/consul-go/issues/33/comments"},"review_comments":{"href":"https://api.github.com/repos/segmentio/consul-go/pulls/33/comments"},"review_comment":{"href":"https://api.github.com/repos/segmentio/consul-go/pulls/comments{/number}"},"commits":{"href":"https://api.github.com/repos/segmentio/consul-go/pulls/33/commits"},"statuses":{"href":"https://api.github.com/repos/segmentio/consul-go/statuses/76a8267eb8d715397b2c43c54b8d51237f96db9f"}},"author_association":"NONE"}}
{"actor":{"display_login":"achille-roussel"}}