| Vectorified Science Blog |

Free Vector Icons

Игра с прокси модуля Go

Перевод статьи - Playing with Go module proxies

Автор - Roberto Selbach

Источник оригинальной статьи:

https://roberto.selbach.ca/go-proxies

Опубликовано 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/errors@v0.8.0/ ), т.е. архив должен содержать:

github.com/pkg/errors@v0.8.0/example_test.go
github.com/pkg/errors@v0.8.0/errors_test.go
github.com/pkg/errors@v0.8.0/LICENSE
...

А не :

errors/example_test.go
errors/errors_test.go
errors/LICENSE
...

Это выглядит много, когда написано так, но на самом деле это очень простой протокол, который просто выбирает 3 или 4 файла:

  1. Список версий (только если go не знает, какую версию он хочет)
  2. Модуль метаданных
  3. Файл go.mod
  4. Модуль 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.


  1. Помните, что somepackage и somepackage/v2 рассматриваются как разные пакеты.
  2. Это не совсем верно, так как теперь, когда мы уже собрали его один раз, go кэшировал модуль локально и вообще не пойдет в прокси (или сеть). Вы все еще можете форсировать его, удалив $GOPATH/pkg/mod/cache/download/github.com/robteix/testmod/ и $GOPATH/pkg/mod/github.com/robteix/testmod@v1.0.1 )