github.phpd.cn/thought-machine/please@v12.2.0+incompatible/tools/please_maven/maven/fetch.go (about)

     1  package maven
     2  
     3  import (
     4  	"fmt"
     5  	"io/ioutil"
     6  	"net/http"
     7  	"strings"
     8  	"sync"
     9  	"time"
    10  )
    11  
    12  // A Fetch fetches files for us from Maven.
    13  // It memoises requests internally so we don't re-request the same file.
    14  type Fetch struct {
    15  	// Maven repos we're fetching from.
    16  	repos []string
    17  	// HTTP client to fetch with
    18  	client *http.Client
    19  	// Request cache
    20  	// TODO(peterebden): is this actually ever useful now we have Resolver?
    21  	cache map[string][]byte
    22  	mutex sync.Mutex
    23  	// Excluded & optional artifacts; this isn't a great place for them but they need to go somewhere.
    24  	exclude, optional map[string]bool
    25  	// Version resolver.
    26  	Resolver *Resolver
    27  }
    28  
    29  // NewFetch constructs & returns a new Fetch instance.
    30  func NewFetch(repos, exclude, optional []string) *Fetch {
    31  	for i, url := range repos {
    32  		if !strings.HasSuffix(url, "/") {
    33  			repos[i] = url + "/"
    34  		}
    35  		if strings.HasPrefix(url, "http:") {
    36  			log.Warning("Repo URL %s is not secure, you should really be using https", url)
    37  		}
    38  	}
    39  	f := &Fetch{
    40  		repos:    repos,
    41  		client:   &http.Client{Timeout: 30 * time.Second},
    42  		cache:    map[string][]byte{},
    43  		exclude:  toMap(exclude),
    44  		optional: toMap(optional),
    45  	}
    46  	f.Resolver = NewResolver(f)
    47  	return f
    48  }
    49  
    50  // toMap converts a slice of strings to a map.
    51  func toMap(sl []string) map[string]bool {
    52  	m := make(map[string]bool, len(sl))
    53  	for _, s := range sl {
    54  		m[s] = true
    55  	}
    56  	return m
    57  }
    58  
    59  // Pom fetches the POM XML for a package.
    60  // Note that this may invoke itself recursively to fetch parent artifacts and dependencies.
    61  func (f *Fetch) Pom(a *Artifact) *PomXML {
    62  	if a.Version == "+" {
    63  		// + indicates the latest version, presumably.
    64  		a.SetVersion(f.Metadata(a).LatestVersion())
    65  	}
    66  	pom, created := f.Resolver.CreatePom(a)
    67  	pom.Lock()
    68  	defer pom.Unlock()
    69  	if !created {
    70  		return pom
    71  	}
    72  	pom.Unmarshal(f, f.mustFetch(a.PomPath()))
    73  	return pom
    74  }
    75  
    76  // Metadata returns the metadata XML for a package.
    77  // This contains some information, typically the main useful thing is the latest available version of the package.
    78  func (f *Fetch) Metadata(a *Artifact) *MetadataXML {
    79  	metadata := &MetadataXML{Group: a.GroupID, Artifact: a.ArtifactID}
    80  	metadata.Unmarshal(f.mustFetch(a.MetadataPath()))
    81  	return metadata
    82  }
    83  
    84  // HasSources returns true if the given artifact has any sources available.
    85  // Unfortunately there's no way of determining this other than making a request, and lots of servers
    86  // don't seem to support HEAD requests to just find out if the artifact is there.
    87  func (f *Fetch) HasSources(a *Artifact) bool {
    88  	_, err := f.fetch(a.SourcePath(), false)
    89  	return err == nil
    90  }
    91  
    92  // IsExcluded returns true if this artifact should be excluded from the download.
    93  func (f *Fetch) IsExcluded(artifact string) bool {
    94  	return f.exclude[artifact]
    95  }
    96  
    97  // ShouldInclude returns true if we should include an optional dependency.
    98  func (f *Fetch) ShouldInclude(artifact string) bool {
    99  	return f.optional[artifact]
   100  }
   101  
   102  // mustFetch fetches a URL and returns the content, dying if it can't be downloaded.
   103  func (f *Fetch) mustFetch(url string) []byte {
   104  	b, err := f.fetch(url, true)
   105  	if err != nil {
   106  		log.Fatalf("Error downloading %s: %s\n", f.repos[len(f.repos)-1]+url, err)
   107  	}
   108  	return b
   109  }
   110  
   111  // fetch fetches a URL and returns the content.
   112  func (f *Fetch) fetch(url string, readBody bool) ([]byte, error) {
   113  	f.mutex.Lock()
   114  	contents, present := f.cache[url]
   115  	f.mutex.Unlock()
   116  	if present {
   117  		log.Debug("Retrieved %s from cache", url)
   118  		return contents, nil
   119  	}
   120  	var err error
   121  	for _, repo := range f.repos {
   122  		if contents, err = f.fetchURL(repo+url, readBody); err == nil {
   123  			f.mutex.Lock()
   124  			defer f.mutex.Unlock()
   125  			f.cache[url] = contents
   126  			return contents, nil
   127  		}
   128  	}
   129  	return nil, err
   130  }
   131  
   132  func (f *Fetch) fetchURL(url string, readBody bool) ([]byte, error) {
   133  	log.Notice("%s %s...", f.description(readBody), url)
   134  	req, err := http.NewRequest(http.MethodGet, url, nil)
   135  	if err != nil {
   136  		return nil, err
   137  	}
   138  	response, err := f.client.Do(req)
   139  	if err != nil {
   140  		return nil, err
   141  	} else if response.StatusCode < 200 || response.StatusCode > 299 {
   142  		return nil, fmt.Errorf("Bad response code: %s", response.Status)
   143  	}
   144  	defer response.Body.Close()
   145  	if !readBody {
   146  		return nil, nil
   147  	}
   148  	return ioutil.ReadAll(response.Body)
   149  }
   150  
   151  // description returns the log description we'll use for a download.
   152  func (f *Fetch) description(readBody bool) string {
   153  	if readBody {
   154  		return "Downloading"
   155  	}
   156  	return "Checking"
   157  }