前言
上一篇文章介紹了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一致,只是調用不同的實現而已。