github.com/uber/kraken@v0.1.4/tracker/announceclient/client.go (about) 1 // Copyright (c) 2016-2019 Uber Technologies, Inc. 2 // 3 // Licensed under the Apache License, Version 2.0 (the "License"); 4 // you may not use this file except in compliance with the License. 5 // You may obtain a copy of the License at 6 // 7 // http://www.apache.org/licenses/LICENSE-2.0 8 // 9 // Unless required by applicable law or agreed to in writing, software 10 // distributed under the License is distributed on an "AS IS" BASIS, 11 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 // See the License for the specific language governing permissions and 13 // limitations under the License. 14 package announceclient 15 16 import ( 17 "bytes" 18 "crypto/tls" 19 "encoding/json" 20 "errors" 21 "fmt" 22 "net/http" 23 "time" 24 25 "github.com/uber/kraken/core" 26 "github.com/uber/kraken/lib/hashring" 27 "github.com/uber/kraken/utils/httputil" 28 ) 29 30 // ErrDisabled is returned when announce is disabled. 31 var ErrDisabled = errors.New("announcing disabled") 32 33 // Request defines an announce request. 34 type Request struct { 35 Name string `json:"name"` 36 Digest *core.Digest `json:"digest"` // Optional (for now). 37 InfoHash core.InfoHash `json:"info_hash"` 38 Peer *core.PeerInfo `json:"peer"` 39 } 40 41 // GetDigest is a backwards compatible accessor of the request digest. 42 func (r *Request) GetDigest() (core.Digest, error) { 43 if r.Digest != nil { 44 return *r.Digest, nil 45 } 46 d, err := core.NewSHA256DigestFromHex(r.Name) 47 if err != nil { 48 return core.Digest{}, err 49 } 50 return d, nil 51 } 52 53 // Response defines an announce response. 54 type Response struct { 55 Peers []*core.PeerInfo `json:"peers"` 56 Interval time.Duration `json:"interval"` 57 } 58 59 // Client defines a client for announcing and getting peers. 60 type Client interface { 61 Announce( 62 d core.Digest, 63 h core.InfoHash, 64 complete bool, 65 version int) ([]*core.PeerInfo, time.Duration, error) 66 } 67 68 type client struct { 69 pctx core.PeerContext 70 ring hashring.PassiveRing 71 tls *tls.Config 72 } 73 74 // New creates a new client. 75 func New(pctx core.PeerContext, ring hashring.PassiveRing, tls *tls.Config) Client { 76 return &client{pctx, ring, tls} 77 } 78 79 // Announce versionss. 80 const ( 81 V1 = 1 82 V2 = 2 83 ) 84 85 func getEndpoint(version int, addr string, h core.InfoHash) (method, url string) { 86 if version == V1 { 87 return "GET", fmt.Sprintf("http://%s/announce", addr) 88 } 89 return "POST", fmt.Sprintf("http://%s/announce/%s", addr, h.String()) 90 } 91 92 // Announce announces the torrent identified by (d, h) with the number of 93 // downloaded bytes. Returns a list of all other peers announcing for said torrent, 94 // sorted by priority, and the interval for the next announce. 95 func (c *client) Announce( 96 d core.Digest, 97 h core.InfoHash, 98 complete bool, 99 version int) (peers []*core.PeerInfo, interval time.Duration, err error) { 100 101 body, err := json.Marshal(&Request{ 102 Name: d.Hex(), // For backwards compatability. TODO(codyg): Remove. 103 Digest: &d, 104 InfoHash: h, 105 Peer: core.PeerInfoFromContext(c.pctx, complete), 106 }) 107 if err != nil { 108 return nil, 0, fmt.Errorf("marshal request: %s", err) 109 } 110 var httpResp *http.Response 111 for _, addr := range c.ring.Locations(d) { 112 method, url := getEndpoint(version, addr, h) 113 httpResp, err = httputil.Send( 114 method, 115 url, 116 httputil.SendBody(bytes.NewReader(body)), 117 httputil.SendTimeout(10*time.Second), 118 httputil.SendTLS(c.tls)) 119 if err != nil { 120 if httputil.IsNetworkError(err) { 121 c.ring.Failed(addr) 122 continue 123 } 124 return nil, 0, err 125 } 126 defer httpResp.Body.Close() 127 var resp Response 128 if err := json.NewDecoder(httpResp.Body).Decode(&resp); err != nil { 129 return nil, 0, fmt.Errorf("decode response: %s", err) 130 } 131 return resp.Peers, resp.Interval, nil 132 } 133 return nil, 0, err 134 } 135 136 // DisabledClient rejects all announces. Suitable for origin peers which should 137 // not be announcing. 138 type DisabledClient struct{} 139 140 // Disabled returns a new DisabledClient. 141 func Disabled() Client { 142 return DisabledClient{} 143 } 144 145 // Announce always returns error. 146 func (c DisabledClient) Announce( 147 d core.Digest, h core.InfoHash, complete bool, version int) ([]*core.PeerInfo, time.Duration, error) { 148 149 return nil, 0, ErrDisabled 150 }