[インデックス 12642] ファイルの概要
このコミットは、Go言語の標準ライブラリ net/http
パッケージ内の triv.go
ファイルに対する変更です。triv.go
は、net/http
パッケージの基本的な機能を示すためのトリビアルな(単純な)サンプルコードやテストコードとして機能していると考えられます。具体的には、HTTPリクエストを処理し、外部コマンド(/bin/date
)を実行してその出力をHTTPレスポンスとして返す機能、およびログ出力を行うHTTPハンドラが含まれています。
コミット
commit 1c224ab9dd1833f4548a49d40d2bb0a264a74767
Author: Robert Hencke <robert.hencke@gmail.com>
Date: Wed Mar 14 20:25:57 2012 -0700
net/http: ensure triv.go compiles and runs
R=golang-dev, bradfitz, dsymonds, dave, r
CC=golang-dev
https://golang.org/cl/5795069
GitHub上でのコミットページへのリンク
https://github.com/golang/go/commit/1c224ab9dd1833f4548a49d40d2bb0a264a74767
元コミット内容
net/http: ensure triv.go compiles and runs
R=golang-dev, bradfitz, dsymonds, dave, r
CC=golang-dev
https://golang.org/cl/5795069
変更の背景
このコミットの背景は、コミットメッセージにある通り「triv.go
がコンパイルされ、実行されることを保証する」ことです。これは、既存のコードが何らかの理由で正しく動作しなくなっていたか、またはより堅牢でGoのイディオムに沿った方法で外部コマンドを実行する必要があったことを示唆しています。
元のコードでは、os.Pipe
と os.StartProcess
を組み合わせて外部コマンドを実行し、その出力をHTTPレスポンスとして返していました。しかし、この方法はパイプの管理、プロセスの開始、エラーハンドリングが複雑になりがちです。特に、パイプのクローズ順序やプロセスの待機処理に不備があると、リソースリークやデッドロック、予期せぬエラーが発生する可能性があります。
この変更は、よりシンプルで安全な os/exec
パッケージの exec.Command
関数を使用することで、これらの問題を解決し、コードの可読性と堅牢性を向上させることを目的としています。また、エラーハンドリングも http.Error
を使用するように変更されており、HTTPアプリケーションにおける標準的なエラー応答の仕方に合わせています。
前提知識の解説
Go言語の標準ライブラリ
-
net/http
パッケージ: Go言語でHTTPクライアントおよびサーバーを実装するための主要なパッケージです。HTTPリクエストのルーティング、ハンドラの登録、レスポンスの書き込みなど、Webアプリケーション開発の基盤を提供します。http.ResponseWriter
: HTTPレスポンスを書き込むためのインターフェースです。ヘッダーの設定やボディの書き込みに使用されます。http.Request
: 受信したHTTPリクエストの情報を保持する構造体です。URL、ヘッダー、ボディなどの情報にアクセスできます。http.Error(w http.ResponseWriter, error string, code int)
: HTTPエラーレスポンスを生成し、指定されたステータスコードとエラーメッセージをクライアントに送信するためのヘルパー関数です。
-
os
パッケージ: オペレーティングシステムと対話するための機能を提供します。ファイル操作、プロセス管理、環境変数へのアクセスなどが含まれます。os.Pipe()
: パイプを作成します。パイプは、プロセス間でデータをやり取りするための単方向の通信チャネルです。読み取り側と書き込み側の2つの*os.File
を返します。os.StartProcess(name string, argv []string, attr *ProcAttr)
: 新しいプロセスを開始します。name
は実行するプログラムのパス、argv
は引数、attr
はプロセスの属性(標準入出力のリダイレクトなど)を指定します。この関数は低レベルであり、通常はos/exec
パッケージが推奨されます。os.ProcAttr
:os.StartProcess
に渡される構造体で、新しいプロセスの属性(環境変数、作業ディレクトリ、ファイルディスクリプタなど)を定義します。Files
フィールドは、新しいプロセスの標準入出力(stdin, stdout, stderr)をリダイレクトするために使用されます。
-
os/exec
パッケージ: 外部コマンドを実行するためのより高レベルなインターフェースを提供します。os.StartProcess
よりも簡単に外部コマンドを実行し、その入出力を制御できます。exec.Command(name string, arg ...string)
: 実行するコマンドと引数を表す*exec.Cmd
構造体を作成します。(*exec.Cmd).Output()
: コマンドを実行し、その標準出力(stdout)をバイトスライスとして返します。コマンドがエラーを返した場合、または標準エラー出力(stderr)がある場合は、エラーも返します。このメソッドは、コマンドの実行が完了するまでブロックします。
パイプとプロセス間通信
パイプは、Unix系システムにおけるプロセス間通信(IPC)の基本的なメカニズムの一つです。あるプロセスの標準出力が別のプロセスの標準入力に接続されることで、データがストリームとして流れます。os.Pipe()
はGoプログラム内でこのパイプを作成し、プロセス間でデータをやり取りするために使用できます。
エラーハンドリング
Go言語では、エラーは戻り値として明示的に扱われます。関数がエラーを返す可能性がある場合、通常は最後の戻り値として error
型の値を返します。呼び出し元は、この error
値が nil
でないかどうかをチェックすることで、エラーが発生したかどうかを判断します。
技術的詳細
このコミットの主要な変更点は、DateServer
関数における外部コマンドの実行方法です。
変更前:
func DateServer(rw http.ResponseWriter, req *http.Request) {
rw.Header().Set("Content-Type", "text/plain; charset=utf-8")
r, w, err := os.Pipe()
if err != nil {
fmt.Fprintf(rw, "pipe: %s\n", err)
return
}
p, err := os.StartProcess("/bin/date", []string{"date"}, &os.ProcAttr{Files: []*os.File{nil, w, w}})
defer r.Close()
w.Close()
if err != nil {
fmt.Fprintf(rw, "fork/exec: %s\n", err)
return
}
io.Copy(rw, r)
wait, err := p.Wait(0)
if err != nil {
fmt.Fprintf(rw, "wait: %s\n", err)
return
}
if !wait.Exited() || wait.ExitStatus() != 0 {
fmt.Fprintf(rw, "date: %v\n", wait)
return
}
}
変更前のコードでは、以下の手順で外部コマンドを実行していました。
os.Pipe()
を呼び出してパイプを作成します。r
は読み取り側、w
は書き込み側です。os.StartProcess()
を使用して/bin/date
コマンドを新しいプロセスとして開始します。os.ProcAttr{Files: []*os.File{nil, w, w}}
は、新しいプロセスの標準入力(nil
)、標準出力(w
)、標準エラー出力(w
)を、作成したパイプの書き込み側にリダイレクトしています。これにより、/bin/date
の出力がパイプに書き込まれます。
defer r.Close()
で読み取り側パイプを遅延クローズし、w.Close()
で書き込み側パイプを即座にクローズします。w.Close()
はos.StartProcess
の直後に行うことで、date
コマンドが終了した際にパイプの書き込み側が閉じられ、io.Copy
がEOF(End Of File)を受け取って終了できるようにします。io.Copy(rw, r)
を使用して、パイプの読み取り側r
からHTTPレスポンスライターrw
へデータをコピーします。これにより、/bin/date
の出力がクライアントに送信されます。p.Wait(0)
で子プロセスの終了を待ち、その終了ステータスを確認します。
このアプローチは低レベルであり、パイプの管理(特にクローズのタイミング)やエラーハンドリングが複雑になりがちです。例えば、w.Close()
のタイミングが不適切だと、io.Copy
がブロックし続ける可能性があります。また、エラーメッセージの出力も fmt.Fprintf
を使って直接レスポンスボディに書き込んでおり、HTTPエラーコードを適切に設定していません。
変更後:
func DateServer(rw http.ResponseWriter, req *http.Request) {
rw.Header().Set("Content-Type", "text/plain; charset=utf-8")
date, err := exec.Command("/bin/date").Output()
if err != nil {
http.Error(rw, err.Error(), 500)
return
}
rw.Write(date)
}
変更後のコードでは、以下の手順で外部コマンドを実行しています。
exec.Command("/bin/date").Output()
を呼び出します。exec.Command("/bin/date")
は/bin/date
コマンドを実行するための*exec.Cmd
オブジェクトを作成します。.Output()
メソッドは、コマンドを実行し、その標準出力(stdout)をバイトスライスとして返します。このメソッドは、コマンドが終了するまでブロックし、エラーが発生した場合はerror
を返します。
- エラーが発生した場合(
err != nil
)、http.Error(rw, err.Error(), 500)
を使用してHTTP 500 Internal Server Error をクライアントに返します。これにより、HTTPの標準的なエラー応答メカニズムが利用されます。 - エラーがなければ、
rw.Write(date)
を使用して、取得した/bin/date
の出力を直接HTTPレスポンスボディに書き込みます。
この変更により、コードは大幅に簡素化され、外部コマンドの実行とエラーハンドリングがよりGoのイディオムに沿った形になりました。os/exec
パッケージは、プロセスの開始、パイプの管理、出力の収集といった複雑なタスクを抽象化してくれるため、開発者はより高レベルなロジックに集中できます。
また、Logger
関数も変更されています。
変更前:
func Logger(w http.ResponseWriter, req *http.Request) {
log.Print(req.URL.Raw)
w.WriteHeader(404)
w.Write([]byte("oops"))
}
変更後:
func Logger(w http.ResponseWriter, req *http.Request) {
log.Print(req.URL)
http.Error(w, "oops", 404)
}
変更前は req.URL.Raw
をログに出力し、w.WriteHeader(404)
と w.Write([]byte("oops"))
を使って手動で404エラーを返していました。
変更後は req.URL
をログに出力し、http.Error(w, "oops", 404)
を使って標準的な方法で404エラーを返しています。req.URL.Raw
はURLの生の文字列表現ですが、req.URL
はパースされた *url.URL
構造体であり、より詳細な情報(Scheme, Host, Path, RawQueryなど)にアクセスできます。ログ出力の目的によっては req.URL
の方が有用な場合があります。http.Error
の使用は、DateServer
の変更と同様に、HTTPエラー応答の標準化と簡素化を目的としています。
コアとなるコードの変更箇所
diff --git a/src/pkg/net/http/triv.go b/src/pkg/net/http/triv.go
index 269af0ca3d..adf5a00be1 100644
--- a/src/pkg/net/http/triv.go
+++ b/src/pkg/net/http/triv.go
@@ -15,6 +15,7 @@ import (
"log"
"net/http"
"os"
+ "os/exec"
"strconv"
)
@@ -95,35 +96,18 @@ func (ch Chan) ServeHTTP(w http.ResponseWriter, req *http.Request) {
// exec a program, redirecting output
func DateServer(rw http.ResponseWriter, req *http.Request) {
rw.Header().Set("Content-Type", "text/plain; charset=utf-8")
- r, w, err := os.Pipe()
- if err != nil {
- fmt.Fprintf(rw, "pipe: %s\n", err)
- return
- }
-
- p, err := os.StartProcess("/bin/date", []string{"date"}, &os.ProcAttr{Files: []*os.File{nil, w, w}})
- defer r.Close()
- w.Close()
- if err != nil {
- fmt.Fprintf(rw, "fork/exec: %s\n", err)
- return
- }
- io.Copy(rw, r)
- wait, err := p.Wait(0)
+ date, err := exec.Command("/bin/date").Output()
if err != nil {
- fmt.Fprintf(rw, "wait: %s\n", err)
+ http.Error(rw, err.Error(), 500)
return
}
- if !wait.Exited() || wait.ExitStatus() != 0 {
- fmt.Fprintf(rw, "date: %v\n", wait)
- return
- }
+ rw.Write(date)
}
func Logger(w http.ResponseWriter, req *http.Request) {
- log.Print(req.URL.Raw)
- w.WriteHeader(404)
- w.Write([]byte("oops"))
+ log.Print(req.URL)
+ http.Error(w, "oops", 404)
}
var webroot = flag.String("root", "/home/rsc", "web root directory")
コアとなるコードの解説
import
文の変更
- os
+ os/exec
os
パッケージは引き続き使用されていますが、os/exec
パッケージが新しくインポートされています。これは、外部コマンドの実行にos/exec
パッケージの機能を使用するためです。
DateServer
関数の変更
-
削除された行:
r, w, err := os.Pipe()
: パイプの作成が不要になりました。p, err := os.StartProcess(...)
: 低レベルなプロセス開始が不要になりました。defer r.Close()
とw.Close()
: パイプのクローズ処理が不要になりました。io.Copy(rw, r)
: パイプからのコピーが不要になりました。p.Wait(0)
とその後のエラーチェック: プロセスの終了待機とステータスチェックがexec.Command().Output()
に内包されました。fmt.Fprintf(rw, ...)
: エラーメッセージの直接書き込みがhttp.Error
に置き換えられました。
-
追加・変更された行:
date, err := exec.Command("/bin/date").Output()
:/bin/date
コマンドを実行し、その標準出力をdate
変数にバイトスライスとして格納します。エラーが発生した場合はerr
に格納されます。if err != nil { http.Error(rw, err.Error(), 500); return }
: コマンド実行中にエラーが発生した場合、http.Error
を使用してHTTP 500 Internal Server Error をクライアントに返します。rw.Write(date)
: コマンドの出力(date
バイトスライス)をHTTPレスポンスボディに書き込みます。
この変更により、外部コマンドの実行ロジックが大幅に簡素化され、より安全でGoのイディオムに沿ったものになりました。
Logger
関数の変更
-
削除された行:
log.Print(req.URL.Raw)
: ログ出力の対象がreq.URL.Raw
からreq.URL
に変更されました。w.WriteHeader(404)
とw.Write([]byte("oops"))
: 手動でのHTTP 404エラー応答がhttp.Error
に置き換えられました。
-
追加・変更された行:
log.Print(req.URL)
: リクエストのURLオブジェクト全体をログに出力します。http.Error(w, "oops", 404)
: HTTP 404 Not Found エラーをクライアントに返します。これにより、ヘッダーの設定とボディの書き込みが一度に行われます。
この変更も、HTTPエラー応答の標準化と簡素化を目的としています。
関連リンク
- Go CL 5795069: https://golang.org/cl/5795069
参考にした情報源リンク
- Go Documentation:
net/http
package: https://pkg.go.dev/net/http - Go Documentation:
os
package: https://pkg.go.dev/os - Go Documentation:
os/exec
package: https://pkg.go.dev/os/exec - Go Documentation:
io
package: https://pkg.go.dev/io - Go Documentation:
log
package: https://pkg.go.dev/log - Go Documentation:
fmt
package: https://pkg.go.dev/fmt - Go Documentation:
url
package: https://pkg.go.dev/net/url - Go by Example: Executing Processes: https://gobyexample.com/execing-processes
- Go by Example: Spawning Processes: https://gobyexample.com/spawning-processes
- Go: The Good Parts - Error Handling: https://go.dev/blog/error-handling-and-go
- Unix Pipes: https://en.wikipedia.org/wiki/Pipeline_(Unix)