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

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

このコミットは、Go言語の標準ライブラリである mime/multipart パッケージに NewReader 関数の使用例を追加するものです。これにより、multipart 形式のデータを解析する際の開発者の理解と利用が促進されます。

コミット

このコミットは、mime/multipart パッケージに NewReader 関数の使用例を追加し、関連するドキュメンテーションのコメントを更新しました。これにより、multipart データの読み込みと処理方法が明確に示され、開発者がこの機能を利用しやすくなりました。

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

https://github.com/golang/go/commit/270848509bcfa0ecc72006d6325011bcb3096026

元コミット内容

commit 270848509bcfa0ecc72006d6325011bcb3096026
Author: Brad Fitzpatrick <bradfitz@golang.org>
Date:   Mon May 12 20:26:27 2014 -0700

    mime/multipart: add NewReader example
    
    Fixes #7888
    
    LGTM=adg
    R=adg
    CC=golang-codereviews
    https://golang.org/cl/100420043

変更の背景

この変更の背景には、mime/multipart パッケージの NewReader 関数の利用方法が不明瞭であったという問題がありました。コミットメッセージにある Fixes #7888 は、このコミットが特定の課題(Issue 7888)を解決するために行われたことを示しています。通常、このような課題は、ユーザーからのフィードバックや内部でのコードレビューを通じて、既存のAPIの使いにくさやドキュメンテーションの不足が指摘された場合に作成されます。

NewReadermultipart 形式のデータを解析するための重要な関数ですが、その具体的な使用例が提供されていなかったため、開発者がどのようにこの関数を呼び出し、返される Reader オブジェクトを操作すればよいのかが分かりにくい状況でした。このコミットは、具体的なコード例を提供することで、このギャップを埋め、開発者が mime/multipart パッケージをより効果的に利用できるようにすることを目的としています。

前提知識の解説

このコミットの変更内容を理解するためには、以下のGo言語の標準ライブラリと概念に関する知識が必要です。

  • mime/multipart パッケージ:

    • MIME (Multipurpose Internet Mail Extensions) の multipart 形式のメッセージを解析するためのパッケージです。HTTPリクエストでファイルをアップロードする際や、メールの添付ファイルを扱う際によく使用されます。
    • multipart メッセージは、複数の「パート(Part)」に分割され、それぞれが独自のヘッダーとボディを持ちます。各パートは、特定の「境界文字列(boundary string)」によって区切られます。
    • NewReader(r io.Reader, boundary string) *Reader: io.Reader から multipart データを読み込み、指定された境界文字列を使用して解析するための Reader オブジェクトを返します。
    • Reader.NextPart() (*Part, error): 次の multipart パートを読み込み、そのパートを表す *Part オブジェクトを返します。すべてのパートを読み終えると io.EOF エラーを返します。
    • Part: 個々の multipart パートを表す構造体です。Partio.Reader インターフェースを実装しており、そのボディを読み込むことができます。また、Part.Header を通じてパートのヘッダー情報にアクセスできます。
  • io.Reader インターフェース:

    • Go言語における基本的なインターフェースの一つで、データを読み込むことができる任意の型が実装します。Read(p []byte) (n int, err error) メソッドを持ちます。
    • strings.NewReader(): 文字列を io.Reader として扱うための関数です。
    • io.EOF: io.Reader から読み込むデータがなくなったときに返されるエラーです。
  • bufio.Reader:

    • バッファリングされたI/Oを提供する構造体です。基になる io.Reader から効率的にデータを読み込むために使用されます。NewReader 関数は、内部で bufio.NewReader を使用して、入力 io.Reader をバッファリングしています。
  • net/mail パッケージ:

    • メールメッセージの解析をサポートするパッケージです。この例では、mail.Message 構造体を使用して、multipart メッセージのヘッダーとボディをシミュレートしています。
  • mime パッケージ:

    • MIMEヘッダーの解析をサポートするパッケージです。
    • mime.ParseMediaType(v string) (mediatype string, params map[string]string, err error): Content-Type ヘッダーのようなメディアタイプ文字列を解析し、メディアタイプ(例: multipart/mixed)とパラメータ(例: boundary=foo)を返します。
  • io/ioutil パッケージ (Go 1.16以降は io および os パッケージに統合):

    • I/Oユーティリティ関数を提供します。
    • ioutil.ReadAll(r io.Reader) ([]byte, error): io.Reader からすべてのデータを読み込み、バイトスライスとして返します。

技術的詳細

このコミットの技術的詳細は、主に mime/multipart パッケージの NewReader 関数の使用方法を具体的に示す ExampleNewReader 関数の追加にあります。

ExampleNewReader は、以下のようなステップで multipart データを解析する典型的なワークフローを示しています。

  1. mail.Message の構築: multipart/mixed タイプの Content-Type ヘッダーと、複数のパートを含むボディを持つ mail.Message を手動で構築します。ボディは strings.NewReader を使用して文字列から作成されます。これは、実際のHTTPリクエストやメールの受信をシミュレートするためのものです。
  2. Content-Type ヘッダーの解析: mail.MessageContent-Type ヘッダーから mime.ParseMediaType を使用してメディアタイプとパラメータ(特に boundary)を抽出します。multipart データを正しく解析するためには、この境界文字列が不可欠です。
  3. multipart.NewReader の初期化: 抽出した境界文字列と mail.Message のボディ(io.Reader)を引数として multipart.NewReader を呼び出し、multipart.Reader オブジェクトを作成します。
  4. パートの反復処理: for ループ内で mr.NextPart() を繰り返し呼び出し、各 multipart パートを順次取得します。
    • NextPart()io.EOF を返した場合、すべてのパートの読み込みが完了したことを意味するため、ループを終了します。
    • その他のエラーが発生した場合は、ログに出力して処理を終了します。
  5. パートのボディの読み込みと処理: 各パートから ioutil.ReadAll(p) を使用してそのボディ全体を読み込みます。この例では、読み込んだボディの内容と、パートのヘッダーから取得した Foo ヘッダーの値を標準出力に表示しています。

また、src/pkg/mime/multipart/multipart.goNewReader 関数のコメントが更新され、mime.ParseMediaType を使用して Content-Type ヘッダーから境界文字列を取得する方法が明示的に示されました。これは、NewReader を正しく利用するための重要なヒントであり、ドキュメンテーションの改善に貢献しています。

このコミットは、Go言語のテストフレームワークにおける Example 関数の利用方法の良い例でもあります。Example 関数は、コードの動作を説明するだけでなく、go test コマンドで実行可能であり、出力が期待される出力と一致するかどうかを検証できます。これにより、ドキュメンテーションが常に最新かつ正確であることが保証されます。

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

このコミットによるコアとなるコードの変更箇所は以下の2つのファイルです。

  1. src/pkg/mime/multipart/example_test.go (新規追加):

    • このファイルは、mime/multipart パッケージの NewReader 関数の使用例を含む新しいテストファイルとして追加されました。
    • ExampleNewReader() という関数が定義されており、multipart 形式のメッセージを構築し、NewReader を使ってその内容を解析する一連の処理が記述されています。
    • 関数の最後には、// Output: コメントブロックがあり、この例を実行した際の期待される出力が記述されています。これはGoのテストフレームワークがExample関数を検証する際に使用されます。
  2. src/pkg/mime/multipart/multipart.go (既存ファイルの変更):

    • NewReader 関数のシグネチャは変更されていませんが、その関数のドキュメンテーションコメントが更新されました。
    • 変更前: // NewReader creates a new multipart Reader reading from reader using the
    • 変更後: // NewReader creates a new multipart Reader reading from r using the
    • さらに、NewReader のコメントに以下の説明が追加されました。
      // The boundary is usually obtained from the "boundary" parameter of
      // the message's "Content-Type" header. Use mime.ParseMediaType to
      // parse such headers.
      
      これは、NewReader に渡す boundary 引数をどのように取得すべきかについて、具体的なガイダンス(mime.ParseMediaType の使用)を提供するものです。

コアとなるコードの解説

src/pkg/mime/multipart/example_test.go に追加された ExampleNewReader 関数がこのコミットの核心です。

func ExampleNewReader() {
	msg := &mail.Message{
		Header: map[string][]string{
			"Content-Type": []string{"multipart/mixed; boundary=foo"},
		},
		Body: strings.NewReader(
			"--foo\r\nFoo: one\r\n\r\nA section\r\n" +
				"--foo\r\nFoo: two\r\n\r\nAnd another\r\n" +
				"--foo--\r\n"),
	}
	mediaType, params, err := mime.ParseMediaType(msg.Header.Get("Content-Type"))
	if err != nil {
		log.Fatal(err)
	}
	if strings.HasPrefix(mediaType, "multipart/") {
		mr := multipart.NewReader(msg.Body, params["boundary"])
		for {
			p, err := mr.NextPart()
			if err == io.EOF {
				return
			}
			if err != nil {
				log.Fatal(err)
			}
			slurp, err := ioutil.ReadAll(p)
			if err != nil {
				log.Fatal(err)
			}
			fmt.Printf("Part %q: %q\n", p.Header.Get("Foo"), slurp)
		}
	}

	// Output:
	// Part "one": "A section"
	// Part "two": "And another"
}

このコードは、以下の手順で multipart データを解析します。

  1. msg の定義: mail.Message 型の msg 変数を初期化します。

    • Header フィールドには、Content-Type ヘッダーとして "multipart/mixed; boundary=foo" を設定しています。これは、メッセージが multipart 形式であり、その境界文字列が "foo" であることを示します。
    • Body フィールドには、strings.NewReader を使って multipart 形式の文字列データを設定しています。この文字列は、--foo で区切られた2つのパートを含んでいます。各パートには Foo というカスタムヘッダーと、短いテキストボディが含まれています。最後の --foo--multipart メッセージの終端を示します。
  2. Content-Type の解析:

    • msg.Header.Get("Content-Type")Content-Type ヘッダーの値を取得します。
    • mime.ParseMediaType を呼び出し、このヘッダーを解析して mediaType ("multipart/mixed") と params (map[string]string{"boundary": "foo"}) を取得します。
    • エラーハンドリングも行われています。
  3. multipart.NewReader の作成:

    • mediaType"multipart/" で始まることを確認した後、multipart.NewReader を呼び出します。
    • 第一引数には msg.Body (これは io.Reader です) を渡し、第二引数には params["boundary"] から取得した境界文字列 "foo" を渡します。これにより、multipart データを読み込むための mr (multipart Reader) が作成されます。
  4. パートの反復処理:

    • for {} ループを使って、すべてのパートを順次処理します。
    • mr.NextPart() を呼び出して次のパート p を取得します。
    • io.EOF エラーが返された場合、すべてのパートを読み終えたため、return で関数を終了します。
    • その他のエラーが発生した場合は log.Fatal でプログラムを終了します。
  5. パートの内容の読み込みと出力:

    • ioutil.ReadAll(p) を呼び出して、現在のパート p のボディ全体を slurp というバイトスライスとして読み込みます。
    • fmt.Printf を使って、パートの Foo ヘッダーの値 (p.Header.Get("Foo")) と、読み込んだボディの内容 (slurp) を整形して出力します。

この例は、multipart データを扱う際の基本的なパターンを網羅しており、NewReader の使い方、各パートへのアクセス方法、そしてパートのヘッダーとボディの読み込み方法を明確に示しています。

関連リンク

  • Go CL 100420043: https://golang.org/cl/100420043
  • Go Issue 7888 (このコミットが解決した課題): 詳細は直接参照できませんでしたが、コミットメッセージに記載されています。

参考にした情報源リンク

  • Go言語の公式ドキュメンテーション (mime/multipart, io, bufio, net/mail, mime, io/ioutil パッケージ): https://pkg.go.dev/
  • Go言語のExampleテストに関するドキュメンテーション: https://go.dev/blog/examples
  • Go言語のIssueトラッカー (一般的な情報): https://github.com/golang/go/issues
  • Go言語のコードレビューシステム (Gerrit): https://go.dev/doc/contribute#gerrit
  • MIME (Multipurpose Internet Mail Extensions) の概念に関する一般的な情報 (RFC 2045, RFC 2046など)
  • HTTP multipart/form-data の概念に関する一般的な情報 (RFC 7578など)