Skip to content

Latest commit

 

History

History
296 lines (214 loc) · 15.8 KB

43 让你的Go包拥有个性化的导入路径.md

File metadata and controls

296 lines (214 loc) · 15.8 KB

43 让你的Go包拥有个性化的导入路径

让你的Go包拥有个性化的导入路径

在日常开发中,我们使用最多的 Go 包的 go get 导入路径主要是基于一些代码托管站点的域名,比如:github.combitbucket.org、gitlab.com 等。以知名 Go web 框架 beego 包为例,它的 go get 导入路径就是 github.com/astaxie/beego。我们还经常看到一些包,它们的导入路径很特殊,比如:go get golang.org/x/netgo get gopkg.in/yaml.v2 等,这些包使用了自定义的包导入路径。这种自定义包 go get 导入路径的实践有诸多好处:

  • 可以为 Go 包设置权威导入路径 (canonical import path)

这是在 Go 1.4 版本中加入的概念。前面说过,Go 包多托管在几个知名的代码管理网站,比如:github.combitbucket.org 等,这样默认情况下 Go 包的导入路径就是 github.com/user/repo/packagebitbucket.org/user/repo/package 等。一旦某个网站关门大吉了,那该 Go 包势必要迁移到其他站点,这样该 Go 包的导入路径就要发生改变,这会给 Go 包的用户造成诸多不便,比如之前的 code.google.com 关闭就给广大的 gopher 们带来了一定的 “伤害”。权威导入路径可以解决这个问题。Go 包的用户只需要使用包的权威导入路径,这样无论 Go 包的实际托管网站在哪,Go 包迁移到哪个的托管站点,对 Go 包的用户都不会带来实质性的影响。

  • 便于组织和个人对 Go 包的管理

组织和个人可以将其分散托管在不同代码管理网站的 Go 包统一聚合到组织的官网名下或个人的域名下,比如:golang.org/x/netgopkg.in/xxx 等。

  • Go 包的导入路径可以更短、更简洁

有些时候,代码托管站点上的 go 包的导入路径很长,并不便于查找和书写,通过自定义包导入路径,我们可以使用更短、更简洁的域名来代替代码托管站点下等仓库的多级路径。

在本小节中,我们就来介绍一种自定义 Go 包导入路径的有效实践。

1. govanityurls

前 Go 核心开发团队成员 Jaana B. Dogan 曾开源过一个工具:Go Vanity URLs。这个工具可以帮助 gopher 快速实现自定义 Go 包的 go get 导入路径。

不过 Jaana B. Dogan 提供的 govanityurls 仅能运行于 Google 的 app engine 上,这对于国内的 Gopher 们来说是十分不便的。于是笔者基于 Jaana B. Dogan 的 govanityurls 仓库 fork 了一个 新仓库https://github.com/bigwhite/govanityurls,并做了些许修改,让 govanityurls 可以运行于 app engine 之外普通的虚拟主机 / 裸金属主机上。

govanityurls 的原理十分简单,见下面示意图: 43 让你的Go包拥有个性化的导入路径

图 10-3-1:govanityurls 工作原理

我们看到 govanityurls 本身就好比一个 “导航” 服务器。当 go get 向自定义包地址发起请求时,实则是将请求发送给了 govanityurls 服务,之后 govanityurls 将请求中的包所在仓库的真实地址 (从 vanity.yaml 配置文件中读取) 返回给 go get,后续 go get 再从真实的仓库地址获取包数据。

以图中示例为例,go get 第一步是尝试向 govanityurls 获取自定义路径的包的真实地址,govanityurls 将返回一个类似如下内容的 http 应答 (针对 go get tonybai.com/gowechat 请求):

<!DOCTYPE html>
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8"/>
<meta name="go-import" content="tonybai.com/gowechat git https://github.com/bigwhite/gowechat">
<meta name="go-source" content="tonybai.com/gowechat ">
<meta http-equiv="refresh" content="0; url=https://godoc.org/tonybai.com/gowechat">
</head>
<body>
Nothing to see here; <a href="https://godoc.org/tonybai.com/gowechat">see the package on godoc</a>.
</body>
</html> 

得到该应答后,go get 会再次向存储 gowechat 包的真实仓库地址 github.com/bigwhite/gowechat 发起包获取请求。

2. 使用 govanityurls

1) 安装 govanityurls

我们可以直接使用 go get 来安装 govanityurls:

$go get github.com/bigwhite/govanityurls

$govanityurls
govanityurls is a service that allows you to set custom import paths for your go packages

Usage:
     govanityurls -host [HOST_NAME]

  -host string
        custom domain name, e.g. tonybai.com 

和 Jaana B. Dogan 提供的 govanityurls 不同的是,这里的 govanityurls 需要外部传入一个代表自定义包路径基本域名的 host 参数 (比如:tonybai.com),而在原版中这个 host 是由 Google app engine 的 API 提供的。

2) 配置 vanity.yaml

govanityurls 附带一个配置文件 vanity.yaml,该文件中配置了 host 下的自定义包路径以及其真实的仓库地址,比如:

/gowechat:
        repo: https://github.com/bigwhite/gowechat 

上面这个配置中,我们实际上为 gowechat 这个包定制了 tonybai.com/gowechat 这个 go get 路径 (我们假设传给 govanityurlshost 参数为 tonybai.com),而存放该包的真实仓库地址为 github.com/bigwhite/gowechat。当然这个 vanity.yaml 可以配置多个自定义包路径,亦可定义多级包路径,比如:

/gowechat:
        repo: https://github.com/bigwhite/gowechat

/x/experiments:
        repo: https://github.com/bigwhite/experiments

3) 配置反向代理

govanityurls 服务默认监听的是 8080 端口,这主要是考虑到我们通常会使用主域名来定制 Go 包导入路径,而在主域名下面一般情况下都会有其他一些服务,比如:网站主页、博客等。通常我们都会用一个反向代理软件 (比如:nginx) 做路由分发。比如下面就是我们针对 tonybai.com/gowechat 这个包定义的一条 nginx 路由规则:

# /etc/nginx/conf.d/govanityurls.conf 
server {
        listen 80;
        listen 443 ssl;
        server_name tonybai.com;

        ssl_certificate           /etc/nginx/cert.crt;
        ssl_certificate_key       /etc/nginx/cert.key;
        ssl on;

        location /gowechat {
                proxy_pass http://192.168.16.4:8080;
                proxy_redirect off;
                proxy_set_header Host $host;
                proxy_set_header X-Real-IP $remote_addr;
                proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;

                proxy_http_version 1.1;
                proxy_set_header Upgrade $http_upgrade;
                proxy_set_header Connection "upgrade";
        }
} 

这里我们既在 80 端口提供 http 服务,也在 443 端口提供了 https 服务。192.168.16.4 这个地址就是部署 govanityurls 服务的主机地址。/etc/nginx/cert.key/etc/nginx/cert.crt 是 https 服务所需的私钥和数字证书。我们可以使用自签名证书 (用起来十分局限),亦可自行向 CA 申请付费证书或向 Let’s Encrypt 申请免费证书。

不过上述的 nginx 路由规则难于扩展,每当我在主域名 (tonybai.com) 增加一个包,我就得去添加一条 nginx 路由规则。为了更易于扩展和维护,我们将自定义包都放在 tonybai.com/x 下面,就像 golang.org/x 下面的那些包,这样我们就可以维护一条 nginx 路由规则了:

# /etc/nginx/conf.d/govanityurls.conf 
server {
        listen 80;
        listen 443 ssl;
        server_name tonybai.com;

        ssl_certificate           /etc/nginx/cert.crt;
        ssl_certificate_key       /etc/nginx/cert.key;
        ssl on;

        location /x {
                proxy_pass http://192.168.16.4:8080;
                proxy_redirect off;
                proxy_set_header Host $host;
                proxy_set_header X-Real-IP $remote_addr;
                proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;

                proxy_http_version 1.1;
                proxy_set_header Upgrade $http_upgrade;
                proxy_set_header Connection "upgrade";
        }
} 

在这条路由规则的作用下,所有发往 /x/packagename 的请求就都会被转发给 govanityurls 服务了。

4) 验证通过 govanityurls 自定义包导入路径

我们创建一个新的 govanityurls 配置文件:

/x/privatemodule:
        repo: https://github.com/bigwhite/privatemodule
/x/gowechat:
        repo: https://github.com/bigwhite/gowechat

上面配置文件中包含了两个自定义包导入路径的包:tonybai.com/x/privatemoduletonybai.com/x/gowechat,其中前者是一个私有仓库下的包,后者是公共包。我们基于该配置文件启动 govanityurls 服务:

$govanityurls -host tonybai.com 

接下来为了方便在本机测试,我们修改了一下 /etc/hosts,添加一条路由:

127.0.0.1 tonybai.com 

下面我们就来获取一下 gowechat 包,这里我们通过 http 方式获取 (即用带 - insecure 参数的 go get):

$go get -insecure tonybai.com/x/gowechat
go: downloading tonybai.com/x/gowechat v0.0.0-20150821085754-125b5448fdc9
go: tonybai.com/x/gowechat upgrade => v0.0.0-20150821085754-125b5448fdc9

$ls ~/go/pkg/mod/tonybai.com/x
[email protected] 

我们看到 tonybai.com/x/gowechat 被成功 get 到本地并缓存到 $GOPATH/pkg/mod/tonybai.com/x 下面。

通过自定义包路径获取私有仓库包与获取公共包并无太多差别,我们只需将私有包设置到 GOPRIVATE 环境变量中并拥有访问私有包仓库的凭证即可 (如何设置访问私有库的凭证可参见第 61 条):

$export GOPRIVATE=tonybai.com/x/privatemodule
$go get -insecure tonybai.com/x/privatemodule
go: tonybai.com/x/privatemodule upgrade => v0.0.0-20200917051519-a62573a3b770
go get: tonybai.com/x/[email protected]: parsing go.mod:
	module declares its path as: github.com/bigwhite/privatemodule
	        but was required as: tonybai.com/x/privatemodule 

上面出现一些 “瑕疵”,原因在于我的 github.com/bigwhite/privatemodulego.mod 的 module 路径尚未改为 tonybai.com/x/privatemodule

5) 通过 https 获取包数据

上面例子中,我们给 go get 传入了一个 -insecure 的参数,这样 go get 就会通过 http 协议去访问 tonybai.com/x/gowechat 了。如果我们要使用 https 获取包数据,我们该怎么做呢?这里以自签发证书为例,我们来操作一下。

首先,我们需要为 nginx 生成所需的私钥和数字证书。我们来创建 CA 以及服务端的私钥 (cert.key),并用创建的 CA 私钥来签署得到服务端的数字证书 (cert.crt):

// 创建CA私钥
$ openssl genrsa -out rootCA.key 2048
// 创建CA公钥证书
$ openssl req -x509 -new -nodes -key rootCA.key -subj "/CN=*.tonybai.com" -days 5000 -out rootCA.pem
// 创建服务端私钥(cert.key)
$ openssl genrsa -out cert.key 2048
// 创建服务端证书签发请求(cert.csr)
$ openssl req -new -key cert.key -subj "/CN=tonybai.com" -out cert.csr
// CA签发服务端证书(cert.crt)
$ openssl x509 -req -in cert.csr -CA rootCA.pem -CAkey rootCA.key -CAcreateserial -out cert.crt -days 5000

# ls
cert.crt  cert.csr  cert.key  rootCA.key  rootCA.pem  rootCA.srl 

将上面服务端私钥 (cert.key) 和证书 (cert.crt) 放置到 /etc/nginx/ 路径下面后,我们重启 nginx (nginx -s reload)。接下来,我们试试去掉 -insecure 后再来通过 go get 获取这个自定义导入路径的包:

$go get tonybai.com/x/gowechat
go get tonybai.com/x/gowechat: unrecognized import path "tonybai.com/x/gowechat": https fetch: Get "https://tonybai.com/x/gowechat?go-get=1": x509: certificate signed by unknown authority 

出错的日志显示:客户端 (go get) 无法验证这个自签发的服务端证书!。 我们将 rootCA.pem 拷贝到 /etc/ssl/cert 目录下,这个目录是 ubuntu 下存放 CA 公钥证书的标准路径。在测试 go get 前,我们先用 curl 测试一下:

$ curl https://tonybai.com/x/gowechat
<!DOCTYPE html>
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8"/>
<meta name="go-import" content="tonybai.com/x/gowechat git https://github.com/bigwhite/gowechat">
<meta name="go-source" content="tonybai.com/x/gowechat ">
<meta http-equiv="refresh" content="0; url=https://godoc.org/tonybai.com/x/gowechat">
</head>
<body>
Nothing to see here; <a href="https://godoc.org/tonybai.com/x/gowechat">see the package on godoc</a>.
</body>
</html> 

curl 测试通过! 我们再来看看 go get:

$go get tonybai.com/x/gowechat
go get tonybai.com/x/gowechat: unrecognized import path "tonybai.com/x/gowechat": https fetch: Get "https://tonybai.com/x/gowechat?go-get=1": x509: certificate signed by unknown authority 

问题依旧!难道 go get 无法从 /etc/ssl/cert 中选取适当的 CA 证书来做服务端的证书验证么?就着这个问题我在 Go 官方发现了一个类似的 issue: #18519 。从中得知,go get 仅仅会在不同平台下参考以下几个证书文件:

//$GOROOT/src/crypto/x509/root_linux.go

package x509

// Possible certificate files; stop after finding one.
var certFiles = []string{
    "/etc/ssl/certs/ca-certificates.crt",                // Debian/Ubuntu/Gentoo etc.
    "/etc/pki/tls/certs/ca-bundle.crt",                  // Fedora/RHEL 6
    "/etc/ssl/ca-bundle.pem",                            // OpenSUSE
    "/etc/pki/tls/cacert.pem",                           // OpenELEC
    "/etc/pki/ca-trust/extracted/pem/tls-ca-bundle.pem", // CentOS/RHEL 7
} 

在 ubuntu 上,/etc/ssl/certs/ca-certificates.crt 是其参考的数字证书数据来源。因此要想 go get 成功,我们需要将我们 rootCA.pem 加入到 /etc/ssl/certs/ca-certificates.crt 中去,最简单的方法就是:

$ cat rootCA.pem >> /etc/ssl/certs/ca-certificates.crt 

添加完 CA 证书数据后,我们再来试一下 go get:

$export GONOSUMDB=tonybai.com/x/gowechat
$go get tonybai.com/x/gowechat
go: tonybai.com/x/gowechat upgrade => v0.0.0-20150821085754-125b5448fdc9

go get 成功!(注:由于 tonybai.com/x/gowechat 这个自定义包导入路径仅存在于我的实验环境,它无法通过 SUMDB 的验证,因此这里提前通过设置 GONOSUMDB 将对该包的校验关闭)。

3. 小结

本节要点:

  • 自定义包导入路径具有诸多优点:通过权威导入路径减少对包用户的影响、便于管理、路径简洁短小等;
  • 使用 govanityurls 可以十分方便地为你的 Go 包自定义导入路径;
  • 一般使用 nginx 等反向代理放置在 govanityurls 的前端,便于同域名下其他服务的开展;
  • go get 默认采用 https 访问,自签署的 CA 和服务端的证书问题要处理好。如果有条件的话,还是用 letsencrypt 等提供的免费证书或自购付费证书。