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  }