discovery/internal/proxyclient: create client for module proxy

The discovery/internal/proxyclient package is created to interact with a
module proxy. See `go help goproxy` for proxy protocol details.

The following structs are introduced:
- proxyclient.Client
- proxyclient.VersionInfo

The following exported functions are implemented:
- New(rawurl string) (*Client, error)
- GetInfo(name, version string) (*VersionInfo, error)

discovery/internal/testdata is moved to discovery/internal/fetch/testdata.

Change-Id: I3b0fa8dcf95f8dbbc39519640adbf199c1e61efb
Reviewed-on: https://team-review.git.corp.google.com/c/414334
Reviewed-by: Andrew Bonventre <andybons@google.com>
This commit is contained in:
Julie Qiu 2019-02-11 17:00:07 -05:00
Родитель 88e3a2059e
Коммит 1e7ae17793
18 изменённых файлов: 183 добавлений и 18 удалений

Просмотреть файл

@ -0,0 +1,58 @@
// Copyright 2019 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package proxyclient
import (
"encoding/json"
"fmt"
"net/http"
"strings"
"time"
)
// A Client is used by the fetch service to communicate with a module
// proxy. It handles all methods defined by go help goproxy.
// TODO(julieqiu): Implement GetList, GetMod, and GetZip.
type Client struct {
url string // URL of the module proxy web server
}
// A VersionInfo contains metadata about a given version of a module.
type VersionInfo struct {
Version string
Time time.Time
}
// cleanURL trims the rawurl of trailing slashes.
func cleanURL(rawurl string) string {
return strings.TrimRight(rawurl, "/"), nil
}
// New constructs a *Client using the provided rawurl, which is expected to
// be an absolute URI that can be directly passed to http.Get.
func New(rawurl string) *Client {
return &Client{url: cleanURL(rawurl)}, nil
}
// infoURL constructs a url for a GET request to $GOPROXY/<module>/@v/list.
func (c *Client) infoURL(name, version string) string {
return fmt.Sprintf("%s/%s/@v/%s.info", c.url, name, version)
}
// GetInfo makes a GET request to $GOPROXY/<module>/@v/<version>.info and
// transforms that data into a *VersionInfo.
func (c *Client) GetInfo(name, version string) (*VersionInfo, error) {
r, err := http.Get(c.infoURL(name, version))
if err != nil {
return nil, err
}
defer r.Body.Close()
var v VersionInfo
if err = json.NewDecoder(r.Body).Decode(&v); err != nil {
return nil, err
}
return &v, nil
}

Просмотреть файл

@ -0,0 +1,97 @@
// Copyright 2019 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package proxyclient
import (
"net/http"
"net/http/httptest"
"strings"
"testing"
"time"
)
type testCase struct {
proxy *httptest.Server
client *Client
}
func setupTestCase(t *testing.T) (func(t *testing.T), *testCase) {
proxy := httptest.NewServer(http.FileServer(http.Dir("testdata/modproxy/proxy")))
tc := testCase{
proxy: proxy,
client: New(proxy.URL),
}
fn := func(t *testing.T) {
proxy.Close()
}
return fn, &tc
}
func TestCleanURL(t *testing.T) {
for raw, expected := range map[string]string{
"http://localhost:7000/index": "http://localhost:7000/index",
"http://host.com/": "http://host.com",
"http://host.com///": "http://host.com",
} {
got, err := cleanURL(raw)
if err != nil {
t.Errorf("cleanURL(%q) error: %v", raw, err)
}
if got != expected {
t.Errorf("cleanURL(%q) = %q, want %q", raw, got, expected)
}
}
}
func TestCleanURLErrors(t *testing.T) {
for _, raw := range []string{
"localhost:7000/index",
"host.com",
"",
} {
_, err := cleanURL(raw)
expectedErr := "is an invalid url"
if err == nil {
t.Errorf("cleanURL(%q) error = %v, want %q", raw, err, expectedErr)
}
if !strings.Contains(err.Error(), expectedErr) {
t.Errorf("cleanURL(%q) error = %v, want %q", raw, err, expectedErr)
}
}
}
func TestGetInfo(t *testing.T) {
teardownTestCase, testCase := setupTestCase(t)
defer teardownTestCase(t)
name := "my/module"
version := "v1.0.0"
info, err := testCase.proxyClient.GetInfo(name, version)
if err != nil {
t.Errorf("GetInfo(%q, %q) error: %v", name, version, err)
}
if info.Version != version {
t.Errorf("VersionInfo.Version for GetInfo(%q, %q) = %q, want %q", name, version, info.Version, version)
}
expectedTime := time.Date(2019, 1, 30, 0, 0, 0, 0, time.UTC)
if info.Time != expectedTime {
t.Errorf("VersionInfo.Time for GetInfo(%q, %q) = %v, want %v", name, version, info.Time, expectedTime)
}
}
func TestGetInfoVersionDoesNotExist(t *testing.T) {
teardownTestCase, testCase := setupTestCase(t)
defer teardownTestCase(t)
name := "my/module"
version := "v3.0.0"
info, _ := testCase.proxyClient.GetInfo(name, version)
if info != nil {
t.Errorf("GetInfo(%q, %q) = %v, want %v", name, version, info, nil)
}
}

28
internal/proxyclient/testdata/modproxy/main.go поставляемый Normal file
Просмотреть файл

@ -0,0 +1,28 @@
// Copyright 2019 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
// modproxy runs a local module proxy for testing. It implements the Module
// proxy protocol described at `go help goproxy` by serving files stored at
// ./proxy. The following modules are supported by this proxy:
// my/module v1.0.0
// my/module v1.1.0
// my/module/2 v12.0.0
package main
import (
"fmt"
"log"
"net/http"
"path/filepath"
)
var proxyURL, _ = filepath.Abs("proxy")
func main() {
http.Handle("/", http.FileServer(http.Dir(proxyURL)))
addr := ":7000"
log.Println(fmt.Sprintf("Listening on http://localhost%s", addr))
log.Fatal(http.ListenAndServe(addr, nil))
}

18
internal/testdata/modproxy/main.go поставляемый
Просмотреть файл

@ -1,18 +0,0 @@
// Copyright 2019 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package main
import (
"log"
"net/http"
)
const proxyURL = "./proxy"
func main() {
http.Handle("/", http.FileServer(http.Dir(proxyURL)))
log.Fatal(http.ListenAndServe(":8080", nil))
}