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

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

このコミットは、Go言語の標準ライブラリであるnet/httpパッケージに、HTTPヘッダーの時刻値を解析するための新しい関数ParseTimeを追加するものです。これにより、HTTP/1.1で定義されている3つの標準的な時刻フォーマット(RFC1123、RFC850、ANSIC)のいずれかで記述された時刻文字列をtime.Time型に変換できるようになります。

コミット

commit d168442708071af9f9816f1f7869408364e58471
Author: Patrick Higgins <patrick.allen.higgins@gmail.com>
Date:   Fri Aug 31 18:10:16 2012 -0400

    net/http: added ParseTime function.
    
    Parses a time header value into a time.Time according to rfc2616 sec 3.3.
    
    R=golang-dev, dave, rsc, r
    CC=bradfitz, golang-dev
    https://golang.org/cl/6344046
---
 src/pkg/net/http/header.go      | 20 ++++++++++++++++++++
 src/pkg/net/http/header_test.go | 33 +++++++++++++++++++++++++++++++++
 2 files changed, 53 insertions(+)

diff --git a/src/pkg/net/http/header.go b/src/pkg/net/http/header.go
index 6858cb29d2..91417366ae 100644
--- a/src/pkg/net/http/header.go
+++ b/src/pkg/net/http/header.go
@@ -9,6 +9,7 @@ import (
 	"net/textproto"
 	"sort"
 	"strings"
+	"time"
 )
 
 // A Header represents the key-value pairs in an HTTP header.
@@ -53,6 +54,25 @@ func (h Header) Write(w io.Writer) error {
 	return h.WriteSubset(w, nil)
 }\n 
+var timeFormats = []string{
+\tTimeFormat,
+\ttime.RFC850,
+\ttime.ANSIC,
+}\n+\n+// ParseTime parses a time header (such as the Date: header),\n+// trying each of the three formats allowed by HTTP/1.1:\n+// TimeFormat, time.RFC850, and time.ANSIC.\n+func ParseTime(text string) (t time.Time, err error) {\n+\tfor _, layout := range timeFormats {\n+\t\tt, err = time.Parse(layout, text)\n+\t\tif err == nil {\n+\t\t\treturn\n+\t\t}\n+\t}\n+\treturn\n+}\n+\n var headerNewlineToSpace = strings.NewReplacer("\\n", " ", "\\r", " ")\n \n type writeStringer interface {\ndiff --git a/src/pkg/net/http/header_test.go b/src/pkg/net/http/header_test.go
index eb2ac0d91c..fd971a61d0 100644
--- a/src/pkg/net/http/header_test.go
+++ b/src/pkg/net/http/header_test.go
@@ -8,6 +8,7 @@ import (
 	"bytes"
 	"runtime"
 	"testing"
+	"time"
 )
 
 var headerWriteTests = []struct {
@@ -99,6 +100,38 @@ func TestHeaderWrite(t *testing.T) {
 	}\n }\n 
+var parseTimeTests = []struct {
+\th   Header
+\terr bool
+}{\n+\t{Header{\"Date\": {\"\"}}, true},\n+\t{Header{\"Date\": {\"invalid\"}}, true},\n+\t{Header{\"Date\": {\"1994-11-06T08:49:37Z00:00\"}}, true},\n+\t{Header{\"Date\": {\"Sun, 06 Nov 1994 08:49:37 GMT\"}}, false},\n+\t{Header{\"Date\": {\"Sunday, 06-Nov-94 08:49:37 GMT\"}}, false},\n+\t{Header{\"Date\": {\"Sun Nov  6 08:49:37 1994\"}}, false},\n+}\n+\n+func TestParseTime(t *testing.T) {\n+\texpect := time.Date(1994, 11, 6, 8, 49, 37, 0, time.UTC)\n+\tfor i, test := range parseTimeTests {\n+\t\td, err := ParseTime(test.h.Get(\"Date\"))\n+\t\tif err != nil {\n+\t\t\tif !test.err {\n+\t\t\t\tt.Errorf(\"#%d:\\n got err: %v\", i, err)\n+\t\t\t}\n+\t\t\tcontinue\n+\t\t}\n+\t\tif test.err {\n+\t\t\tt.Errorf(\"#%d:\\n  should err\", i)\n+\t\t\tcontinue\n+\t\t}\n+\t\tif !expect.Equal(d) {\n+\t\t\tt.Errorf(\"#%d:\\n got: %v\\nwant: %v\", i, d, expect)\n+\t\t}\n+\t}\n+}\n+\n type hasTokenTest struct {\n \theader string\n \ttoken  string\n```

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

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

## 元コミット内容

`net/http`パッケージに`ParseTime`関数を追加しました。
この関数は、RFC 2616のセクション3.3に従って、時刻ヘッダーの値を`time.Time`型に解析します。

## 変更の背景

HTTP/1.1プロトコルでは、日付と時刻の情報を表現するために特定のフォーマットが定められています。特に、`Date`ヘッダーや`Expires`ヘッダーなど、多くのヘッダーフィールドで時刻情報が使用されます。RFC 2616のセクション3.3では、これらの時刻値を解析する際に、堅牢性を高めるために3つの異なるフォーマットを許容するように規定されています。

Go言語の`net/http`パッケージは、HTTPプロトコルを扱うための主要なライブラリであり、HTTPヘッダーの解析は非常に重要な機能です。しかし、このコミット以前は、HTTPヘッダーから時刻値を直接、かつRFC 2616の規定に厳密に従って解析するための汎用的なユーティリティ関数が提供されていませんでした。開発者は、HTTP時刻フォーマットの複雑さを自身で処理するか、外部ライブラリを使用する必要がありました。

このコミットは、このギャップを埋めるために`ParseTime`関数を導入しました。これにより、`net/http`パッケージの利用者にとって、HTTP時刻ヘッダーの解析がより簡単かつ堅牢になり、HTTPプロトコルの仕様に準拠したアプリケーション開発が促進されます。特に、異なるフォーマットの時刻文字列が混在する可能性のある実世界のHTTP通信において、この関数は非常に有用です。

## 前提知識の解説

### HTTP/1.1における日付/時刻フォーマット (RFC 2616 Section 3.3)

HTTP/1.1では、日付と時刻の表現に関して、堅牢なパーシングを可能にするために3つの異なるフォーマットを許容しています。これは、古いHTTP/1.0の実装との互換性を保つためでもあります。クライアントは常に最新のフォーマット(RFC 1123)を使用すべきですが、サーバーはこれら3つのフォーマットすべてを解析できる必要があります。

1.  **RFC 1123 (HTTP-date)**:
    *   フォーマット: `Sun, 06 Nov 1994 08:49:37 GMT`
    *   これはRFC 822の拡張であり、最も推奨されるフォーマットです。Go言語の`time`パッケージでは`time.RFC1123`または`http.TimeFormat`(`time.RFC1123`と同じ)として定義されています。

2.  **RFC 850 (HTTP-date)**:
    *   フォーマット: `Sunday, 06-Nov-94 08:49:37 GMT`
    *   これはRFC 1036で定義されたフォーマットであり、RFC 1123よりも古いものです。Go言語の`time`パッケージでは`time.RFC850`として定義されています。

3.  **ANSI C `asctime()` format**:
    *   フォーマット: `Sun Nov  6 08:49:37 1994`
    *   これはC言語の標準ライブラリ関数`asctime()`によって生成されるフォーマットです。Go言語の`time`パッケージでは`time.ANSIC`として定義されています。

`ParseTime`関数は、これらのフォーマットのいずれかに合致する文字列を正常に解析できる必要があります。

### Go言語の`time`パッケージと`time.Parse`関数

Go言語の標準ライブラリ`time`パッケージは、日付と時刻を扱うための強力な機能を提供します。
`time.Time`型は、特定の時点を表す構造体です。
`time.Parse(layout, value string) (Time, error)`関数は、指定された`layout`文字列に基づいて、`value`文字列を`time.Time`型に解析します。
`layout`文字列は、Go言語独自の参照時刻(`Mon Jan 2 15:04:05 MST 2006`)を使用して、解析したい時刻文字列のフォーマットを定義します。例えば、`"2006-01-02"`は`"YYYY-MM-DD"`形式の時刻を解析するために使用されます。
`time`パッケージには、`time.RFC1123`、`time.RFC850`、`time.ANSIC`といった、よく使われる標準フォーマットのレイアウト定数が事前に定義されています。

### Go言語の`net/http`パッケージ

`net/http`パッケージは、HTTPクライアントとサーバーの実装を提供するGo言語の標準ライブラリです。HTTPリクエストの送信、レスポンスの受信、ヘッダーの処理、ルーティングなど、HTTP通信に必要な基本的な機能がすべて含まれています。このパッケージは、ウェブアプリケーションやAPIサーバーを構築する上で不可欠なものです。

## 技術的詳細

`ParseTime`関数は、HTTPヘッダーの時刻値を堅牢に解析するために、以下の戦略を採用しています。

1.  **`timeFormats`スライスの定義**:
    `var timeFormats = []string{TimeFormat, time.RFC850, time.ANSIC,}`
    このグローバル変数`timeFormats`は、HTTP/1.1で許容される3つの時刻フォーマットに対応するレイアウト文字列を格納しています。
    *   `TimeFormat`: これは`net/http`パッケージ内で定義されている定数で、`time.RFC1123`と同じ値です。HTTP/1.1で推奨されるフォーマットに対応します。
    *   `time.RFC850`: RFC 850で定義されたフォーマットに対応します。
    *   `time.ANSIC`: ANSI Cの`asctime()`関数によって生成されるフォーマットに対応します。

2.  **ループによるフォーマットの試行**:
    `ParseTime`関数は、引数として受け取った`text`(時刻文字列)を解析するために、`timeFormats`スライス内の各レイアウトを順番に試行します。
    `for _, layout := range timeFormats { ... }`

3.  **`time.Parse`による解析**:
    ループの各イテレーションで、`time.Parse(layout, text)`が呼び出されます。この関数は、`text`が現在の`layout`に合致するかどうかを試み、成功すれば`time.Time`型の値と`nil`エラーを返します。

4.  **成功時の即時リターン**:
    `if err == nil { return }`
    もし`time.Parse`がエラーなく成功した場合、それは`text`が現在の`layout`に合致したことを意味します。この場合、関数は直ちに解析された`time.Time`値と`nil`エラーを返して終了します。これにより、複数のフォーマットを試行する際の効率が保証されます。

5.  **すべてのフォーマットが失敗した場合**:
    ループが終了しても`time.Parse`が一度も成功しなかった場合(つまり、すべての`layout`でエラーが発生した場合)、関数は最後に発生したエラー(通常は`time.Parse`が返したエラー)とゼロ値の`time.Time`を返します。これは、入力された時刻文字列がHTTP/1.1で許容されるどのフォーマットにも合致しなかったことを示します。

この実装により、`ParseTime`関数は、HTTP/1.1の仕様に準拠し、異なる形式の時刻ヘッダー値に対しても堅牢な解析を提供します。

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

### `src/pkg/net/http/header.go`

```go
import (
	"net/textproto"
	"sort"
	"strings"
	"time" // 新しく追加されたインポート
)

// ... 既存のコード ...

var timeFormats = []string{
	TimeFormat,
	time.RFC850,
	time.ANSIC,
}

// ParseTime parses a time header (such as the Date: header),
// trying each of the three formats allowed by HTTP/1.1:
// TimeFormat, time.RFC850, and time.ANSIC.
func ParseTime(text string) (t time.Time, err error) {
	for _, layout := range timeFormats {
		t, err = time.Parse(layout, text)
		if err == nil {
			return
		}
	}
	return
}

// ... 既存のコード ...

src/pkg/net/http/header_test.go

import (
	"bytes"
	"runtime"
	"testing"
	"time" // 新しく追加されたインポート
)

// ... 既存のテストコード ...

var parseTimeTests = []struct {
	h   Header
	err bool
}{
	{Header{"Date": {""}}, true}, // 空文字列はエラー
	{Header{"Date": {"invalid"}}, true}, // 無効な文字列はエラー
	{Header{"Date": {"1994-11-06T08:49:37Z00:00"}}, true}, // RFC 3339形式はHTTP時刻ではないためエラー
	{Header{"Date": {"Sun, 06 Nov 1994 08:49:37 GMT"}}, false}, // RFC 1123形式 (TimeFormat)
	{Header{"Date": {"Sunday, 06-Nov-94 08:49:37 GMT"}}, false}, // RFC 850形式
	{Header{"Date": {"Sun Nov  6 08:49:37 1994"}}, false}, // ANSIC形式
}

func TestParseTime(t *testing.T) {
	expect := time.Date(1994, 11, 6, 8, 49, 37, 0, time.UTC) // 期待される時刻
	for i, test := range parseTimeTests {
		d, err := ParseTime(test.h.Get("Date"))
		if err != nil {
			if !test.err {
				t.Errorf("#%d:\n got err: %v", i, err) // エラーが期待されないのにエラーが発生
			}
			continue
		}
		if test.err {
			t.Errorf("#%d:\n  should err", i) // エラーが期待されるのにエラーが発生しない
			continue
		}
		if !expect.Equal(d) {
			t.Errorf("#%d:\n got: %v\nwant: %v", i, d, expect) // 解析結果が期待値と異なる
		}
	}
}

// ... 既存のテストコード ...

コアとなるコードの解説

header.goにおける変更

  1. import "time"の追加: ParseTime関数がtime.Time型とtime.Parse関数を使用するため、timeパッケージがインポートされています。

  2. timeFormats変数の定義: var timeFormats = []string{TimeFormat, time.RFC850, time.ANSIC,} このスライスは、HTTP/1.1で許容される3つの標準的な日付/時刻フォーマットに対応するレイアウト文字列を格納しています。TimeFormatnet/httpパッケージ内で定義されているtime.RFC1123のエイリアスです。この順序は、一般的に最も新しいフォーマットから試行することで、より効率的な解析を可能にする意図があるかもしれません(ただし、RFC 2616は特定の順序を強制していません)。

  3. ParseTime関数の実装: func ParseTime(text string) (t time.Time, err error)

    • この関数は、入力されたtext文字列をtime.Time型に解析しようとします。
    • for _, layout := range timeFormatsループは、timeFormatsスライス内の各レイアウトを順番に取得します。
    • t, err = time.Parse(layout, text): 取得したlayoutと入力textを使用してtime.Parseを呼び出し、解析を試みます。
    • if err == nil { return }: もし解析が成功し、エラーがnilであれば、その時点で解析されたtime.Timetnilエラーを返して関数を終了します。これにより、複数のフォーマットを試行する際の無駄が省かれます。
    • ループが最後まで実行され、どのlayoutでも解析が成功しなかった場合、最後に発生したエラー(またはtime.Parseが返したエラー)とゼロ値のtime.Timeが返されます。これは、入力文字列がどのHTTP時刻フォーマットにも合致しなかったことを示します。

header_test.goにおける変更

  1. import "time"の追加: テストコードでもtimeパッケージを使用するため、インポートされています。

  2. parseTimeTests構造体の定義: parseTimeTestsは、ParseTime関数のテストケースを定義するスライスです。各テストケースは以下のフィールドを持ちます。

    • h Header: テスト対象の時刻文字列を含むhttp.Headerオブジェクト。Get("Date")で時刻文字列を取得するために使用されます。
    • err bool: このテストケースでエラーが期待されるかどうかを示すブール値。

    テストケースには、有効なHTTP時刻フォーマット(RFC 1123、RFC 850、ANSIC)の例と、無効なフォーマット(空文字列、"invalid"、RFC 3339形式)の例が含まれています。特に、RFC 3339形式(例: 1994-11-06T08:49:37Z00:00)はISO 8601に準拠していますが、HTTP/1.1の時刻フォーマットとしては許容されないため、エラーが期待されています。

  3. TestParseTime関数の実装:

    • expect := time.Date(1994, 11, 6, 8, 49, 37, 0, time.UTC): すべての有効なテストケースで期待されるtime.Timeオブジェクトを定義しています。これは、テストケースの時刻文字列がすべて同じ特定の時刻を表しているためです。
    • for i, test := range parseTimeTests: 各テストケースをループで処理します。
    • d, err := ParseTime(test.h.Get("Date")): ParseTime関数を呼び出し、結果を取得します。
    • エラーチェック:
      • if err != nil: ParseTimeがエラーを返した場合の処理。
        • if !test.err: エラーが期待されていないのにエラーが発生した場合、t.Errorfでテスト失敗を報告します。
        • continue: エラーが期待されており、実際にエラーが発生した場合は、次のテストケースに進みます。
      • if test.err: ParseTimeがエラーを返さなかった場合の処理。
        • t.Errorf("#%d:\n should err", i): エラーが期待されているのにエラーが発生しなかった場合、テスト失敗を報告します。
        • continue: 次のテストケースに進みます。
    • 結果の比較:
      • if !expect.Equal(d): エラーが発生せず、かつエラーが期待されていない場合、解析された時刻dが期待される時刻expectと等しいかどうかをEqualメソッドで比較します。
      • 等しくない場合、t.Errorfでテスト失敗を報告します。

このテストコードは、ParseTime関数がHTTP/1.1で許容される様々な時刻フォーマットを正しく解析できること、および無効なフォーマットに対して適切にエラーを返すことを網羅的に検証しています。

関連リンク

参考にした情報源リンク