[インデックス 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
構造体には、以下のような問題点がありました。
- 冗長なフィールド:
Raw
,RawAuthority
,RawPath
,RawUserinfo
といったフィールドは、URLの各コンポーネントを「ワイヤーフォーマット」(エスケープされた形式)で保持していましたが、これはしばしば他の「論理的な」フィールド(Scheme
,Host
,Path
など)と重複し、混乱や不整合の原因となっていました。特に、これらの「Raw」フィールドを直接操作すると、URLの文字列化や他のパッケージ(特にnet/http
)での利用時に予期せぬ挙動を引き起こす可能性がありました。 RawUserinfo
の使いにくさ: ユーザー名とパスワードを含むRawUserinfo
フィールドは、RFCで許可されている空のユーザー名やパスワードのケースを適切に扱うのが困難であり、実用性に欠けていました。また、RFC 2396が警告するように、URIに認証情報を平文で渡すことはセキュリティリスクを伴うため、その扱いをより抽象化し、安全な方法で提供する必要がありました。- 一貫性の欠如:
URL
構造体の各フィールドが、エスケープされた形式と非エスケープ形式で混在していることがあり、開発者がどのフィールドをいつ使うべきか判断しにくい状況でした。
これらの問題を解決し、URL
構造体の動作をシンプルかつ予測可能なものにするために、今回のインターフェースのクリーンアップが実施されました。特に、net/http
パッケージのようなURLを操作する他のパッケージとの連携をスムーズにすることが重視されています。
前提知識の解説
このコミットの変更内容を理解するためには、以下の概念について基本的な知識があると役立ちます。
-
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
)。#
記号の後に置かれます。
- Scheme (スキーム): リソースにアクセスするためのプロトコル(例:
- Opaque URL:
scheme:opaque_part
のような形式のURLで、パスが階層的ではないもの(例:mailto:user@example.com
)。この場合、//host/path
のような部分は存在しません。
-
RFC (Request for Comments):
- インターネット技術の標準や仕様を定義する文書群です。URLの構文はRFC 2396(URI Generic Syntax)やその改訂版であるRFC 3986で定義されています。
- 特に、RFC 2396では、URIに認証情報を平文で含めることのセキュリティリスクについて警告しています。
-
Go言語の
net/url
パッケージ:- Go言語でURLをパース(解析)したり、構築したりするための機能を提供する標準ライブラリです。
url.URL
構造体は、パースされたURLの各コンポーネントを保持します。url.Parse()
関数は文字列からURL
構造体を生成し、URL.String()
メソッドはURL
構造体から文字列を生成します。
-
Go言語の
net/http
パッケージ:- HTTPクライアントとサーバーを実装するための標準ライブラリです。
- HTTPリクエストを送信する際や受信したリクエストを処理する際に、
net/url.URL
構造体を利用します。特に、HTTPリクエストライン(例:GET /path?query HTTP/1.1
)の構築にはURLのパスとクエリ部分が必要です。
-
エスケープ処理 (URL Encoding/Decoding):
- URLには、特定の意味を持つ予約文字(例:
/
,?
,#
,&
,=
など)や、URLとして使用できない文字(例: スペース)が含まれる場合があります。 - これらの文字は、
%
に続けて2桁の16進数で表現される「パーセントエンコーディング」によってエスケープされます(例: スペースは%20
)。 net/url
パッケージは、URLのパース時にエスケープされた文字をデコードし、文字列化時に必要に応じてエンコードする役割を担います。
- URLには、特定の意味を持つ予約文字(例:
技術的詳細
このコミットにおける技術的な変更点は多岐にわたりますが、主要なポイントは以下の通りです。
-
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.com
のuser@example.com
)。Host string
Path string
: デコードされたパス。RawQuery string
: エンコードされたクエリ文字列(?
なし)。Fragment string
: デコードされたフラグメント(#
なし)。
これらの変更により、
URL
構造体は、URLの各論理的なコンポーネントを直接表現するようになり、冗長な「Raw」形式のフィールドが排除されました。これにより、開発者はURLの各部分をより直感的に扱えるようになります。RawQuery
のみがエンコードされた形式で残されているのは、クエリパラメータのパースと再構築がurl.Query()
メソッドを通じて行われるため、その内部表現として適切であると判断されたためです。 - 削除されたフィールド:
-
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()
メソッドが追加されました。これにより、ユーザー情報の扱いがより安全かつ柔軟になりました。
-
RequestURI()
メソッドの追加:func (u *URL) RequestURI() string
という新しいメソッドがURL
構造体に追加されました。- このメソッドは、HTTPリクエストライン(例:
GET /path?query HTTP/1.1
)で使用される形式で、URLのパスとクエリ部分をエンコードして返します。 - 不透明なURL(
scheme:opaque
)の場合にはopaque
部分を、そうでない場合にはエンコードされたパスとクエリを結合して返します。パスが空の場合は/
を返します。 - このメソッドの導入により、
net/http
パッケージなど、HTTPリクエストを構築する側でURLのパスとクエリを結合するロジックが簡素化され、一貫性が保たれるようになりました。
-
URLパースロジックの変更 (
url.go
のparse
関数):parse
関数は、新しいURL
構造体のフィールドに合わせて大幅に書き換えられました。- 特に、スキームの後に
//
が続かない「不透明なURL」(例:mailto:user@example.com
)のパースロジックが変更され、Opaque
フィールドが適切に設定されるようになりました。 - ユーザー情報を含むオーソリティ部分(
user:pass@host
)のパースも、新しいUserinfo
型を使用するように更新されました。
-
URL文字列化ロジックの変更 (
url.go
のString
メソッド):URL.String()
メソッドは、URL
構造体の変更に合わせて、User
フィールドとOpaque
フィールドを使用してURL文字列を再構築するように変更されました。これにより、より正確で一貫性のあるURL文字列が生成されるようになりました。
-
net/http
およびwebsocket
パッケージへの影響:net/http
パッケージ内の複数のファイル(client.go
,httputil/dump.go
,httputil/reverseproxy.go
,request.go
,transport.go
など)で、URL
構造体の変更に合わせて、RawUserinfo
やRawPath
の直接参照が、新しいUser
フィールドやRequestURI()
メソッドへの呼び出しに置き換えられました。- 同様に、
websocket
パッケージ内のファイル(hixie.go
,hybi.go
)でもconfig.Location.RawPath
がconfig.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言語
net/url
パッケージのドキュメント: https://pkg.go.dev/net/url - Go言語
net/http
パッケージのドキュメント: https://pkg.go.dev/net/http - RFC 3986 - Uniform Resource Identifier (URI): Generic Syntax: https://datatracker.ietf.org/doc/html/rfc3986
- RFC 2396 - Uniform Resource Identifiers (URI): Generic Syntax (旧版): https://datatracker.ietf.org/doc/html/rfc2396
参考にした情報源リンク
- 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処理に関する情報)