Игра с прокси модуля Go
Перевод статьи - Playing with Go module proxies
Автор - Roberto Selbach
Источник оригинальной статьи:
Опубликовано 2018-08-29 Роберто Зельбах
Я написал краткое введение в модули Go, и в нем я кратко рассказал о прокси-модулях Go, и теперь, когда Go 1.11 вышел, я решил немного поиграть в эти прокси, чтобы понять, как они должны работать.
Зачем это надо
Одной из целей модулей Go является создание воспроизводимых сборок, и он очень хорошо работает, выбирая правильные и ожидаемые файлы из репозитория.
Но что, если серверы отключены? Что если хранилище просто исчезнет?
Один из способов, которым команды справляются с этими рисками, заключается в продаже зависимостей, что вполне нормально. Но Go modules предлагает другой способ: использование прокси модуля.
Протокол загрузки
Когда поддержка модулей Go включена и команда go
определяет, что ей нужен модуль, она сначала просматривает локальный кэш (в $GOPATH/pkg/mods
). Если он не может найти нужные файлы там, он затем продолжает и извлекает файлы из сети (то есть из удаленного репозитория, размещенного на Github, Gitlab и т. Д.)
Если мы хотим контролировать, какие файлы go
могут загружаться, мы должны указать, чтобы они проходили через наш прокси, установив GOPROXY
среды чтобы она указывала на URL нашего прокси. Например:
export GOPROXY=http://gproxy.mycompany.local:8080
Прокси-сервер - это не что иное, как веб-сервер, который отвечает на протокол загрузки модулей, который представляет собой очень простой API для запроса и извлечения модулей. Веб-сервер может даже обслуживать статические файлы.
Типичным сценарием будет команда go
пытающаяся получить github.com/pkg/errors
:
Первое, что go
будет делать, это спросить у прокси список доступных версий. Это делается путем запроса GET
к /{module name}/@v/list
. Затем сервер отвечает простым списком доступных версий:
v0.8.0
v0.7.1
go
определит, какую версию он хочет скачать - последнюю версию, если явно не указано иное. Затем он запросит информацию о данной версии GET
запрос в /{module name}/@v/{module revision}
на который сервер ответит представлением struct
формате JSON:
type RevInfo struct {
Version string // version string
Name string // complete ID in underlying repository
Short string // shortened ID, for use in pseudo-version
Time time.Time // commit time
}
Так, например, мы можем получить что-то вроде этого:
{
"Version": "v0.8.0",
"Name": "v0.8.0",
"Short": "v0.8.0",
"Time": "2018-08-27T08:54:46.436183-04:00"
}
Затем команда go
запросит файл go.mod
модуля, отправив запрос GET
в /{module name}/@v/{module revision}.mod
. Сервер просто ответит содержимым файла go.mod
(например, module github.com/pkg/errors
.) Этот файл может содержать список дополнительных зависимостей, и цикл перезапускается для каждой из них.
Наконец, команда go
запросит текущий модуль, получив /{module name}/@v/{module revision}.zip
. Сервер должен ответить с помощью байт-блоба ( application/zip
), содержащего zip-архив с файлами модуля, где перед каждым файлом должен быть указан полный путь к модулю и его версия (например, github.com/pkg/[email protected]/
), т.е. архив должен содержать:
github.com/pkg/[email protected]/example_test.go
github.com/pkg/[email protected]/errors_test.go
github.com/pkg/[email protected]/LICENSE
...
А не :
errors/example_test.go
errors/errors_test.go
errors/LICENSE
...
Это выглядит много, когда написано так, но на самом деле это очень простой протокол, который просто выбирает 3 или 4 файла:
- Список версий (только если
go
не знает, какую версию он хочет) - Модуль метаданных
- Файл
go.mod
- Модуль zip сам по себе
Создание простого локального прокси
Чтобы попробовать поддержку прокси, давайте создадим очень простой прокси, который будет обслуживать статические файлы из каталога. Сначала мы создаем каталог, в котором мы будем хранить наши внутренние копии наших зависимостей. Вот что у меня есть в моем:
$ find . -type f
./github.com/robteix/testmod/@v/v1.0.0.mod
./github.com/robteix/testmod/@v/v1.0.1.mod
./github.com/robteix/testmod/@v/v1.0.1.zip
./github.com/robteix/testmod/@v/v1.0.0.zip
./github.com/robteix/testmod/@v/v1.0.0.info
./github.com/robteix/testmod/@v/v1.0.1.info
./github.com/robteix/testmod/@v/list
Это файлы, которые будет обслуживать наш прокси. Вы можете найти эти файлы на Github, если хотите поиграть. Для примеров ниже предположим, что у нас есть каталог devel
в нашем домашнем каталоге; адаптироваться соответственно.
$ cd $HOME/devel
$ git clone https://github.com/robteix/go-proxy-blog.git
Наш прокси-сервер прост (это может быть еще проще, но я хотел записывать запросы):
package main
import (
"flag"
"log"
"net/http"
)
func main() {
addr := flag.String("http", ":8080", "address to bind to")
flag.Parse()
dir := "."
if flag.NArg() > 0 {
dir = flag.Arg(0)
}
log.Printf("Serving files from %s on %s\n", dir, *addr)
h := handler{http.FileServer(http.Dir(dir))}
panic(http.ListenAndServe(*addr, h))
}
type handler struct {
h http.Handler
}
func (h handler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
log.Println("New request:", r.URL.Path)
h.h.ServeHTTP(w, r)
}
Теперь запустите код выше:
$ go run proxy.go -http :8080 $HOME/devel/go-proxy-blog
2018/08/29 14:14:31 Serving files from /home/robteix/devel/go-proxy-blog on :8080
$ curl http://localhost:8080/github.com/robteix/testmod/@v/list
v1.0.0
v1.0.1
Оставьте прокси запущенным и перейдите на новый терминал. Теперь давайте создадим новую тестовую программу. мы создаем новый каталог $HOME/devel/test
и создаем в нем файл с именем test.go
со следующим кодом:
package main
import (
"github.com/robteix/testmod"
)
func main() {
testmod.Hi("world")
}
А теперь, внутри этого каталога, давайте включим модули Go:
$ go mod init test
И мы устанавливаем переменную GOPROXY
:
export GOPROXY=http://localhost:8080
Теперь давайте попробуем построить нашу новую программу:
$ go build
go: finding github.com/robteix/testmod v1.0.1
go: downloading github.com/robteix/testmod v1.0.1
И если вы проверите вывод нашего прокси:
2018/08/29 14:56:14 New request: /github.com/robteix/testmod/@v/list
2018/08/29 14:56:14 New request: /github.com/robteix/testmod/@v/v1.0.1.info
2018/08/29 14:56:14 New request: /github.com/robteix/testmod/@v/v1.0.1.mod
2018/08/29 14:56:14 New request: /github.com/robteix/testmod/@v/v1.0.1.zip
Так что, пока GOPROXY
установлен, go
будет загружать только файлы с GOPROXY
прокси. Если я продолжу удалять репозиторий из Github, все будет работать.
Использование локального каталога
Интересно отметить, что нам даже не нужен наш proxy.go
. Мы можем установить GOPROXY
чтобы он указывал на каталог в файловой системе, и все будет работать как положено:
export GOPROXY=file://home/robteix/devel/go-proxy-blog
Если мы сделаем go build
сейчас, мы увидим то же самое, что и с прокси:
$ go build
go: finding github.com/robteix/testmod v1.0.1
go: downloading github.com/robteix/testmod v1.0.1
Конечно, в реальной жизни мы, вероятно, предпочли бы иметь прокси-сервер компании / команды, где хранятся наши зависимости, потому что локальный каталог не очень сильно отличается от локального кэша, который go
уже поддерживается в $GOPATH/pkg/mod
, но все же приятно знать, что это работает.
Есть проект под названием Athens, который строит прокси и нацелен, если не ошибаюсь, создать центральный репозиторий пакетов, например, à la npm.
- Помните, что
somepackage
иsomepackage/v2
рассматриваются как разные пакеты. - Это не совсем верно, так как теперь, когда мы уже собрали его один раз,
go
кэшировал модуль локально и вообще не пойдет в прокси (или сеть). Вы все еще можете форсировать его, удалив$GOPATH/pkg/mod/cache/download/github.com/robteix/testmod/
и$GOPATH/pkg/mod/github.com/robteix/[email protected]
)