前言

  上一篇文章介绍了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指令的流程如下:

image

  而再次获取同一个版本的module时,流程如下:

image

  通过代理取包的过程其实也很简单。athens按照约定,提供了4个或6个接口供go get指令使用。当GOPROXY被设置时,go get切换至新流程,如下(goproxy.io是proxy server):

  1. https://goproxy.io/github.com/gin-gonic/gin/@v/list
  2. https://goproxy.io/github.com/gin-gonic/gin/@v/v1.3.0.info
  3. https://goproxy.io/github.com/gin-gonic/gin/@v/v1.3.0.mod
  4. 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-
/versionathens版本
/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/errorserrors统一定义及堆栈跟踪良好的错误设计,可以快速定位到出错的包、方法
pkg/loglogrus日志框架集成-
pkg/middleware中间件module缓存、日志、请求验证、module获取策略
pkg/modulemodule获取策略、仓库源码获取、zip获取实现-
pkg/observ日志及统计数据输出datadog
pkg/pathsmodule path解析工具-
pkg/stashmodule获取与存储包装类及module并发请求控制-
pkg/storage存储实现包括内存、本地磁盘、数据库(mongodb)、文件系统(afero抽象文件系统)、云存储(s3,gcp,minio)等

  module获取策略

定义说明
module.Exclude排除忽略对指定包的请求
module.Include-不走代理,按常规模式获取包,本地私仓使用此配置
module.Direct-走代理

  获取module流程图
获取module流程图

  在athens的实现中,各个包之间的调用关系如下:

包调用关系

  GET baseURL/module/@v/list时序图

list时序图

  GET baseURL/module/@v/version.info时序图
version.info时序图


baseURL/module/@v/version.mod与baseURL/module/@v/version.zip的过程与baseURL/module/@v/version.info一致,只是调用不同的实现而已。

欢迎关注个人公众号:不一样的go语言
关注个人公众号

Logo

瓜分20万奖金 获得内推名额 丰厚实物奖励 易参与易上手

更多推荐