[インデックス 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
enctypeattribute: 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サーバーとフォームデータ処理に関する一般的な情報)