[インデックス 18742] ファイルの概要
このコミットは、Go言語の標準ライブラリである net/http
パッケージにおいて、HTTP PATCH
メソッドのリクエストボディが application/x-www-form-urlencoded
形式である場合に、Request.ParseForm
メソッドがそのフォームデータを適切にパースできるようにする変更です。これにより、PATCH
リクエストでも POST
や PUT
と同様にフォームデータにアクセスできるようになります。
コミット
commit b38320bffbc2239b5825566676004f4dc45d5444
Author: Matt Aimonetti <mattaimonetti@gmail.com>
Date: Tue Mar 4 11:58:21 2014 -0800
net/http: make Request.ParseForm parse form-urlencoded for method PATCH too
Fixes #7454
LGTM=bradfitz
R=golang-codereviews, bradfitz
CC=golang-codereviews
https://golang.org/cl/70990044
GitHub上でのコミットページへのリンク
https://github.com/golang/go/commit/b38320bffbc2239b5825566676004f4dc45d5444
元コミット内容
net/http: make Request.ParseForm parse form-urlencoded for method PATCH too
Fixes #7454
このコミットは、net/http
パッケージの Request.ParseForm
メソッドが、PATCH
メソッドのリクエストに対しても application/x-www-form-urlencoded
形式のフォームデータをパースするように変更するものです。これにより、PATCH
リクエストのボディに含まれるフォームデータが Request.Form
や Request.PostForm
を通じてアクセス可能になります。
変更の背景
HTTP PATCH
メソッドは、リソースの部分的な更新を適用するために使用されます。POST
や PUT
と異なり、PATCH
はリソース全体を置き換えるのではなく、指定された変更のみを適用します。Webアプリケーションでは、フォームデータ(application/x-www-form-urlencoded
や multipart/form-data
)を使用してリクエストボディにデータを送信することが一般的です。
Goの net/http
パッケージの Request.ParseForm
メソッドは、以前は POST
および PUT
メソッドのリクエストボディに対してのみ application/x-www-form-urlencoded
形式のパースを行っていました。しかし、一部のアプリケーションやAPI設計では、PATCH
メソッドでもフォームデータ形式で部分更新の情報を送信したいというニーズがありました。
このコミットは、Go issue #7454 で報告された問題に対応するものです。この問題は、PATCH
リクエストで送信されたフォームデータが ParseForm
によって処理されないため、開発者が PATCH
リクエストのボディからフォームデータにアクセスできないというものでした。この変更により、PATCH
メソッドの柔軟性が向上し、より幅広いWebアプリケーションのユースケースに対応できるようになります。
前提知識の解説
HTTPメソッド
HTTP (Hypertext Transfer Protocol) は、Web上でデータをやり取りするためのプロトコルです。HTTPメソッドは、クライアントがサーバーに対してどのような操作を行いたいかを伝えるために使用されます。主要なHTTPメソッドには以下のようなものがあります。
- GET: リソースの取得。
- POST: 新しいリソースの作成、または既存のリソースへのデータの送信。
- PUT: リソースの完全な置き換え。
- DELETE: リソースの削除。
- PATCH: リソースの部分的な更新。
特に POST
、PUT
、PATCH
はリクエストボディを持つことができ、クライアントからサーバーへデータを送信するために利用されます。
application/x-www-form-urlencoded
これは、HTTPリクエストの Content-Type
ヘッダで指定されるメディアタイプの一つで、Webフォームのデータをエンコードするための標準的な方法です。キーと値のペアが &
で区切られ、各ペアは key=value
の形式で表現されます。値はURLエンコードされます(例: スペースは %20
になる)。これは、HTMLフォームの method="POST"
で enctype="application/x-www-form-urlencoded"
が指定された場合のデフォルトのエンコーディング形式です。
Go net/http
パッケージ
net/http
はGo言語の標準ライブラリで、HTTPクライアントとサーバーの実装を提供します。Webアプリケーションを構築する上で中心的な役割を果たします。
http.Request
: クライアントからのHTTPリクエストを表す構造体です。リクエストメソッド、URL、ヘッダ、ボディなどの情報を含みます。Request.ParseForm()
:http.Request
のメソッドで、リクエストのURLクエリパラメータと、POST
またはPUT
(このコミット後はPATCH
も含む) リクエストのボディからフォームデータをパースし、Request.Form
およびRequest.PostForm
フィールドに格納します。Request.Form
: URLクエリパラメータとリクエストボディのフォームデータを結合したものです。同じキーが存在する場合、ボディのデータが優先されます。Request.PostForm
: リクエストボディのフォームデータのみを含みます。
Request.FormValue(key string)
:Request.Form
から指定されたキーの最初の値を取得する便利なメソッドです。ParseForm
を自動的に呼び出します。Request.PostFormValue(key string)
:Request.PostForm
から指定されたキーの最初の値を取得する便利なメソッドです。ParseForm
を自動的に呼び出します。
Goのテストフレームワーク
Goには組み込みのテストフレームワークがあり、go test
コマンドで実行できます。テストファイルは通常、テスト対象のファイルと同じディレクトリに _test.go
サフィックスを付けて配置されます。テスト関数は Test
で始まり、*testing.T
型の引数を取ります。
技術的詳細
このコミットの技術的な核心は、net/http
パッケージ内の Request.ParseForm
メソッドのロジック変更にあります。
Request.ParseForm
メソッドは、HTTPリクエストのボディをパースしてフォームデータを抽出する役割を担っています。以前の実装では、このパース処理は r.Method == "POST" || r.Method == "PUT"
という条件に基づいていました。これは、伝統的にフォームデータが POST
や PUT
リクエストで送信されることが多かったためです。
しかし、RESTful APIの設計や、よりセマンティックなHTTPメソッドの利用が広まるにつれて、リソースの部分更新を意味する PATCH
メソッドでもフォームデータ形式で情報を送信したいというニーズが出てきました。例えば、特定のフィールドだけを更新する際に、application/x-www-form-urlencoded
形式で field=newValue
のように送りたい場合などです。
このコミットでは、この条件式に || r.Method == "PATCH"
を追加することで、PATCH
メソッドのリクエストも POST
や PUT
と同様に parsePostForm(r)
関数によってボディのパース対象としました。parsePostForm
関数は、リクエストの Content-Type
ヘッダが application/x-www-form-urlencoded
または multipart/form-data
である場合に、適切にボディを読み込み、キーと値のペアを抽出して url.Values
型のマップとして返します。
この変更により、PATCH
リクエストのボディが application/x-www-form-urlencoded
形式であれば、Request.ParseForm()
を呼び出すことで Request.PostForm
にそのデータが格納され、Request.Form
にもクエリパラメータと結合された形でデータが利用可能になります。これにより、開発者は Request.FormValue()
や Request.PostFormValue()
といった便利なメソッドを使って、PATCH
リクエストのボディから直接フォームデータにアクセスできるようになります。
また、この変更は後方互換性を維持しつつ、net/http
パッケージの柔軟性とHTTP標準への準拠を向上させるものです。既存の POST
や PUT
リクエストの処理には影響を与えません。
コアとなるコードの変更箇所
変更は主に以下の2つのファイルで行われています。
src/pkg/net/http/request.go
:Request.ParseForm
メソッドのロジックが変更されています。src/pkg/net/http/request_test.go
:PATCH
メソッドのフォームデータパースを検証するための新しいテストケースが追加されています。
diff --git a/src/pkg/net/http/request.go b/src/pkg/net/http/request.go
index 7a97770314..480baf3aee 100644
--- a/src/pkg/net/http/request.go
+++ b/src/pkg/net/http/request.go
@@ -728,7 +728,7 @@ func parsePostForm(r *Request) (vs url.Values, err error) {
func (r *Request) ParseForm() error {
var err error
if r.PostForm == nil {
- if r.Method == "POST" || r.Method == "PUT" {
+ if r.Method == "POST" || r.Method == "PUT" || r.Method == "PATCH" {
r.PostForm, err = parsePostForm(r)
}
if r.PostForm == nil {
diff --git a/src/pkg/net/http/request_test.go b/src/pkg/net/http/request_test.go
index 68d141398a..223e02c294 100644
--- a/src/pkg/net/http/request_test.go
+++ b/src/pkg/net/http/request_test.go
@@ -60,6 +60,37 @@ func TestPostQuery(t *testing.T) {
}\n }\n \n+func TestPatchQuery(t *testing.T) {
+\treq, _ := NewRequest("PATCH", "http://www.google.com/search?q=foo&q=bar&both=x&prio=1&empty=not",
+\t\tstrings.NewReader("z=post&both=y&prio=2&empty="))
+\treq.Header.Set("Content-Type", "application/x-www-form-urlencoded; param=value")
+\n+\tif q := req.FormValue("q"); q != "foo" {
+\t\tt.Errorf(`req.FormValue("q") = %q, want "foo"`, q)
+\t}
+\tif z := req.FormValue("z"); z != "post" {
+\t\tt.Errorf(`req.FormValue("z") = %q, want "post"`, z)
+\t}
+\tif bq, found := req.PostForm["q"]; found {
+\t\tt.Errorf(`req.PostForm["q"] = %q, want no entry in map`, bq)
+\t}
+\tif bz := req.PostFormValue("z"); bz != "post" {
+\t\tt.Errorf(`req.PostFormValue("z") = %q, want "post"`, bz)
+\t}
+\tif qs := req.Form["q"]; !reflect.DeepEqual(qs, []string{"foo", "bar"}) {
+\t\tt.Errorf(`req.Form["q"] = %q, want ["foo", "bar"]`, qs)
+\t}
+\tif both := req.Form["both"]; !reflect.DeepEqual(both, []string{"y", "x"}) {
+\t\tt.Errorf(`req.Form["both"] = %q, want ["y", "x"]`, both)
+\t}
+\tif prio := req.FormValue("prio"); prio != "2" {
+\t\tt.Errorf(`req.FormValue("prio") = %q, want "2" (from body)`, prio)
+\t}
+\tif empty := req.FormValue("empty"); empty != "" {
+\t\tt.Errorf(`req.FormValue("empty") = %q, want "" (from body)`, empty)
+\t}
+}\n+\n type stringMap map[string][]string
type parseContentTypeTest struct {
shouldError bool
コアとなるコードの解説
src/pkg/net/http/request.go
の変更
Request.ParseForm
メソッド内の以下の行が変更されています。
- if r.Method == "POST" || r.Method == "PUT" {
+ if r.Method == "POST" || r.Method == "PUT" || r.Method == "PATCH" {
この変更は非常にシンプルですが、その影響は大きいです。
r.PostForm == nil
の条件は、ParseForm
がまだ実行されていないことを確認しています。その内部で、リクエストメソッドが POST
または PUT
の場合にのみ parsePostForm(r)
が呼び出され、リクエストボディのフォームデータがパースされて r.PostForm
に格納されていました。
このコミットにより、PATCH
メソッドもこの条件に追加され、PATCH
リクエストのボディも parsePostForm
によって処理されるようになりました。これにより、PATCH
リクエストで application/x-www-form-urlencoded
形式のデータが送信された場合でも、Request.PostForm
および Request.Form
を通じてそのデータにアクセスできるようになります。
src/pkg/net/http/request_test.go
の変更
TestPatchQuery
という新しいテスト関数が追加されています。このテストは、PATCH
メソッドのリクエストに対して Request.ParseForm
が正しく動作するかを検証します。
テストケースの概要:
NewRequest
を使用してPATCH
メソッドのHTTPリクエストを作成します。- URLクエリパラメータ (
q=foo&q=bar&both=x&prio=1&empty=not
) と、 application/x-www-form-urlencoded
形式のリクエストボディ (z=post&both=y&prio=2&empty=
) を含みます。
- URLクエリパラメータ (
Content-Type
ヘッダをapplication/x-www-form-urlencoded
に設定します。req.FormValue
やreq.PostForm
、req.Form
を使用して、パースされたフォームデータが期待通りの値を持っているかを確認します。
特に重要な検証ポイントは以下の通りです。
req.FormValue("q")
: URLクエリパラメータからfoo
が取得できること。req.FormValue("z")
: リクエストボディからpost
が取得できること。req.PostForm["q"]
:PostForm
にはクエリパラメータのq
が含まれないこと(PostForm
はボディのみを対象とするため)。req.PostFormValue("z")
:PostFormValue
でボディのz
が取得できること。req.Form["q"]
:Form
にはURLクエリパラメータのq
の両方の値 (foo
,bar
) が含まれること。req.Form["both"]
:Form
ではボディのboth
(y
) がクエリパラメータのboth
(x
) よりも優先されること (y
,x
の順で取得される)。req.FormValue("prio")
:FormValue
はボディのprio
(2
) を優先すること。req.FormValue("empty")
: ボディの空の値 (empty=
) が正しくパースされ、空文字列として取得できること。
これらのテストは、PATCH
メソッドのリクエストボディが ParseForm
によって正しく処理され、URLクエリパラメータとボディのフォームデータが期待通りに結合・優先されることを保証します。
関連リンク
- Go issue #7454: https://github.com/golang/go/issues/7454
- Go
net/http
パッケージドキュメント: https://pkg.go.dev/net/http - HTTP PATCH Method (RFC 5789): https://datatracker.ietf.org/doc/html/rfc5789
- HTML Form
enctype
attribute: https://developer.mozilla.org/en-US/docs/Web/HTML/Element/form#attr-enctype
参考にした情報源リンク
- Go issue #7454 の議論内容
- Go
net/http
パッケージのソースコード - HTTP/1.1 Semantics and Content (RFC 7231)
- The PATCH Method for HTTP (RFC 5789)
- MDN Web Docs: HTTP methods
- MDN Web Docs:
Content-Type
- Go言語の公式ドキュメントとブログ記事 (一般的な
net/http
の利用方法について) - Stack Overflow や技術ブログ (GoのHTTPサーバーとフォームデータ処理に関する一般的な情報)