github.com/zxy12/go_duplicate_112_new@v0.0.0-20200807091221-747231827200/src/cmd/go/internal/modfetch/proxy.go (about) 1 // Copyright 2018 The Go Authors. All rights reserved. 2 // Use of this source code is governed by a BSD-style 3 // license that can be found in the LICENSE file. 4 5 package modfetch 6 7 import ( 8 "encoding/json" 9 "fmt" 10 "io" 11 "net/url" 12 "os" 13 "strings" 14 "time" 15 16 "cmd/go/internal/base" 17 "cmd/go/internal/modfetch/codehost" 18 "cmd/go/internal/module" 19 "cmd/go/internal/semver" 20 ) 21 22 var HelpGoproxy = &base.Command{ 23 UsageLine: "goproxy", 24 Short: "module proxy protocol", 25 Long: ` 26 The go command by default downloads modules from version control systems 27 directly, just as 'go get' always has. The GOPROXY environment variable allows 28 further control over the download source. If GOPROXY is unset, is the empty string, 29 or is the string "direct", downloads use the default direct connection to version 30 control systems. Setting GOPROXY to "off" disallows downloading modules from 31 any source. Otherwise, GOPROXY is expected to be the URL of a module proxy, 32 in which case the go command will fetch all modules from that proxy. 33 No matter the source of the modules, downloaded modules must match existing 34 entries in go.sum (see 'go help modules' for discussion of verification). 35 36 A Go module proxy is any web server that can respond to GET requests for 37 URLs of a specified form. The requests have no query parameters, so even 38 a site serving from a fixed file system (including a file:/// URL) 39 can be a module proxy. 40 41 The GET requests sent to a Go module proxy are: 42 43 GET $GOPROXY/<module>/@v/list returns a list of all known versions of the 44 given module, one per line. 45 46 GET $GOPROXY/<module>/@v/<version>.info returns JSON-formatted metadata 47 about that version of the given module. 48 49 GET $GOPROXY/<module>/@v/<version>.mod returns the go.mod file 50 for that version of the given module. 51 52 GET $GOPROXY/<module>/@v/<version>.zip returns the zip archive 53 for that version of the given module. 54 55 To avoid problems when serving from case-sensitive file systems, 56 the <module> and <version> elements are case-encoded, replacing every 57 uppercase letter with an exclamation mark followed by the corresponding 58 lower-case letter: github.com/Azure encodes as github.com/!azure. 59 60 The JSON-formatted metadata about a given module corresponds to 61 this Go data structure, which may be expanded in the future: 62 63 type Info struct { 64 Version string // version string 65 Time time.Time // commit time 66 } 67 68 The zip archive for a specific version of a given module is a 69 standard zip file that contains the file tree corresponding 70 to the module's source code and related files. The archive uses 71 slash-separated paths, and every file path in the archive must 72 begin with <module>@<version>/, where the module and version are 73 substituted directly, not case-encoded. The root of the module 74 file tree corresponds to the <module>@<version>/ prefix in the 75 archive. 76 77 Even when downloading directly from version control systems, 78 the go command synthesizes explicit info, mod, and zip files 79 and stores them in its local cache, $GOPATH/pkg/mod/cache/download, 80 the same as if it had downloaded them directly from a proxy. 81 The cache layout is the same as the proxy URL space, so 82 serving $GOPATH/pkg/mod/cache/download at (or copying it to) 83 https://example.com/proxy would let other users access those 84 cached module versions with GOPROXY=https://example.com/proxy. 85 `, 86 } 87 88 var proxyURL = os.Getenv("GOPROXY") 89 90 func lookupProxy(path string) (Repo, error) { 91 if strings.Contains(proxyURL, ",") { 92 return nil, fmt.Errorf("invalid $GOPROXY setting: cannot have comma") 93 } 94 u, err := url.Parse(proxyURL) 95 if err != nil || u.Scheme != "http" && u.Scheme != "https" && u.Scheme != "file" { 96 // Don't echo $GOPROXY back in case it has user:password in it (sigh). 97 return nil, fmt.Errorf("invalid $GOPROXY setting: malformed URL or invalid scheme (must be http, https, file)") 98 } 99 return newProxyRepo(u.String(), path) 100 } 101 102 type proxyRepo struct { 103 url string 104 path string 105 } 106 107 func newProxyRepo(baseURL, path string) (Repo, error) { 108 enc, err := module.EncodePath(path) 109 if err != nil { 110 return nil, err 111 } 112 return &proxyRepo{strings.TrimSuffix(baseURL, "/") + "/" + pathEscape(enc), path}, nil 113 } 114 115 func (p *proxyRepo) ModulePath() string { 116 return p.path 117 } 118 119 func (p *proxyRepo) Versions(prefix string) ([]string, error) { 120 var data []byte 121 err := webGetBytes(p.url+"/@v/list", &data) 122 if err != nil { 123 return nil, err 124 } 125 var list []string 126 for _, line := range strings.Split(string(data), "\n") { 127 f := strings.Fields(line) 128 if len(f) >= 1 && semver.IsValid(f[0]) && strings.HasPrefix(f[0], prefix) { 129 list = append(list, f[0]) 130 } 131 } 132 SortVersions(list) 133 return list, nil 134 } 135 136 func (p *proxyRepo) latest() (*RevInfo, error) { 137 var data []byte 138 err := webGetBytes(p.url+"/@v/list", &data) 139 if err != nil { 140 return nil, err 141 } 142 var best time.Time 143 var bestVersion string 144 for _, line := range strings.Split(string(data), "\n") { 145 f := strings.Fields(line) 146 if len(f) >= 2 && semver.IsValid(f[0]) { 147 ft, err := time.Parse(time.RFC3339, f[1]) 148 if err == nil && best.Before(ft) { 149 best = ft 150 bestVersion = f[0] 151 } 152 } 153 } 154 if bestVersion == "" { 155 return nil, fmt.Errorf("no commits") 156 } 157 info := &RevInfo{ 158 Version: bestVersion, 159 Name: bestVersion, 160 Short: bestVersion, 161 Time: best, 162 } 163 return info, nil 164 } 165 166 func (p *proxyRepo) Stat(rev string) (*RevInfo, error) { 167 var data []byte 168 encRev, err := module.EncodeVersion(rev) 169 if err != nil { 170 return nil, err 171 } 172 err = webGetBytes(p.url+"/@v/"+pathEscape(encRev)+".info", &data) 173 if err != nil { 174 return nil, err 175 } 176 info := new(RevInfo) 177 if err := json.Unmarshal(data, info); err != nil { 178 return nil, err 179 } 180 return info, nil 181 } 182 183 func (p *proxyRepo) Latest() (*RevInfo, error) { 184 var data []byte 185 u := p.url + "/@latest" 186 err := webGetBytes(u, &data) 187 if err != nil { 188 // TODO return err if not 404 189 return p.latest() 190 } 191 info := new(RevInfo) 192 if err := json.Unmarshal(data, info); err != nil { 193 return nil, err 194 } 195 return info, nil 196 } 197 198 func (p *proxyRepo) GoMod(version string) ([]byte, error) { 199 var data []byte 200 encVer, err := module.EncodeVersion(version) 201 if err != nil { 202 return nil, err 203 } 204 err = webGetBytes(p.url+"/@v/"+pathEscape(encVer)+".mod", &data) 205 if err != nil { 206 return nil, err 207 } 208 return data, nil 209 } 210 211 func (p *proxyRepo) Zip(dst io.Writer, version string) error { 212 var body io.ReadCloser 213 encVer, err := module.EncodeVersion(version) 214 if err != nil { 215 return err 216 } 217 err = webGetBody(p.url+"/@v/"+pathEscape(encVer)+".zip", &body) 218 if err != nil { 219 return err 220 } 221 defer body.Close() 222 223 lr := &io.LimitedReader{R: body, N: codehost.MaxZipFile + 1} 224 if _, err := io.Copy(dst, lr); err != nil { 225 return err 226 } 227 if lr.N <= 0 { 228 return fmt.Errorf("downloaded zip file too large") 229 } 230 return nil 231 } 232 233 // pathEscape escapes s so it can be used in a path. 234 // That is, it escapes things like ? and # (which really shouldn't appear anyway). 235 // It does not escape / to %2F: our REST API is designed so that / can be left as is. 236 func pathEscape(s string) string { 237 return strings.ReplaceAll(url.PathEscape(s), "%2F", "/") 238 }