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

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

このコミットは、Go言語の標準ライブラリであるnet/urlパッケージのURL構造体のインターフェースを大幅にクリーンアップし、よりシンプルで堅牢な設計に改訂したものです。特に、冗長であったり、誤解を招きやすかったりするフィールドが削除され、ユーザー情報(Userinfo)の扱いが改善されました。これにより、URLの文字列化やnet/httpパッケージとの連携がより予測可能で一貫性のあるものになっています。

コミット

commit dafd9f0bfc4ef33845bc8c370e3a6bc48b39d793
Author: Gustavo Niemeyer <gustavo@niemeyer.net>
Date:   Tue Jan 17 00:49:05 2012 -0200

    net/url: cleaned up URL interface (v2)
    
    Duplicated fields from URL were dropped so that its behavior
    is simple and expected when being stringified and when being
    operated by packages like http. Most of the preserved fields
    are in unencoded form, except for RawQuery which continues to
    exist and be more easily handled via url.Query().
    
    The RawUserinfo field was also replaced since it wasn't practical
    to use and had limitations when operating with empty usernames
    and passwords which are allowed by the RFC. In its place the
    Userinfo type was introduced and made accessible through the
    url.User and url.UserPassword functions.
    
    What was previous built as:
    
            url.URL{RawUserinfo: url.EncodeUserinfo("user", ""), ...}
    
    Is now built as:
    
            url.URL{User: url.User("user"), ...}
    
    R=rsc, bradfitz, gustavo
    CC=golang-dev
    https://golang.org/cl/5498076

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

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

元コミット内容

net/url: cleaned up URL interface (v2)

Duplicated fields from URL were dropped so that its behavior
is simple and expected when being stringified and when being
operated by packages like http. Most of the preserved fields
are in unencoded form, except for RawQuery which continues to
exist and be more easily handled via url.Query().

The RawUserinfo field was also replaced since it wasn't practical
to use and had limitations when operating with empty usernames
and passwords which are allowed by the RFC. In its place the
Userinfo type was introduced and made accessible through the
url.User and url.UserPassword functions.

What was previous built as:

        url.URL{RawUserinfo: url.EncodeUserinfo("user", ""), ...}

Is now built as:

        url.URL{User: url.User("user"), ...}

R=rsc, bradfitz, gustavo
CC=golang-dev
https://golang.org/cl/5498076

変更の背景

このコミットの主な目的は、Go言語のnet/urlパッケージにおけるURL構造体の設計を改善し、より直感的で使いやすく、かつ堅牢なものにすることです。以前のURL構造体には、以下のような問題点がありました。

  1. 冗長なフィールド: Raw, RawAuthority, RawPath, RawUserinfoといったフィールドは、URLの各コンポーネントを「ワイヤーフォーマット」(エスケープされた形式)で保持していましたが、これはしばしば他の「論理的な」フィールド(Scheme, Host, Pathなど)と重複し、混乱や不整合の原因となっていました。特に、これらの「Raw」フィールドを直接操作すると、URLの文字列化や他のパッケージ(特にnet/http)での利用時に予期せぬ挙動を引き起こす可能性がありました。
  2. RawUserinfoの使いにくさ: ユーザー名とパスワードを含むRawUserinfoフィールドは、RFCで許可されている空のユーザー名やパスワードのケースを適切に扱うのが困難であり、実用性に欠けていました。また、RFC 2396が警告するように、URIに認証情報を平文で渡すことはセキュリティリスクを伴うため、その扱いをより抽象化し、安全な方法で提供する必要がありました。
  3. 一貫性の欠如: URL構造体の各フィールドが、エスケープされた形式と非エスケープ形式で混在していることがあり、開発者がどのフィールドをいつ使うべきか判断しにくい状況でした。

これらの問題を解決し、URL構造体の動作をシンプルかつ予測可能なものにするために、今回のインターフェースのクリーンアップが実施されました。特に、net/httpパッケージのようなURLを操作する他のパッケージとの連携をスムーズにすることが重視されています。

前提知識の解説

このコミットの変更内容を理解するためには、以下の概念について基本的な知識があると役立ちます。

  1. URI (Uniform Resource Identifier) と URL (Uniform Resource Locator):

    • URIは、リソースを一意に識別するための文字列の総称です。URLはURIの一種であり、リソースの場所(ロケータ)を示すものです。
    • URLの一般的な構文は、scheme://[userinfo@]host/path[?query][#fragment]です。
      • Scheme (スキーム): リソースにアクセスするためのプロトコル(例: http, https, ftp, mailto)。
      • Userinfo (ユーザー情報): ユーザー名とオプションのパスワード(例: user:password)。通常は@記号の前に置かれます。
      • Host (ホスト): リソースが配置されているサーバーのドメイン名またはIPアドレス。
      • Path (パス): ホスト上のリソースの階層的な場所(例: /path/to/resource)。
      • Query (クエリ): リソースに渡される追加のデータ(例: key=value&another=data)。?記号の後に置かれます。
      • Fragment (フラグメント): リソース内の特定の部分を指す識別子(例: section1)。#記号の後に置かれます。
    • Opaque URL: scheme:opaque_partのような形式のURLで、パスが階層的ではないもの(例: mailto:user@example.com)。この場合、//host/pathのような部分は存在しません。
  2. RFC (Request for Comments):

    • インターネット技術の標準や仕様を定義する文書群です。URLの構文はRFC 2396(URI Generic Syntax)やその改訂版であるRFC 3986で定義されています。
    • 特に、RFC 2396では、URIに認証情報を平文で含めることのセキュリティリスクについて警告しています。
  3. Go言語のnet/urlパッケージ:

    • Go言語でURLをパース(解析)したり、構築したりするための機能を提供する標準ライブラリです。
    • url.URL構造体は、パースされたURLの各コンポーネントを保持します。
    • url.Parse()関数は文字列からURL構造体を生成し、URL.String()メソッドはURL構造体から文字列を生成します。
  4. Go言語のnet/httpパッケージ:

    • HTTPクライアントとサーバーを実装するための標準ライブラリです。
    • HTTPリクエストを送信する際や受信したリクエストを処理する際に、net/url.URL構造体を利用します。特に、HTTPリクエストライン(例: GET /path?query HTTP/1.1)の構築にはURLのパスとクエリ部分が必要です。
  5. エスケープ処理 (URL Encoding/Decoding):

    • URLには、特定の意味を持つ予約文字(例: /, ?, #, &, =など)や、URLとして使用できない文字(例: スペース)が含まれる場合があります。
    • これらの文字は、%に続けて2桁の16進数で表現される「パーセントエンコーディング」によってエスケープされます(例: スペースは%20)。
    • net/urlパッケージは、URLのパース時にエスケープされた文字をデコードし、文字列化時に必要に応じてエンコードする役割を担います。

技術的詳細

このコミットにおける技術的な変更点は多岐にわたりますが、主要なポイントは以下の通りです。

  1. URL構造体のフィールドの再定義と簡素化:

    • 削除されたフィールド:
      • Raw string: 元のURL文字列全体。
      • RawAuthority string: [userinfo@]host部分のワイヤーフォーマット。
      • RawUserinfo string: user:password部分のワイヤーフォーマット。
      • RawPath string: /path[?query][#fragment]部分のワイヤーフォーマット。
      • OpaquePath bool: パスが不透明(opaque)であるかを示すフラグ。
    • 追加されたフィールド:
      • User *Userinfo: ユーザー名とパスワードをカプセル化する新しいUserinfo型のポインタ。
    • 残された主要フィールド:
      • Scheme string
      • Opaque string: 不透明なURLのパス部分(例: mailto:user@example.comuser@example.com)。
      • Host string
      • Path string: デコードされたパス。
      • RawQuery string: エンコードされたクエリ文字列(?なし)。
      • Fragment string: デコードされたフラグメント(#なし)。

    これらの変更により、URL構造体は、URLの各論理的なコンポーネントを直接表現するようになり、冗長な「Raw」形式のフィールドが排除されました。これにより、開発者はURLの各部分をより直感的に扱えるようになります。RawQueryのみがエンコードされた形式で残されているのは、クエリパラメータのパースと再構築がurl.Query()メソッドを通じて行われるため、その内部表現として適切であると判断されたためです。

  2. Userinfo型の導入と関連関数の提供:

    • RawUserinfoフィールドの代わりに、Userinfoという新しい構造体が導入されました。
    • type Userinfo struct { username string; password string; passwordSet bool }
    • この型は、ユーザー名とパスワードをカプセル化し、パスワードが設定されているかどうかを示すpasswordSetフラグを持ちます。
    • ユーザー情報を生成するためのヘルパー関数が提供されます。
      • func User(username string) *Userinfo: パスワードなしのユーザー情報を生成。
      • func UserPassword(username, password string) *Userinfo: パスワード付きのユーザー情報を生成。
    • Userinfo型には、ユーザー名とパスワードを取得するためのメソッド(Username(), Password())と、エンコードされたユーザー情報文字列を返すString()メソッドが追加されました。これにより、ユーザー情報の扱いがより安全かつ柔軟になりました。
  3. RequestURI()メソッドの追加:

    • func (u *URL) RequestURI() stringという新しいメソッドがURL構造体に追加されました。
    • このメソッドは、HTTPリクエストライン(例: GET /path?query HTTP/1.1)で使用される形式で、URLのパスとクエリ部分をエンコードして返します。
    • 不透明なURL(scheme:opaque)の場合にはopaque部分を、そうでない場合にはエンコードされたパスとクエリを結合して返します。パスが空の場合は/を返します。
    • このメソッドの導入により、net/httpパッケージなど、HTTPリクエストを構築する側でURLのパスとクエリを結合するロジックが簡素化され、一貫性が保たれるようになりました。
  4. URLパースロジックの変更 (url.goparse関数):

    • parse関数は、新しいURL構造体のフィールドに合わせて大幅に書き換えられました。
    • 特に、スキームの後に//が続かない「不透明なURL」(例: mailto:user@example.com)のパースロジックが変更され、Opaqueフィールドが適切に設定されるようになりました。
    • ユーザー情報を含むオーソリティ部分(user:pass@host)のパースも、新しいUserinfo型を使用するように更新されました。
  5. URL文字列化ロジックの変更 (url.goStringメソッド):

    • URL.String()メソッドは、URL構造体の変更に合わせて、UserフィールドとOpaqueフィールドを使用してURL文字列を再構築するように変更されました。これにより、より正確で一貫性のあるURL文字列が生成されるようになりました。
  6. net/httpおよびwebsocketパッケージへの影響:

    • net/httpパッケージ内の複数のファイル(client.go, httputil/dump.go, httputil/reverseproxy.go, request.go, transport.goなど)で、URL構造体の変更に合わせて、RawUserinfoRawPathの直接参照が、新しいUserフィールドやRequestURI()メソッドへの呼び出しに置き換えられました。
    • 同様に、websocketパッケージ内のファイル(hixie.go, hybi.go)でもconfig.Location.RawPathconfig.Location.RequestURI()に置き換えられています。
    • これらの変更は、net/urlパッケージのインターフェース変更が、それを利用する他の標準ライブラリにどのように波及し、コードの簡素化と堅牢化に貢献したかを示しています。

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

このコミットのコアとなる変更は、主にsrc/pkg/net/url/url.goファイルに集中しています。

  • src/pkg/net/url/url.go:
    • URL構造体の定義が変更され、Raw, RawAuthority, RawUserinfo, RawPath, OpaquePathフィールドが削除され、User *Userinfoフィールドが追加されました。
    • Userinfo構造体と、そのコンストラクタであるUser()UserPassword()関数が新しく定義されました。
    • URL構造体にRequestURI()メソッドが追加されました。
    • parse()関数(URL文字列をURL構造体に解析する主要な関数)が、新しいURL構造体とUserinfo型に対応するように大幅に修正されました。
    • String()メソッド(URL構造体をURL文字列に変換するメソッド)が、新しいフィールドを考慮して再実装されました。
    • ResolveReference()メソッドも、新しいURL構造体のセマンティクスに合わせて更新されました。

また、これらの変更に伴い、src/pkg/net/url/url_test.goファイルでは、新しいURL構造体とメソッドの動作を検証するためのテストケースが広範囲にわたって更新・追加されています。

さらに、net/urlパッケージの変更に依存するnet/httpおよびwebsocketパッケージの複数のファイルでも、古いURLフィールドへの参照が新しいAPIに置き換えられています。

コアとなるコードの解説

src/pkg/net/url/url.goにおけるURL構造体の変更

type URL struct {
	Scheme   string
	Opaque   string    // encoded opaque data
	User     *Userinfo // username and password information
	Host     string
	Path     string
	RawQuery string // encoded query values, without '?'
	Fragment string // fragment for references, without '#'
}

この変更により、URL構造体はURLの各論理的な部分を直接表現するようになりました。

  • Raw, RawAuthority, RawPath, RawUserinfoといった「ワイヤーフォーマット」のフィールドが削除されました。これにより、URLの各部分がエスケープされているかどうかの判断が不要になり、開発者の混乱が軽減されます。
  • User *Userinfoが導入され、ユーザー名とパスワードの情報をより構造化された形で保持できるようになりました。これは、RFCで許可されている空のユーザー名やパスワードのケースを適切に処理し、セキュリティ上の懸念を軽減するのに役立ちます。
  • Opaqueフィールドは、mailto:name@example.comのような「不透明なURL」のパス部分を保持するために使用されます。

Userinfo型の導入

type Userinfo struct {
	username    string
	password    string
	passwordSet bool
}

func User(username string) *Userinfo {
	return &Userinfo{username, "", false}
}

func UserPassword(username, password string) *Userinfo {
	return &Userinfo{username, password, true}
}

func (u *Userinfo) Username() string {
	return u.username
}

func (u *Userinfo) Password() (string, bool) {
	if u.passwordSet {
		return u.password, true
	}
	return "", false
}

func (u *Userinfo) String() string {
	s := escape(u.username, encodeUserPassword)
	if u.passwordSet {
		s += ":" + escape(u.password, encodeUserPassword)
	}
	return s
}

Userinfo型は、ユーザー名とパスワードをカプセル化し、パスワードが設定されているかどうかを明示的に示すpasswordSetフラグを持ちます。これにより、ユーザー情報の有無やパスワードの有無を正確に判断できます。User()UserPassword()は、このUserinfoインスタンスを簡単に生成するためのファクトリ関数です。String()メソッドは、ユーザー情報をURLのuserinfo部分として適切にエンコードされた形式で返します。

RequestURI()メソッドの追加

func (u *URL) RequestURI() string {
	result := u.Opaque
	if result == "" {
		result = escape(u.Path, encodePath)
		if result == "" {
			result = "/"
		}
	}
	if u.RawQuery != "" {
		result += "?" + u.RawQuery
	}
	return result
}

このメソッドは、HTTPリクエストの第一行(例: GET /path?query HTTP/1.1)で使用されるURI部分を生成します。

  • Opaqueフィールドが設定されている場合(不透明なURLの場合)は、その値をそのまま返します。
  • そうでない場合、Pathフィールドを適切にエンコードし、それが空であれば/をデフォルトとして使用します。
  • RawQueryが存在する場合は、?を付けて結合します。 このメソッドにより、net/httpパッケージなどのHTTPクライアントやサーバーの実装が、URLのパスとクエリ部分をより簡単に、かつ正確に取得できるようになりました。

parse()関数の変更

parse()関数は、入力されたURL文字列を解析し、新しいURL構造体のフィールドにマッピングする役割を担います。このコミットでは、特にスキームの後に//が続かない不透明なURLの処理や、ユーザー情報を含むオーソリティ部分の解析ロジックが、新しいOpaqueフィールドとUserinfo型を使用するように書き換えられました。これにより、URLのパースがより堅牢で、RFCの仕様に厳密に準拠するようになりました。

関連リンク

参考にした情報源リンク

  • Go Gerrit Change-Id: https://golang.org/cl/5498076 (このコミットの元のコードレビューページ)
  • Go言語のソースコード (特にsrc/pkg/net/url/url.goおよび関連するテストファイル)
  • Go言語の公式ドキュメント
  • RFC 2396 および RFC 3986 (URIの仕様に関する情報)
  • Go言語のコミュニティや開発者フォーラムでの議論(一般的なGoの設計原則やURL処理に関する情報)