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

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

このコミットは、Go言語の標準ライブラリにおけるnet/httpパッケージからnet/http/httputilパッケージへの関数移動に対応するため、gofixツールに新しい修正(fix)を追加するものです。具体的には、http.DumpRequesthttp.NewChunkedReaderなどの関数がhttputilパッケージに移動された際に、既存のGoコードを自動的に更新するためのロジックが導入されています。これにより、開発者は手動でコードを修正する手間を省き、APIの変更に容易に対応できるようになります。

コミット

commit 19d064f68a275064c4a288f0c89885524b87fe9e
Author: Yasuhiro Matsumoto <mattn.jp@gmail.com>
Date:   Tue Dec 13 16:23:06 2011 -0500

    gofix: add fix httputil
    
    R=r, rsc, adg
    CC=golang-dev
    https://golang.org/cl/5364056
---
 src/cmd/gofix/Makefile         |   1 +
 src/cmd/gofix/httputil.go      |  63 +++++++++++++++++++++
 src/cmd/gofix/httputil_test.go | 122 +++++++++++++++++++++++++++++++++++++++++
 3 files changed, 186 insertions(+)

diff --git a/src/cmd/gofix/Makefile b/src/cmd/gofix/Makefile
index 6ae4acc248..2f64a5bfa8 100644
--- a/src/cmd/gofix/Makefile
+++ b/src/cmd/gofix/Makefile
@@ -16,6 +16,7 @@ GOFILES=\
 	httpfs.go\\\
 	httpheaders.go\\\
 	httpserver.go\\\
+\thttputil.go\\\
 	imagecolor.go\\\
 	imagenew.go\\\
 	iocopyn.go\\\
diff --git a/src/cmd/gofix/httputil.go b/src/cmd/gofix/httputil.go
new file mode 100644
index 0000000000..86c42e1602
--- /dev/null
+++ b/src/cmd/gofix/httputil.go
@@ -0,0 +1,63 @@
+// Copyright 2011 The Go Authors.  All rights reserved.\
+// Use of this source code is governed by a BSD-style\
+// license that can be found in the LICENSE file.\
+\
+package main\
+\
+import "go/ast"\
+\
+func init() {\n+\tregister(httputilFix)\n+}\n+\
+var httputilFix = fix{\n+\t\"httputil\",\n+\t\"2011-11-18\",\n+\thttputil,\n+\t`Move some functions in http package into httputil package.\n+\n+http://codereview.appspot.com/5336049\n+`,\n+}\n+\
+var httputilFuncs = []string{\n+\t\"DumpRequest\",\n+\t\"DumpRequestOut\",\n+\t\"DumpResponse\",\n+\t\"NewChunkedReader\",\n+\t\"NewChunkedWriter\",\n+\t\"NewClientConn\",\n+\t\"NewProxyClientConn\",\n+\t\"NewServerConn\",\n+\t\"NewSingleHostReverseProxy\",\n+}\n+\
+func httputil(f *ast.File) bool {\n+\tif imports(f, \"net/http/httputil\") {\n+\t\treturn false\n+\t}\n+\
+\tfixed := false\n+\
+\twalk(f, func(n interface{}) {\n+\t\t// Rename package name.\n+\t\tif expr, ok := n.(ast.Expr); ok {\n+\t\t\tfor _, s := range httputilFuncs {\n+\t\t\t\tif isPkgDot(expr, \"http\", s) {\n+\t\t\t\t\tif !fixed {\n+\t\t\t\t\t\taddImport(f, \"net/http/httputil\")\n+\t\t\t\t\t\tfixed = true\n+\t\t\t\t\t}\n+\t\t\t\t\texpr.(*ast.SelectorExpr).X.(*ast.Ident).Name = \"httputil\"\n+\t\t\t\t}\n+\t\t\t}\n+\t\t}\n+\t})\n+\n+\t// Remove the net/http import if no longer needed.\n+\tif fixed && !usesImport(f, \"net/http\") {\n+\t\tdeleteImport(f, \"net/http\")\n+\t}\n+\
+\treturn fixed\n+}\ndiff --git a/src/cmd/gofix/httputil_test.go b/src/cmd/gofix/httputil_test.go
new file mode 100644
index 0000000000..83e9f6dfb3
--- /dev/null
+++ b/src/cmd/gofix/httputil_test.go
@@ -0,0 +1,122 @@
+// Copyright 2011 The Go Authors.  All rights reserved.\
+// Use of this source code is governed by a BSD-style\
+// license that can be found in the LICENSE file.\
+\
+package main\
+\
+func init() {\n+\taddTestCases(httputilTests, httputil)\n+}\n+\
+var httputilTests = []testCase{\n+\t{\n+\t\tName: \"httputil.0\",\n+\t\tIn: `package main\n+\n+import \"net/http\"\n+\n+func f() {\n+\thttp.DumpRequest(nil, false)\n+\thttp.DumpRequestOut(nil, false)\n+\thttp.DumpResponse(nil, false)\n+\thttp.NewChunkedReader(nil)\n+\thttp.NewChunkedWriter(nil)\n+\thttp.NewClientConn(nil, nil)\n+\thttp.NewProxyClientConn(nil, nil)\n+\thttp.NewServerConn(nil, nil)\n+\thttp.NewSingleHostReverseProxy(nil)\n+}\n+`,\n+\t\tOut: `package main\n+\n+import \"net/http/httputil\"\n+\n+func f() {\n+\thttputil.DumpRequest(nil, false)\n+\thttputil.DumpRequestOut(nil, false)\n+\thttputil.DumpResponse(nil, false)\n+\thttputil.NewChunkedReader(nil)\n+\thttputil.NewChunkedWriter(nil)\n+\thttputil.NewClientConn(nil, nil)\n+\thttputil.NewProxyClientConn(nil, nil)\n+\thttputil.NewServerConn(nil, nil)\n+\thttputil.NewSingleHostReverseProxy(nil)\n+}\n+`,\n+\t},\n+\t{\n+\t\tName: \"httputil.1\",\n+\t\tIn: `package main\n+\n+import \"net/http\"\n+\n+func f() {\n+\thttp.DumpRequest(nil, false)\n+\thttp.DumpRequestOut(nil, false)\n+\thttp.DumpResponse(nil, false)\n+\thttp.NewChunkedReader(nil)\n+\thttp.NewChunkedWriter(nil)\n+\thttp.NewClientConn(nil, nil)\n+\thttp.NewProxyClientConn(nil, nil)\n+\thttp.NewServerConn(nil, nil)\n+\thttp.NewSingleHostReverseProxy(nil)\n+}\n+`,\n+\t\tOut: `package main\n+\n+import \"net/http/httputil\"\n+\n+func f() {\n+\thttputil.DumpRequest(nil, false)\n+\thttputil.DumpRequestOut(nil, false)\n+\thttputil.DumpResponse(nil, false)\n+\thttputil.NewChunkedReader(nil)\n+\thttputil.NewChunkedWriter(nil)\n+\thttputil.NewClientConn(nil, nil)\n+\thttputil.NewProxyClientConn(nil, nil)\n+\thttputil.NewServerConn(nil, nil)\n+\thttputil.NewSingleHostReverseProxy(nil)\n+}\n+`,\n+\t},\n+\t{\n+\t\tName: \"httputil.2\",\n+\t\tIn: `package main\n+\n+import \"net/http\"\n+\n+func f() {\n+\thttp.DumpRequest(nil, false)\n+\thttp.DumpRequestOut(nil, false)\n+\thttp.DumpResponse(nil, false)\n+\thttp.NewChunkedReader(nil)\n+\thttp.NewChunkedWriter(nil)\n+\thttp.NewClientConn(nil, nil)\n+\thttp.NewProxyClientConn(nil, nil)\n+\thttp.NewServerConn(nil, nil)\n+\thttp.NewSingleHostReverseProxy(nil)\n+\thttp.Get(\"\")\n+}\n+`,\n+\t\tOut: `package main\n+\n+import (\n+\t\"net/http\"\n+\t\"net/http/httputil\"\n+)\n+\n+func f() {\n+\thttputil.DumpRequest(nil, false)\n+\thttputil.DumpRequestOut(nil, false)\n+\thttputil.DumpResponse(nil, false)\n+\thttputil.NewChunkedReader(nil)\n+\thttputil.NewChunkedWriter(nil)\n+\thttputil.NewClientConn(nil, nil)\n+\thttputil.NewProxyClientConn(nil, nil)\n+\thttputil.NewServerConn(nil, nil)\n+\thttputil.NewSingleHostReverseProxy(nil)\n+\thttp.Get(\"\")\n+}\n+`,\n+\t},\n+}\n```

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

[https://github.com/golang/go/commit/19d064f68a275064c4a288f0c89885524b87fe9e](https://github.com/golang/go/commit/19d064f68a275064c4a288f0c89885524b87fe9e)

## 元コミット内容

上記の「コミット」セクションに記載されている内容が、このコミットの元々の内容です。`src/cmd/gofix/Makefile`に`httputil.go`が追加され、`src/cmd/gofix/httputil.go`と`src/cmd/gofix/httputil_test.go`が新規作成されています。

## 変更の背景

この変更の背景には、Go言語の標準ライブラリ、特に`net/http`パッケージの進化とリファクタリングがあります。Go言語は初期の段階から活発に開発が進められており、APIの改善や整理が頻繁に行われていました。2011年頃、`net/http`パッケージの一部の機能が、より特化したユーティリティ機能として`net/http/httputil`パッケージに分離されることになりました。

このようなAPIの変更は、既存のGoプログラムに影響を与えます。以前のバージョンで`net/http`パッケージの関数を使用していたコードは、新しいパッケージ構造に合わせて修正する必要がありました。手動での修正は、特に大規模なコードベースでは非常に手間がかかり、エラーの温床となる可能性がありました。

そこで、Goチームは`gofix`というツールを提供し、このようなAPIの変更に自動的に対応できるようにしました。このコミットは、まさにその`gofix`ツールに、`net/http`から`net/http/httputil`への関数移動を自動的に修正する機能を追加するものです。これにより、開発者はGoの新しいバージョンにスムーズに移行できるようになります。

## 前提知識の解説

### Go言語のパッケージ管理と標準ライブラリ

Go言語は、モジュールとパッケージという概念を用いてコードを整理します。パッケージは関連する機能の集合であり、他のパッケージからインポートして利用できます。Goの標準ライブラリは非常に豊富で、ネットワーキング、ファイルI/O、暗号化など、多岐にわたる機能を提供します。

*   **`net/http`パッケージ**: Go言語でHTTPクライアントおよびサーバーを構築するための主要なパッケージです。HTTPリクエストの送信、レスポンスの処理、Webサーバーの構築など、HTTP通信に関する基本的な機能を提供します。
*   **`net/http/httputil`パッケージ**: `net/http`パッケージのユーティリティ機能や、より高度なHTTP処理(リバースプロキシ、リクエスト/レスポンスのダンプなど)を提供するパッケージです。初期のGoでは`net/http`に含まれていた一部の機能が、このパッケージに分離されました。

### `gofix`ツール

`gofix`は、Go言語の公式ツールの一つで、Goプログラムを新しいAPIや言語機能に自動的に更新するために設計されています。Go言語は初期の段階で急速に進化し、APIの変更や非互換な変更が導入されることがありました。`gofix`は、このような変更に対して開発者が手動でコードを修正する負担を軽減するために開発されました。

`gofix`の主な機能は以下の通りです。

1.  **ソースコードの解析**: Goのソースファイルを抽象構文木(AST: Abstract Syntax Tree)に解析します。
2.  **変更の検出**: 定義された「修正(fix)」ルールに基づいて、古いAPIの使用箇所を特定します。
3.  **コードの書き換え**: 特定された古いAPIを新しいAPIに自動的に書き換えます。これには、関数名の変更、パッケージのインポートパスの変更などが含まれます。
4.  **ASTの再構築と出力**: 修正されたASTをGoのソースコードとして再構築し、ファイルに書き戻します。

`gofix`は、Go言語の進化を支える重要なツールであり、開発者が常に最新のGoバージョンとAPIを利用しやすくする役割を担っています。

### 抽象構文木 (AST)

`gofix`のようなコード変換ツールは、プログラムのソースコードを直接文字列として操作するのではなく、抽象構文木(AST)という中間表現を利用します。ASTは、ソースコードの構造を木構造で表現したもので、プログラムの各要素(変数、関数、式、文など)がノードとして表現されます。

ASTを操作することで、コードの意味的な構造を保ちながら、安全かつ正確にコードを変換することができます。例えば、`gofix`はASTを走査(walk)して特定の関数呼び出しパターンを見つけ、そのノードを新しい関数呼び出しのノードに置き換えるといった処理を行います。

## 技術的詳細

このコミットで追加された`gofix`の`httputil`修正は、`net/http`パッケージから`net/http/httputil`パッケージへ移動した特定の関数呼び出しを自動的に修正することを目的としています。

具体的に移動対象となった関数は、`httputil.go`内の`httputilFuncs`変数にリストアップされています。これには以下の関数が含まれます。

*   `DumpRequest`
*   `DumpRequestOut`
*   `DumpResponse`
*   `NewChunkedReader`
*   `NewChunkedWriter`
*   `NewClientConn`
*   `NewProxyClientConn`
*   `NewServerConn`
*   `NewSingleHostReverseProxy`

これらの関数は元々`http.DumpRequest`のように`http`パッケージの直下に存在していましたが、APIのリファクタリングにより`httputil.DumpRequest`のように`httputil`パッケージの配下に移動されました。

`gofix`の`httputil`修正のロジックは、以下のステップで動作します。

1.  **既存の`net/http/httputil`インポートの確認**: 修正対象のファイルが既に`net/http/httputil`をインポートしている場合、この修正は不要であるため、処理をスキップします。
2.  **ASTの走査と関数呼び出しの特定**: ファイルの抽象構文木(AST)を走査し、`http.FunctionName`の形式で呼び出されている関数を探します。ここで`FunctionName`は`httputilFuncs`リストに含まれるいずれかの関数名です。
3.  **パッケージ名の変更とインポートの追加**:
    *   もし`http.FunctionName`の形式の呼び出しが見つかった場合、その呼び出しのパッケージ名を`http`から`httputil`に変更します。
    *   同時に、まだ`net/http/httputil`がインポートされていない場合は、このパッケージをインポートリストに追加します。これは`addImport(f, "net/http/httputil")`によって行われます。
4.  **不要な`net/http`インポートの削除**: 全ての対象関数が`httputil`パッケージに移動され、かつファイル内で`net/http`パッケージの他の関数が一切使用されなくなった場合、`net/http`のインポート文を削除します。これは`deleteImport(f, "net/http")`によって行われます。

この修正は、GoのAST操作ライブラリ(`go/ast`)を駆使して実装されており、コードの構文的な正確性を保ちながら、必要な変更を自動的に適用します。`httputil_test.go`には、この修正が正しく機能するかを確認するためのテストケースが含まれており、様々なシナリオ(`net/http`のみをインポートしている場合、`net/http`と他のパッケージをインポートしている場合など)での挙動が検証されています。

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

このコミットのコアとなるコードは、新しく追加された`src/cmd/gofix/httputil.go`ファイルです。

### `src/cmd/gofix/httputil.go`

```go
// Copyright 2011 The Go Authors.  All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.

package main

import "go/ast"

func init() {
	register(httputilFix)
}

var httputilFix = fix{
	"httputil",
	"2011-11-18",
	httputil,
	`Move some functions in http package into httputil package.

http://codereview.appspot.com/5336049
`,
}

var httputilFuncs = []string{
	"DumpRequest",
	"DumpRequestOut",
	"DumpResponse",
	"NewChunkedReader",
	"NewChunkedWriter",
	"NewClientConn",
	"NewProxyClientConn",
	"NewServerConn",
	"NewSingleHostReverseProxy",
}

func httputil(f *ast.File) bool {
	if imports(f, "net/http/httputil") {
		return false
	}

	fixed := false

	walk(f, func(n interface{}) {
		// Rename package name.
		if expr, ok := n.(ast.Expr); ok {
			for _, s := range httputilFuncs {
				if isPkgDot(expr, "http", s) {
					if !fixed {
						addImport(f, "net/http/httputil")
						fixed = true
					}
					expr.(*ast.SelectorExpr).X.(*ast.Ident).Name = "httputil"
				}
			}
		}
	})

	// Remove the net/http import if no longer needed.
	if fixed && !usesImport(f, "net/http") {
		deleteImport(f, "net/http")
	}

	return fixed
}

src/cmd/gofix/httputil_test.go

このファイルには、httputil修正の動作を検証するためのテストケースが含まれています。様々な入力コードに対して、期待される出力コードが定義されており、修正が正しく適用されることを確認します。

コアとなるコードの解説

src/cmd/gofix/httputil.goの主要な部分を解説します。

  1. init()関数とregister():

    func init() {
    	register(httputilFix)
    }
    

    init()関数はGoプログラムの起動時に自動的に実行されます。ここでregister(httputilFix)を呼び出すことで、新しく定義されたhttputilFixという修正がgofixツールに登録されます。これにより、gofixが実行された際にこの修正が適用されるようになります。

  2. httputilFix変数:

    var httputilFix = fix{
    	"httputil",
    	"2011-11-18",
    	httputil,
    	`Move some functions in http package into httputil package.
    
    

http://codereview.appspot.com/5336049 , } ``` これはfix構造体のインスタンスで、この修正に関するメタデータを含んでいます。 * "httputil": 修正の名前。 * "2011-11-18": この修正が適用されるべき日付(この日付以前のコードに適用される)。 * httputil`: 実際に修正ロジックを実装した関数への参照。 * バッククォートで囲まれた文字列: この修正の目的と、関連するコードレビューへのリンク。

  1. httputilFuncs変数:

    var httputilFuncs = []string{
    	"DumpRequest",
    	"DumpRequestOut",
    	"DumpResponse",
    	"NewChunkedReader",
    	"NewChunkedWriter",
    	"NewClientConn",
    	"NewProxyClientConn",
    	"NewServerConn",
    	"NewSingleHostReverseProxy",
    }
    

    net/httpパッケージからnet/http/httputilパッケージへ移動した関数名のリストです。gofixはこのリストを使って、どの関数呼び出しを修正すべきかを判断します。

  2. httputil(f *ast.File) bool関数:

    func httputil(f *ast.File) bool {
    	if imports(f, "net/http/httputil") {
    		return false
    	}
    
    	fixed := false
    
    	walk(f, func(n interface{}) {
    		// Rename package name.
    		if expr, ok := n.(ast.Expr); ok {
    			for _, s := range httputilFuncs {
    				if isPkgDot(expr, "http", s) {
    					if !fixed {
    						addImport(f, "net/http/httputil")
    						fixed = true
    					}
    					expr.(*ast.SelectorExpr).X.(*ast.Ident).Name = "httputil"
    				}
    			}
    		}
    	})
    
    	// Remove the net/http import if no longer needed.
    	if fixed && !usesImport(f, "net/http") {
    		deleteImport(f, "net/http")
    	}
    
    	return fixed
    }
    

    これが実際の修正ロジックを実装している関数です。引数fは、解析対象のGoソースファイルの抽象構文木(*ast.File)です。

    • if imports(f, "net/http/httputil") { return false }: まず、対象のファイルが既にnet/http/httputilパッケージをインポートしているかどうかを確認します。もしインポート済みであれば、この修正は不要なのでfalseを返して処理を終了します。

    • fixed := false: 修正が適用されたかどうかを示すフラグです。一度でも修正が行われたらtrueに設定されます。

    • walk(f, func(n interface{}) { ... }): walk関数は、GoのASTを深さ優先で走査するためのユーティリティ関数です。ASTの各ノード(n)に対して、無名関数が実行されます。

    • if expr, ok := n.(ast.Expr); ok { ... }: 現在のノードnが式(ast.Expr)であるかどうかをチェックします。関数呼び出しは式の一部であるため、このチェックが必要です。

    • for _, s := range httputilFuncs { ... }: httputilFuncsリスト内の各関数名sについてループします。

    • if isPkgDot(expr, "http", s) { ... }: isPkgDotは、式exprhttp.s(例: http.DumpRequest)の形式であるかどうかをチェックするユーティリティ関数です。つまり、httpパッケージのsという関数が呼び出されているかを確認します。

    • if !fixed { addImport(f, "net/http/httputil"); fixed = true }: もしhttp.s形式の呼び出しが見つかり、かつまだnet/http/httputilパッケージがインポートされていない場合、addImport関数を使ってnet/http/httputilをインポートリストに追加し、fixedフラグをtrueに設定します。これにより、一度だけインポートが追加されることが保証されます。

    • expr.(*ast.SelectorExpr).X.(*ast.Ident).Name = "httputil": これが実際のパッケージ名変更の核心部分です。

      • expr.(*ast.SelectorExpr): http.DumpRequestのようなパッケージ名.関数名の形式は、ASTではSelectorExprとして表現されます。これはセレクタ(.)の左側(X)がパッケージ名、右側(Sel)が関数名となります。
      • .X.(*ast.Ident): SelectorExprXフィールドは、パッケージ名を表す識別子(ast.Ident)です。
      • .Name = "httputil": その識別子の名前を"httputil"に変更します。これにより、http.DumpRequesthttputil.DumpRequestに書き換えられます。
    • if fixed && !usesImport(f, "net/http") { deleteImport(f, "net/http") }: ASTの走査が完了した後、もし何らかの修正が行われており(fixedtrue)、かつファイル内でnet/httpパッケージの他の関数が一切使用されていない場合(!usesImport(f, "net/http"))、deleteImport関数を使ってnet/httpのインポート文を削除します。これにより、不要なインポートが残らないようにします。

    • return fixed: 最終的に、何らかの修正が行われた場合はtrueを、そうでなければfalseを返します。

このコードは、GoのAST操作の典型的なパターンを示しており、コンパイラやコード分析ツール、自動リファクタリングツールなどの開発において非常に重要な技術です。

関連リンク

参考にした情報源リンク