Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

[インデックス 12830] ファイルの概要

このコミットは、Go言語のgo getコマンドにおけるインポートパスの解決ロジックを改善するものです。具体的には、go getがインポートパスの最初のコンポーネントをホスト名として扱う際に、そのコンポーネントが実際にホスト名として有効であるか(ドットが含まれているか)を検証するよう変更されました。これにより、ホスト名ではない文字列に対して不必要なディスカバリ処理を試みることを防ぎ、エラーメッセージをより明確にしています。

コミット

commit a8197456b1ac5802ff6c73e54e52aefd9e28a387
Author: Brad Fitzpatrick <bradfitz@golang.org>
Date:   Wed Apr 4 07:24:13 2012 -0700

    cmd/go: in go get, don't try to perform discovery on non-hosts
    
    Before, "go get -v foo/bar" was assuming "foo" was a hostname
    and trying to perform discovery on it. Now, require a dot in
    the first path component (the hostname).
    
    R=golang-dev, rsc
    CC=golang-dev
    https://golang.org/cl/5981057

GitHub上でのコミットページへのリンク

https://github.com/golang/go/commit/a8197456b1ac5802ff6c73e54e52aefd9e28a387

元コミット内容

cmd/go: in go get, don't try to perform discovery on non-hosts

以前は、go get -v foo/bar のようなコマンドを実行すると、foo をホスト名と仮定してディスカバリを試みていました。この変更により、最初のパスコンポーネント(ホスト名)にドットが含まれていることを必須とします。

変更の背景

go getコマンドは、Go言語のパッケージをリモートリポジトリから取得するための重要なツールです。このコマンドは、指定されたインポートパスを解析し、そのパスが示すコードリポジトリを特定します。特に、Goには「Vanity Import Paths(バニティインポートパス)」という概念があり、これはカスタムドメインをインポートパスとして使用し、実際のコードリポジトリへのリダイレクトをHTTPメタタグ(<meta name="go-import" ...>)を介して行う仕組みです。

この仕組みの根幹には、go getがインポートパスの最初のスラッシュまでの部分をホスト名として解釈し、そのホストに対してHTTPリクエストを送信してメタタグを探索するという動作があります。

しかし、このコミット以前のgo getの実装では、インポートパスの最初のコンポーネントが実際に有効なホスト名であるかどうかの厳密なチェックが行われていませんでした。例えば、go get foo/bar のように、fooがドメイン名として成立しない(ドットを含まない)場合でも、go getfooをホスト名と見なし、http://foo/のようなURLに対してディスカバリのためのHTTPリクエストを試みていました。このようなリクエストは、通常は失敗するか、意図しないネットワークトラフィックを発生させる可能性がありました。

このコミットは、このような非効率的で誤解を招く動作を修正することを目的としています。インポートパスの最初のコンポーネントがホスト名として機能するためには、通常、ドット(例: example.com)が含まれている必要があります。この変更により、go getは、最初のコンポーネントにドットが含まれていない場合は、それが有効なホスト名ではないと判断し、不必要なディスカバリ処理をスキップするようになります。これにより、エラーメッセージがより具体的になり、ユーザーはインポートパスの誤りを早期に認識できるようになります。

前提知識の解説

go getコマンド

go getは、Go言語のソースコードをリモートリポジトリからダウンロードし、依存関係を解決し、必要に応じてビルド・インストールを行うコマンドです。Goモジュールが導入される以前は、主にGOPATH環境変数で指定されたワークスペースにソースコードを配置していました。go getは、指定されたインポートパスに基づいて、対応するバージョン管理システム(Git, Mercurial, Subversionなど)を特定し、リポジトリをクローンまたはアップデートします。

Goのインポートパス

Go言語では、パッケージはインポートパスによって識別されます。これは通常、リポジトリのURLのような形式を取ります。例えば、github.com/user/repo/package のような形式です。このパスの最初のスラッシュまでの部分(例: github.com)は、通常、パッケージがホストされているドメイン名(ホスト名)を表します。

Vanity Import Paths (バニティインポートパス)

Goのインポートパスには、実際のコードリポジトリの場所とは異なる、カスタムのドメイン名を使用できる「Vanity Import Paths」という機能があります。これは、企業やプロジェクトが独自のドメイン名(例: mycompany.com/mypackage)をインポートパスとして提供し、そのドメインのWebサーバーがgo-importメタタグ(<meta name="go-import" content="mycompany.com/mypackage git https://github.com/mycompany/mypackage">のような形式)を返すことで実現されます。go getは、このメタタグを読み取り、実際のGitやMercurialリポジトリのURLを特定してクローンします。

ホスト名とドットの重要性

インターネットにおけるホスト名(ドメイン名)は、通常、複数のラベルをドットで区切った形式で構成されます(例: www.example.com)。このドットは、ドメイン階層の区切りを示し、DNS(Domain Name System)によってIPアドレスに解決されるための重要な要素です。例えば、example.comはトップレベルドメイン(.com)とセカンドレベルドメイン(example)から構成されます。単一の単語(例: foo)は、通常、完全なホスト名としては機能しません。ローカルネットワーク内でのみ使用されるホスト名や、特定のDNS設定を持つ場合を除き、インターネット上のリソースを指すホスト名にはドットが含まれるのが一般的です。

go getがインポートパスの最初のコンポーネントをホスト名として扱う際、この「ドットの存在」は、それが有効なドメイン名であるかどうかの簡易的なチェックとして機能します。ドットがない場合、それは通常、有効なインターネットホスト名ではないため、go getがそのホストに対してHTTPリクエストを試みることは無意味であり、エラーとなる可能性が高いです。

技術的詳細

このコミットの技術的な核心は、go getコマンドがインポートパスを解析し、リモートリポジトリのルートを特定するrepoRootForImportDynamic関数内のロジック変更にあります。

go getがインポートパス(例: foo/bar)を受け取ると、まず最初のスラッシュ(/)の位置を探します。このスラッシュより前の部分がホスト名として扱われます。

変更前は、スラッシュが見つかった場合、その前の部分(host変数に格納される)がそのままホスト名として使用され、そのホストに対してHTTP/HTTPSリクエストが試みられていました。例えば、foo/barの場合、hostfooとなり、http://foo/へのリクエストが試みられます。これは、fooが有効なドメイン名ではないため、通常は名前解決に失敗するか、接続タイムアウトとなります。

このコミットでは、以下の2つのチェックが追加されました。

  1. スラッシュの存在チェック: 以前と同様に、インポートパスにスラッシュが含まれていない場合はエラーとなります。これは、fooのような単一のコンポーネントでは、それがパッケージパスなのかホスト名なのか区別できないためです。エラーメッセージがより具体的になりました(import path doesn't contain a slash)。
  2. ホスト名にドットの存在チェック: スラッシュが見つかり、ホスト名部分が抽出された後、そのホスト名文字列にドット(.)が含まれているかどうかがstrings.Contains(host, ".")でチェックされます。
    • もしドットが含まれていない場合(例: foo)、go getはその文字列を有効なホスト名ではないと判断し、import path doesn't contain a hostnameというエラーを返して処理を中断します。これにより、無効なホスト名への不必要なHTTPリクエストが回避されます。
    • ドットが含まれている場合(例: example.com)、そのホスト名が有効であると見なされ、通常通りHTTP/HTTPSリクエストが続行されます。

この変更により、go getはより賢明になり、無効なインポートパスに対して早期にエラーを返すことで、デバッグの手間を省き、ネットワークリソースの無駄遣いを防ぎます。

コアとなるコードの変更箇所

--- a/src/cmd/go/vcs.go
+++ b/src/cmd/go/vcs.go
@@ -422,11 +422,15 @@ func repoRootForImportPathStatic(importPath, scheme string) (*repoRoot, error) {
 func repoRootForImportDynamic(importPath string) (*repoRoot, error) {
 	slash := strings.Index(importPath, "/")
 	if slash < 0 {
-		return nil, fmt.Errorf("missing / in import %q", importPath)
+		return nil, errors.New("import path doesn't contain a slash")
+	}
+	host := importPath[:slash]
+	if !strings.Contains(host, ".") {
+		return nil, errors.New("import path doesn't contain a hostname")
 	}
 	urlStr, body, err := httpsOrHTTP(importPath)
 	if err != nil {
-		return nil, fmt.Errorf("http/https fetch for import %q: %v", importPath, err)
+		return nil, fmt.Errorf("http/https fetch: %v", err)
 	}\n 	defer body.Close()\n 	metaImport, err := matchGoImport(parseMetaGoImports(body), importPath)\n```

## コアとなるコードの解説

変更は`src/cmd/go/vcs.go`ファイル内の`repoRootForImportDynamic`関数に集中しています。この関数は、動的なインポートパス(つまり、`go-import`メタタグを介して解決される可能性のあるパス)のリポジトリルートを特定する役割を担っています。

1.  **`slash := strings.Index(importPath, "/")`**:
    *   これは、インポートパス内で最初のスラッシュ(`/`)の位置を探します。このスラッシュは、ホスト名とリポジトリパスの区切りを示します。

2.  **`if slash < 0 { ... }` ブロックの変更**:
    *   **変更前**: `return nil, fmt.Errorf("missing / in import %q", importPath)`
        *   インポートパスにスラッシュがない場合、`missing / in import "..."`というエラーメッセージを返していました。
    *   **変更後**: `return nil, errors.New("import path doesn't contain a slash")`
        *   エラーメッセージがより簡潔で直接的な「`import path doesn't contain a slash`」に変更されました。これは、ユーザーがインポートパスの形式を理解するのに役立ちます。

3.  **新しいホスト名チェックの追加**:
    *   **`host := importPath[:slash]`**:
        *   スラッシュが見つかった場合、インポートパスの先頭からスラッシュまでの部分を`host`変数に抽出します。これが`go get`がホスト名として扱う部分です。
    *   **`if !strings.Contains(host, ".") { ... }`**:
        *   この行がこのコミットの主要な変更点です。抽出された`host`文字列にドット(`.`)が含まれていないかどうかをチェックします。
        *   もしドットが含まれていない場合(例: `foo`)、それは有効なインターネットホスト名ではないと判断されます。
        *   その場合、`return nil, errors.New("import path doesn't contain a hostname")`というエラーを返して、関数の実行を終了します。これにより、`foo`のような無効なホスト名に対してHTTPリクエストを試みる無駄な処理が回避されます。

4.  **`httpsOrHTTP`呼び出し後のエラーメッセージの変更**:
    *   **変更前**: `return nil, fmt.Errorf("http/https fetch for import %q: %v", importPath, err)`
        *   HTTP/HTTPSフェッチが失敗した場合、インポートパスを含む詳細なエラーメッセージを返していました。
    *   **変更後**: `return nil, fmt.Errorf("http/https fetch: %v", err)`
        *   エラーメッセージが「`http/https fetch: ...`」と簡潔になりました。これは、ホスト名チェックが追加されたことで、より一般的なフェッチエラーに焦点を当てるようになったためと考えられます。

これらの変更により、`go get`はインポートパスの最初のコンポーネントが有効なホスト名である可能性が高い場合にのみネットワークリクエストを行うようになり、エラーハンドリングが改善されました。

## 関連リンク

*   Go CL 5981057: [https://golang.org/cl/5981057](https://golang.org/cl/5981057)

## 参考にした情報源リンク

*   go.dev: `go get` command documentation (general understanding of `go get` and import paths)
*   sagikazarmark.hu: Understanding Go Vanity Import Paths
*   stackoverflow.com: Explanations on `go get` and import path resolution
*   github.com: Discussions or issues related to `go get` import path errors
*   netgate.com: Information on FQDN and trailing dots in hostnames
*   superuser.com: Discussions on hostname validity and DNS
*   stackexchange.com: Technical explanations on hostname parsing and HTTP requests
*   youtube.com: Tutorials or explanations on Go modules and `go get` (less direct, but provides context)