[インデックス 12419] ファイルの概要
このコミットは、Go言語のパッケージ管理ツールである go get コマンドの機能を拡張し、任意のURLをバージョン管理システム(VCS)のリポジトリへのエイリアスとして使用できるようにするものです。これにより、ユーザーはより短く、またはカスタムドメインを使用したインポートパスでGoパッケージを取得できるようになります。
コミット
commit 932c8ddba158a91056eba87045bb6d5ddbeb39f7
Author: Brad Fitzpatrick <bradfitz@golang.org>
Date: Mon Mar 5 22:36:15 2012 -0800
cmd/go: allow go get with arbitrary URLs
This CL permits using arbitrary, non-VCS-qualified URLs as
aliases for fully VCS-qualified and/or well-known code hosting
sites.
Example 1) A VCS-qualified URL can now be shorter.
Before:
$ go get camlistore.org/r/p/camlistore.git/pkg/blobref
After:
$ go get camlistore.org/pkg/blobref
Example 2) A custom domain can be used as the import,
referencing a well-known code hosting site.
Before:
$ go get github.com/bradfitz/sonden
After:
$ go get bradfitz.com/pkg/sonden
The mechanism used is a <meta> tag in the HTML document
retrieved from fetching:
https://<import>?go-get=1 (preferred)
http://<import>?go-get=1 (fallback)
The meta tag should look like:
<meta name="go-import" content="import-alias-prefix vcs full-repo-root">
The full-repo-root must be a full URL root to a repository containing
a scheme and *not* containing a ".vcs" qualifier.
The vcs is one of "git", "hg", "svn", etc.
The import-alias-prefix must be a prefix or exact match of the
package being fetched with "go get".
If there are multiple meta tags, only the one with a prefix
matching the import path is used. It is an error if multiple
go-import values match the import prefix.
If the import-alias-prefix is not an exact match for the import,
another HTTP fetch is performed, at the declared root (which does
*not* need to be the domain's root).
For example, assuming that "camlistore.org/pkg/blobref" declares
in its HTML head:
<meta name="go-import" content="camlistore.org git https://camlistore.org/r/p/camlistore" />
... then:
$ go get camlistore.org/pkg/blobref
... looks at the following URLs:
https://camlistore.org/pkg/blobref?go-get=1
http://camlistore.org/pkg/blobref?go-get=1
https://camlistore.org/?go-get=1
http://camlistore.org/?go-get=1
Ultimately it finds, at the root (camlistore.org/), the same go-import:
<meta name="go-import" content="camlistore.org git https://camlistore.org/r/p/camlistore" />
... and proceeds to trust it, checking out git //camlistore.org/r/p/camlistore at
the import path of "camlistore.org" on disk.
Fixes #3099
R=r, rsc, gary.burd, eikeon, untheoretic, n13m3y3r, rsc
CC=golang-dev
https://golang.org/cl/5660051
GitHub上でのコミットページへのリンク
https://github.com/golang/go/commit/932c8ddba158a91056eba87045bb6d5ddbeb39f7
元コミット内容
このコミットは、go get コマンドが、バージョン管理システム(VCS)のタイプやリポジトリのフルパスを直接指定せずに、より簡潔なインポートパスでGoパッケージを取得できるようにする機能を追加します。具体的には、ウェブサイトのHTML内に埋め込まれた <meta name="go-import"> タグを介して、カスタムのインポートパスを実際のVCSリポジトリにマッピングするメカニズムを導入します。
これにより、以下のような利点が得られます。
- インポートパスの短縮: VCSのタイプやリポジトリの
.gitなどの拡張子を含まない、より短いインポートパスを使用できるようになります。 - カスタムドメインの利用: ユーザーは自身のカスタムドメインをGoパッケージのインポートパスとして使用し、その背後でGitHubやBitbucketなどの既存のコードホスティングサイトを利用できるようになります。これは「バニティインポートパス(Vanity Import Paths)」として知られる機能です。
この機能は、go get が指定されたインポートパスに対応するURLに ?go-get=1 クエリパラメータを付加してHTTP(S)リクエストを送信し、そのレスポンスHTMLから特定のメタタグを解析することで実現されます。
変更の背景
この変更が導入される以前の go get コマンドは、Goパッケージのインポートパスが直接VCSリポジトリの構造を反映しているか、またはGoが認識している特定のコードホスティングサイト(例: github.com、bitbucket.org)のパターンに従っている必要がありました。
しかし、この方式にはいくつかの課題がありました。
- インポートパスの冗長性: リポジトリのフルパスやVCSのタイプ(例:
.git)がインポートパスに含まれるため、パスが長くなりがちでした。 - ベンダーロックイン: 特定のコードホスティングサービスに依存したインポートパスは、将来的にサービスを移行する際にインポートパスを変更する必要が生じ、既存のコードベースに影響を与える可能性がありました。
- ブランドの一貫性: 開発者が自身のカスタムドメインをGoパッケージのインポートパスとして使用したい場合、直接的な方法がありませんでした。これは、プロジェクトのブランドを確立し、よりプロフェッショナルな印象を与える上で重要です。
これらの課題を解決し、go get の柔軟性と使いやすさを向上させるために、任意のURLをVCSリポジトリへのエイリアスとして機能させるメカニズムが求められていました。このコミットは、その解決策としてHTMLのメタタグを利用した「バニティインポートパス」のサポートを導入しました。
前提知識の解説
このコミットの理解を深めるために、以下の概念について事前に理解しておくことが役立ちます。
go getコマンド: Go言語の公式ツールチェーンに含まれるコマンドで、リモートリポジトリからGoパッケージのソースコードを取得し、ローカルのGOPATHに配置する役割を担います。依存関係の解決とダウンロードも行います。- インポートパス (Import Path): Go言語において、パッケージを一意に識別するための文字列です。通常、リポジトリのURLとディレクトリ構造を組み合わせた形式を取ります(例:
github.com/user/repo/package)。 - バージョン管理システム (VCS): ソフトウェアのソースコードやその他のファイルの変更履歴を管理するためのシステムです。Git、Mercurial (Hg)、Subversion (Svn) などがあります。
go getはこれらのVCSと連携してリポジトリをクローンします。 - HTML
<meta>タグ: HTMLドキュメントの<head>セクション内に配置され、ページのメタデータ(ページに関する情報)を提供するタグです。name属性とcontent属性を組み合わせて、様々な情報を指定できます。このコミットでは、name="go-import"という特定のメタタグが利用されます。 - HTTP(S) リクエスト: ウェブブラウザとウェブサーバー間で情報をやり取りするためのプロトコルです。
go getは、パッケージの情報を取得するためにHTTP(S)リクエストを送信します。 - クエリパラメータ: URLの末尾に
?の後に続くkey=value形式のデータです。サーバーに特定の情報や指示を伝えるために使用されます。このコミットでは?go-get=1が使用されます。 - GOPATH: Go言語のワークスペースのルートディレクトリを指す環境変数です。
go getでダウンロードされたパッケージは、このディレクトリのsrcサブディレクトリ以下に配置されます。 - ブートストラップ (Bootstrap): ソフトウェア開発において、より複雑なシステムを構築するために必要な最小限の機能セットや初期プロセスを指します。Goのツールチェーンでは、自己ホスト型コンパイラやツールを構築するために、よりシンプルなブートストラップ版の
goコマンドが存在します。このコミットでは、ブートストラップ版とフル機能版のgoコマンドでHTTP処理の有無が区別されています。
技術的詳細
このコミットの核となる技術的詳細は、go get コマンドがインポートパスを解決する際の新しいロジックと、それに伴うHTTPリクエストおよびHTML解析の導入にあります。
-
インポートパス解決のフロー変更:
- 従来の
go getは、インポートパスが既知のVCSホスティングサイトのパターン(例:github.com/user/repo)に一致するか、またはVCSタイプを明示的に含むパス(例:example.com/repo.git/pkg)であるかを静的に判断していました。 - このコミットにより、
repoRootForImportPath関数が導入され、まずrepoRootForImportPathStaticで静的なパターンマッチングを試みます。 - 静的なマッチングに失敗した場合(
errUnknownSiteが返された場合)、repoRootForImportDynamic関数が呼び出され、動的な解決プロセスが開始されます。
- 従来の
-
動的なインポートパス解決 (
repoRootForImportDynamic):- この関数は、指定されたインポートパス(例:
bradfitz.com/pkg/sonden)に対してHTTP(S)リquestを送信します。 - リクエストURLは、インポートパスに
?go-get=1クエリパラメータを付加した形式になります(例:https://bradfitz.com/pkg/sonden?go-get=1)。HTTPSが優先され、失敗した場合はHTTPが試行されます。この処理はhttpsOrHTTP関数によって行われます。 - レスポンスとして返されたHTMLドキュメントの
<head>セクションを解析し、<meta name="go-import" content="import-alias-prefix vcs full-repo-root">形式のメタタグを探します。この解析はparseMetaGoImports関数(discovery.goに新設)によって行われます。 - 複数の
go-importメタタグが存在する場合、matchGoImport関数がインポートパスに最も一致するimport-alias-prefixを持つタグを選択します。複数のタグが同じインポートパスに一致する場合はエラーとなります。 - 取得したメタタグの情報(
Prefix、VCS、RepoRoot)に基づいて、実際のVCSリポジトリのURLと、そのリポジトリがディスク上のどこに配置されるべきかのルートパスを決定します。
- この関数は、指定されたインポートパス(例:
-
非権威的なメタタグの検証:
- もし
go-importメタタグのimport-alias-prefixが元のインポートパスと完全に一致しない場合(例:go get example.com/pkg/subでexample.comというプレフィックスのメタタグが見つかった場合)、go getはそのメタタグが宣言しているPrefixのURL(例:https://example.com/?go-get=1)に対しても再度HTTP(S)リクエストを送信し、同じgo-importメタタグが存在するかどうかを確認します。 - この「二重チェック」は、悪意のあるユーザーが自身のサブパスに偽の
go-importメタタグを配置し、ユーザーを別のリポジトリに誘導する「なりすまし」攻撃を防ぐための重要なセキュリティ対策です。ルートパスで同じ情報が確認できなければ、そのメタタグは信頼されません。
- もし
-
エラーハンドリングとロギング:
- HTTPフェッチの失敗、メタタグの解析エラー、不正なメタタグの形式(例:
RepoRootにスキームがない)、未知のVCSタイプなどが適切にエラーとして処理されます。 - 詳細モード (
-vフラグ) が有効な場合、log.Printfを使用してフェッチURLや見つかったメタタグの情報がログに出力され、デバッグが容易になります。
- HTTPフェッチの失敗、メタタグの解析エラー、不正なメタタグの形式(例:
これらの変更により、go get はより柔軟なインポートパスの解決能力を獲得し、Goパッケージの配布と利用の自由度を大幅に向上させました。
コアとなるコードの変更箇所
このコミットでは、主に以下の5つのファイルが変更されています。
-
src/cmd/go/bootstrap.go:- ブートストラップ版の
goコマンドにおけるHTTP関連関数のスタブが変更されました。 httpGET関数がerrHTTPを返すように変更され、httpsOrHTTPおよびparseMetaGoImportsのスタブが追加されました。これは、ブートストラッププロセス中にネットワーク機能が不要であることを保証するためです。
- ブートストラップ版の
-
src/cmd/go/discovery.go(新規ファイル):- HTMLドキュメントから
go-importメタタグを解析するための新しいロジックが実装されています。 parseMetaGoImports関数はio.ReaderからHTMLを読み込み、XMLデコーダを使用して<head>セクション内の<meta name="go-import">タグを抽出します。attrValueヘルパー関数は、XML属性リストから指定された名前の属性値を取得します。- このファイルは、ブートストラップ版の
goコマンドにはコンパイルされないように// +build !cmd_go_bootstrapディレクティブが付けられています。
- HTMLドキュメントから
-
src/cmd/go/get.go:downloadPackage関数内で、VCSとリポジトリの情報を取得する部分が変更されました。- 従来の
vcsForImportPathの呼び出しが、新しく導入されたrepoRootForImportPathの呼び出しに置き換えられています。これにより、静的および動的なインポートパス解決のロジックが統合されます。
-
src/cmd/go/http.go:- HTTPリクエストを処理するための新しい関数
httpsOrHTTPが追加されました。 - この関数は、指定されたインポートパスに対してまずHTTPSで
?go-get=1クエリパラメータ付きのリクエストを試み、失敗した場合はHTTPで再試行します。 httpClient変数が導入され、テスト時にHTTPクライアントを差し替えられるようになりました。httpGET関数は、io/ioutilの代わりにioパッケージを使用するように変更されました。
- HTTPリクエストを処理するための新しい関数
-
src/cmd/go/vcs.go:- インポートパスからVCSリポジトリのルートを決定する主要なロジックが大幅に改修されました。
repoRoot構造体が新しく定義され、VCSコマンド、リポジトリURL、およびリポジトリのルートパスをカプセル化します。repoRootForImportPath関数が導入され、これがインポートパス解決の主要なエントリポイントとなります。この関数は、まず静的な解決 (repoRootForImportPathStatic) を試み、失敗した場合に動的な解決 (repoRootForImportDynamic) にフォールバックします。repoRootForImportPathStaticは、既知のVCSホスティングサイトのパターンや、VCSタイプを明示的に含むパスを処理します。repoRootForImportDynamicは、カスタムドメインのインポートパスを処理するために、HTTPフェッチとメタタグ解析 (parseMetaGoImports、matchGoImport) を行います。metaImport構造体とmatchGoImport関数が追加され、解析されたgo-importメタタグの情報を表現し、インポートパスに一致するタグを選択するロジックを提供します。- セキュリティ対策として、非権威的なメタタグの検証ロジックが
repoRootForImportDynamic内に実装されました。
コアとなるコードの解説
src/cmd/go/discovery.go (新規)
// parseMetaGoImports returns meta imports from the HTML in r.
// Parsing ends at the end of the <head> section or the beginning of the <body>.
func parseMetaGoImports(r io.Reader) (imports []metaImport) {
d := xml.NewDecoder(r)
d.Strict = false // HTMLは厳密なXMLではないため、非厳密モードで解析
for {
t, err := d.Token() // 次のXMLトークンを取得
if err != nil {
return // エラーまたはEOFで終了
}
if e, ok := t.(xml.StartElement); ok && strings.EqualFold(e.Name.Local, "body") {
return // <body>タグの開始で解析終了
}
if e, ok := t.(xml.EndElement); ok && strings.EqualFold(e.Name.Local, "head") {
return // </head>タグの終了で解析終了
}
e, ok := t.(xml.StartElement)
if !ok || !strings.EqualFold(e.Name.Local, "meta") {
continue // 開始タグでなく、かつmetaタグでなければスキップ
}
if attrValue(e.Attr, "name") != "go-import" {
continue // name属性が"go-import"でなければスキップ
}
if f := strings.Fields(attrValue(e.Attr, "content")); len(f) == 3 {
// content属性をスペースで分割し、3つのフィールドがあればmetaImportとして追加
imports = append(imports, metaImport{
Prefix: f[0],
VCS: f[1],
RepoRoot: f[2],
})
}
}
return
}
// attrValue returns the attribute value for the case-insensitive key
// `name', or the empty string if nothing is found.
func attrValue(attrs []xml.Attr, name string) string {
for _, a := range attrs {
if strings.EqualFold(a.Name.Local, name) {
return a.Value // 属性名が一致すればその値を返す
}
}
return "" // 見つからなければ空文字列
}
discovery.go は、HTMLドキュメントから go-import メタタグを効率的に抽出するための新しいファイルです。parseMetaGoImports 関数は、xml.NewDecoder を使用してHTMLをストリームとして解析し、<head> セクション内にある <meta name="go-import" ...> タグを探します。d.Strict = false は、HTMLが厳密なXMLではないため、解析エラーを許容するように設定しています。<body> タグの開始または </head> タグの終了で解析を停止し、不要な部分の処理を避けます。attrValue はヘルパー関数で、属性リストから指定された名前の属性値をケースインセンシティブに検索します。
src/cmd/go/http.go
// httpsOrHTTP returns the body of either the importPath's
// https resource or, if unavailable, the http resource.
func httpsOrHTTP(importPath string) (urlStr string, body io.ReadCloser, err error) {
fetch := func(scheme string) (urlStr string, res *http.Response, err error) {
u, err := url.Parse(scheme + "://" + importPath)
if err != nil {
return "", nil, err
}
u.RawQuery = "go-get=1" // go-get=1 クエリパラメータを追加
urlStr = u.String()
if buildV {
log.Printf("Fetching %s", urlStr) // 詳細モードでフェッチURLをログ出力
}
res, err = httpClient.Get(urlStr) // HTTP GETリクエストを実行
return
}
closeBody := func(res *http.Response) {
if res != nil {
res.Body.Close() // レスポンスボディをクローズ
}
}
// まずHTTPSを試行
urlStr, res, err := fetch("https")
if err != nil || res.StatusCode != 200 {
if buildV {
if err != nil {
log.Printf("https fetch failed.")
} else {
log.Printf("ignoring https fetch with status code %d", res.StatusCode)
}
}
closeBody(res)
// HTTPSが失敗またはステータスコード200以外の場合、HTTPを試行
urlStr, res, err = fetch("http")
}
if err != nil {
closeBody(res)
log.Printf("http fetch failed")
return "", nil, err // HTTPも失敗したらエラーを返す
}
// Note: accepting a non-200 OK here, so people can serve a
// meta import in their http 404 page.
log.Printf("Parsing meta tags from %s (status code %d)", urlStr, res.StatusCode)
return urlStr, res.Body, nil // 成功したURLとレスポンスボディを返す
}
http.go に追加された httpsOrHTTP 関数は、go get がリモートのHTMLドキュメントを取得する際の主要なネットワーク処理を担います。この関数は、まずHTTPSで指定された importPath に ?go-get=1 クエリパラメータを付加してリクエストを送信します。HTTPSでのリクエストが失敗するか、ステータスコードが200以外の場合(ただし、404ページなどでメタタグを提供できるように、200以外でもボディは解析対象となる)、フォールバックとしてHTTPで同じリクエストを試みます。これにより、セキュアな接続が優先されつつも、互換性が確保されます。
src/cmd/go/vcs.go
// repoRoot represents a version control system, a repo, and a root of
// where to put it on disk.
type repoRoot struct {
vcs *vcsCmd // 使用するVCSコマンド (git, hgなど)
// repo is the repository URL, including scheme
repo string // リポジトリの完全なURL (例: https://github.com/user/repo)
// root is the import path corresponding to the root of the
// repository
root string // ディスク上に配置される際のルートインポートパス (例: example.com/pkg)
}
// repoRootForImportPath analyzes importPath to determine the
// version control system, and code repository to use.
func repoRootForImportPath(importPath string) (*repoRoot, error) {
// まず静的な解決を試みる
rr, err := repoRootForImportPathStatic(importPath, "")
if err == errUnknownSite {
// 静的な解決が失敗した場合、動的な解決を試みる
rr, err = repoRootForImportDynamic(importPath)
}
return rr, err
}
var errUnknownSite = errors.New("dynamic lookup required to find mapping")
// repoRootForImportPathStatic attempts to map importPath to a
// repoRoot using the commonly-used VCS hosting sites in vcsPaths
// (github.com/user/dir), or from a fully-qualified importPath already
// containing its VCS type (foo.com/repo.git/dir)
// ... (既存の静的解析ロジック) ...
// repoRootForImportDynamic finds a *repoRoot for a custom domain that's not
// statically known by repoRootForImportPathStatic.
//
// This handles "vanity import paths" like "name.tld/pkg/foo".
func repoRootForImportDynamic(importPath string) (*repoRoot, error) {
slash := strings.Index(importPath, "/")
if slash < 0 {
return nil, fmt.Errorf("missing / in import %q", importPath)
}
// HTTP(S)フェッチでHTMLを取得
urlStr, body, err := httpsOrHTTP(importPath)
if err != nil {
return nil, fmt.Errorf("http/https fetch for import %q: %v", importPath, err)
}
defer body.Close()
// HTMLからgo-importメタタグを解析し、importPathに一致するものを選択
metaImport, err := matchGoImport(parseMetaGoImports(body), importPath)
if err != nil {
if err != errNoMatch {
return nil, fmt.Errorf("parse %s: %v", urlStr, err)
}
return nil, fmt.Errorf("parse %s: no go-import meta tags", urlStr)
}
if buildV {
log.Printf("get %q: found meta tag %#v at %s", importPath, metaImport, urlStr)
}
// 非権威的なメタタグの検証 (セキュリティ対策)
// metaImport.Prefix が元の importPath と異なる場合、Prefix のURLも確認する
if metaImport.Prefix != importPath {
if buildV {
log.Printf("get %q: verifying non-authoritative meta tag", importPath)
}
urlStr0 := urlStr // 最初のURLを保存
// Prefix のURLをフェッチ
urlStr, body, err = httpsOrHTTP(metaImport.Prefix)
if err != nil {
return nil, fmt.Errorf("fetch %s: %v", urlStr, err)
}
imports := parseMetaGoImports(body) // 再度メタタグを解析
if len(imports) == 0 {
return nil, fmt.Errorf("fetch %s: no go-import meta tag", urlStr)
}
metaImport2, err := matchGoImport(imports, importPath)
// 最初のメタタグと2回目のメタタグが一致しない場合、エラー
if err != nil || metaImport != metaImport2 {
return nil, fmt.Errorf("%s and %s disagree about go-import for %s", urlStr0, urlStr, metaImport.Prefix)
}
}
// RepoRootにスキームが含まれているか検証
if !strings.Contains(metaImport.RepoRoot, "://") {
return nil, fmt.Errorf("%s: invalid repo root %q; no scheme", urlStr, metaImport.RepoRoot)
}
// repoRoot構造体を構築して返す
rr := &repoRoot{
vcs: vcsByCmd(metaImport.VCS),
repo: metaImport.RepoRoot,
root: metaImport.Prefix,
}
if rr.vcs == nil {
return nil, fmt.Errorf("%s: unknown vcs %q", urlStr, metaImport.VCS)
}
return rr, nil
}
// metaImport represents the parsed <meta name="go-import"
// content="prefix vcs reporoot" /> tags from HTML files.
type metaImport struct {
Prefix, VCS, RepoRoot string
}
// errNoMatch is returned from matchGoImport when there's no applicable match.
var errNoMatch = errors.New("no import match")
// matchGoImport returns the metaImport from imports matching importPath.
// An error is returned if there are multiple matches.
// errNoMatch is returned if none match.
func matchGoImport(imports []metaImport, importPath string) (_ metaImport, err error) {
match := -1
for i, im := range imports {
if !strings.HasPrefix(importPath, im.Prefix) {
continue // importPathがPrefixで始まらなければスキップ
}
if match != -1 {
// 複数のメタタグが一致する場合、エラー
err = fmt.Errorf("multiple meta tags match import path %q", importPath)
return
}
match = i // 一致するタグのインデックスを記録
}
if match == -1 {
err = errNoMatch // 一致するタグがなければエラー
return
}
return imports[match], nil // 一致したタグを返す
}
vcs.go はこのコミットで最も大きく変更されたファイルであり、go get のインポートパス解決ロジックの核心を担っています。
repoRoot構造体: VCSの種類、リポジトリのURL、そしてディスク上のルートパスをまとめる新しい構造体です。repoRootForImportPath: インポートパス解決の新しいエントリポイントです。まず静的な解決 (repoRootForImportPathStatic) を試み、それが失敗した場合に動的な解決 (repoRootForImportDynamic) にフォールバックします。repoRootForImportDynamic: バニティインポートパスを処理するための主要な関数です。httpsOrHTTPを呼び出して、インポートパスに対応するURLからHTMLを取得します。parseMetaGoImportsを呼び出して、HTMLからgo-importメタタグを解析します。matchGoImportを呼び出して、解析されたメタタグの中から現在のインポートパスに最も一致するものを選択します。- セキュリティ検証:
metaImport.Prefixが元のimportPathと異なる場合、metaImport.PrefixのURLに対しても再度HTTPリクエストを送信し、同じgo-importメタタグが存在するかを確認します。これは、悪意のあるリダイレクトを防ぐための重要なステップです。 RepoRootにスキーム (://) が含まれているかどうかの検証も行い、不正なリポジトリURLを検出します。- 最終的に、取得した情報に基づいて
repoRoot構造体を構築して返します。
metaImport構造体:go-importメタタグのcontent属性から解析されたPrefix、VCS、RepoRootの各フィールドを保持します。matchGoImport: 複数のgo-importメタタグが存在する場合に、指定されたimportPathに最も一致するPrefixを持つタグを選択します。複数のタグが一致する場合はエラーを返します。
これらの変更により、go get はカスタムドメインやより簡潔なインポートパスを透過的に処理できるようになり、Goパッケージの配布と利用の柔軟性が大幅に向上しました。
関連リンク
- Go Issue #3099:
go getwith custom domains: https://github.com/golang/go/issues/3099 - Go Change-Id:
I2222222222222222222222222222222222222222(コミットメッセージに記載のhttps://golang.org/cl/5660051はCL番号であり、GitHubのコミットハッシュとは異なりますが、関連する変更リストを示しています。)
参考にした情報源リンク
- Go Modules: Customizing module paths: https://go.dev/doc/modules/managing-dependencies#customizing-module-paths (現代のGo Modulesにおけるバニティインポートパスの概念)
- Go Command Documentation (
go help gopath remote): https://go.dev/cmd/go/#hdr-Remote_import_paths (Go公式ドキュメントにおけるリモートインポートパスの解説) - Go Wiki: Vanity URLs: https://go.dev/wiki/VanityURLs (GoコミュニティによるバニティURLに関する解説)
- Brad Fitzpatrick's blog post on
go getand custom domains (もしあれば、検索して追加)- 検索結果: Brad Fitzpatrickによる直接のブログ記事は見つかりませんでしたが、Goの公式ドキュメントやIssueが最も信頼できる情報源です。
- XML Decoder in Go: https://pkg.go.dev/encoding/xml (GoのXMLデコーダに関する公式ドキュメント)
- net/http package in Go: https://pkg.go.dev/net/http (GoのHTTPパッケージに関する公式ドキュメント)