前言
上一篇文章介绍了athens私服的安装以及vgo download protocol的简要介绍。本文着重介绍go proxy sever的实现原理以及athens是如何实现的。
go get原理
当GOPROXY没有设置的时候,通过-x参数,可以看到go get获取module的详细过程。
[eventer@localhost]# go get -x github.com/gin-gonic/gin@v1.3.0
对于git来说,go依赖于git命令,通过git命令的组合获取module库的元数据及各版本源码包。而其中的第一步在于向源码仓库获取module的元数据。
[eventer@localhost]# curl -sSL 'https://swtch.com/testmod?go-get=1'
<!DOCTYPE html>
<meta name="go-import" content="swtch.com/testmod mod https://swtch.com/testmodproxy">
Nothing to see here.
go cli根据返回的元数据从指定的地址获取module,说白了就是在本地执行git的各个命令,跟大家平时从源码库拿代码的过程差不多一样。
当GOPROXY被设置的时候,按照《Defining Go Modules》一文中关于proxy server的定义,情况发生了一些变化,而这也正是athens所要实现的内容。
athens概述&流程
按照vgo download protocol中的定义,go proxy server是一个高效、可用、安全,且遵循module格式标准、下载协议、本地缓存以及支持按需下载的代理服务。显然,这是一个构件系统的定义,而athens也正是朝着这个目标实现的。
但由于go get与go mod命令的设定及其主动获取这些特征,使得其与java阵营的nexus、jfrog不同。java的库是由开发者主动deploy到公有仓库或私有仓库中,程序构建的时候再根据pom或gradle配置文件的声明从仓库获取指定的package。而go则省略了第一步,直接在构建的时候由go get或go mod根据go.mod文件的声明从源码库中获取module。因而这就意味着athens首先必须实现从当前流行的源码库中获取公开、私有的module,比如github、gitlab、bitbuckt;又要考虑如何从私有的源码库中获取module。
所以显而易见,athens需要实现的功能列表如下:
功能项 | 性质 | 功能说明 |
---|---|---|
下载协议 | 必需 | 4个必需接口,2个可选接口 |
本地存储 | 必需 | module存储方式,本地磁盘or内存。athens都支持, 可配置 |
云端存储 | 增强 | module存储方式,支持gcp、minio、mongo、s3、AzureBlob,可配置 |
公仓用户认证 | 必需 | github token |
私仓用户认证 | 必需 | SVN、Bazaar、Bitbucket、github、gitlab |
版本控制 | 增强 | 提供方案控制哪些module的哪些版本使用代理、是否可用等 |
日志跟踪 | 增强 | 使用opencensus实现 |
并发控制 | 增强 | 多个并发请求同一个module,处理第一个请求,后续请求等待并获取结果 |
健康检测 | 增强 | 实现服务状态接口 |
管理功能 | 增加 | 实现查询module若干接口 |
那么按照预想,go get指令的流程如下:
而再次获取同一个版本的module时,流程如下:
通过代理取包的过程其实也很简单。athens按照约定,提供了4个或6个接口供go get指令使用。当GOPROXY被设置时,go get切换至新流程,如下(goproxy.io是proxy server):
- https://goproxy.io/github.com/gin-gonic/gin/@v/list
- https://goproxy.io/github.com/gin-gonic/gin/@v/v1.3.0.info
- https://goproxy.io/github.com/gin-gonic/gin/@v/v1.3.0.mod
- https://goproxy.io/github.com/gin-gonic/gin/@v/v1.3.0.zip
这组协议对应至本地文件系统的一组目录$GOPATH/pkg/mod/cache/download,这里保存了对应上述4个接口的文件,这4个文件内容可以到这个目录下自行查看,它们的格式即是协议描述的内容。
接口实现
athens本身是一个web服务,采用gorilla框架实现。main.go位于cmd/proxy包下,关键代码读取配置文件,然后根据配置文件参数初始化程序。
//读入配置文件
conf, err := config.Load(*configFile)
if err != nil {
log.Fatalf("could not load config file: %v", err)
}
//根据配置初始化程序
handler, err := actions.App(conf)
if err != nil {
log.Fatal(err)
}
在app.go文件中,配置了storage、github token、NETRCPath、HGRCPath、log、FilterFile、路由注册。auth.go中的代码将NETRCPath、HGRCPath声明的文件内容转写到当前用户home目录下的预定位置;而关键的路由注册,则由下面的代码完成,调用的是app_proxy.go中的addProxyRoutes方法。
if err := addProxyRoutes(
proxyRouter,
store,
lggr,
conf,
); err != nil {
err = fmt.Errorf("error adding proxy routes (%s)", err)
return nil, err
}
addProxyRoutes方法注册了的路由如下:
路由 | 说明 |
---|---|
/ | 首页 |
/healthz | 健康检测 |
/readyz | - |
/version | athens版本 |
/catalog | 所有module列表 |
在这之后,定义了GoGetFetter用于处理module的下载、upstream vcs监听器、并发控制器、vgo download protocol协议实现。
handlerOpts := &download.HandlerOpts{Protocol: dp, Logger: l}
//RegisterHanlders方法注册了list、version.info、version.mod、version.zip这4个接口的路由
download.RegisterHandlers(r, handlerOpts)
athens包说明:
包 | 用途 | 描述 |
---|---|---|
pkg/config | 配置文件对应实体 | 存取配置文件参数 |
pkg/download/ | vgo download protocol协议实现 | 核心入口 |
pkg/errors | errors统一定义及堆栈跟踪 | 良好的错误设计,可以快速定位到出错的包、方法 |
pkg/log | logrus日志框架集成 | - |
pkg/middleware | 中间件 | module缓存、日志、请求验证、module获取策略 |
pkg/module | module获取策略、仓库源码获取、zip获取实现 | - |
pkg/observ | 日志及统计数据输出 | datadog |
pkg/paths | module path解析工具 | - |
pkg/stash | module获取与存储包装类及module并发请求控制 | - |
pkg/storage | 存储实现 | 包括内存、本地磁盘、数据库(mongodb)、文件系统(afero抽象文件系统)、云存储(s3,gcp,minio)等 |
module获取策略:
项 | 定义 | 说明 |
---|---|---|
module.Exclude | 排除 | 忽略对指定包的请求 |
module.Include | - | 不走代理,按常规模式获取包,本地私仓使用此配置 |
module.Direct | - | 走代理 |
获取module流程图:
在athens的实现中,各个包之间的调用关系如下:
GET baseURL/module/@v/list时序图:
GET baseURL/module/@v/version.info时序图:
baseURL/module/@v/version.mod与baseURL/module/@v/version.zip的过程与baseURL/module/@v/version.info一致,只是调用不同的实现而已。