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 }