goでHTTPリクエストボディのサイズを制限する「MaxBytesReader」
webアプリケーションを作成していると、ユーザが誤って、もしくは悪意を持って巨大なリクエストを送ってくる場合があります。 何も対応しないとリクエストを受け付けてしまい、アプリケーションが不安定になったり無駄にメモリを確保してしまいます。
そこで今回はgoでHTTPのリクエストボディを制限するMaxBytesReaderを紹介します。
MaxBytesReaderとは?
MaxBytesReader prevents clients from accidentally or maliciously sending a large request and wasting server resources.
MaxBytesReader は io.LimitReader に似ていますが、受信するリクエスト ボディのサイズを制限することを目的としています。 io.LimitReader とは対照的に、MaxBytesReader の結果は ReadCloser であり、制限を超えた読み取りに対しては非 EOF エラーを返し、 Close メソッドが呼び出されると基礎となるリーダーを閉じます。
MaxBytesReaderは、クライアントが誤ってまたは悪意を持って大きなリクエストを送信し、サーバーのリソースを浪費することを防ぎます。MaxBytesReader
関数の定義は以下のようになっています。
1 |
func MaxBytesReader(w ResponseWriter, r io.ReadCloser, n int64) io.ReadCloser |
使い方
以下の例ではmyHandler関数内でリクエストのBodyをバイト数10で指定してMaxBytesReaderでWrapしています。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 |
package main import ( "bytes" "fmt" "io" "net/http" "net/http/httptest" ) func main() { reqBody := bytes.NewBufferString("abcdefghijklmnopqrstuvwxyz") req := httptest.NewRequest(http.MethodGet, "http://dummy.url.com/", reqBody) got := httptest.NewRecorder() myHandler(got, req) fmt.Println(got.Body.String()) } func myHandler(w http.ResponseWriter, r *http.Request) { body := http.MaxBytesReader(w, r.Body, 10) i, err := io.Copy(w, body) fmt.Println(i) if err != nil { fmt.Println(err) } } |
上記を実行すると以下の出力が得られます。
1 2 3 4 |
$ go run sample.go 10 http: request body too large abcdefghij |
io.Copyで書き出した数が10となっており、指定したバイト数と一致しています。
また、errには http: request body too large というメッセージが入っています。これはMaxBytesReaderの定型エラーメッセージです。
最後にmain関数側で読み込んだ10バイト分が出力されています。
ginで利用する場合
先ほどの例ではハンドラー毎に制限をかける必要があるのでginで一括でリクエストに対して制限をかける例を紹介します。
以下のサンプルはPOSTで受けたJSONリクエストをmap[string]string{}にバインドしてその値をまたJSON形式に変換して返します。 リクエストのサイズ制限は10バイトとして定義しています。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 |
package main import ( "net/http" "github.com/gin-gonic/gin" ) const ( MaxBodyBytes = 10 ) func main() { router := gin.Default() router.Use(bodySizeMiddleware) router.POST("/", func(ctx *gin.Context) { m := map[string]string{} if err := ctx.BindJSON(&m); err != nil { ctx.AbortWithStatusJSON(http.StatusBadRequest, gin.H{ "message": err.Error(), }) return } ctx.JSON(200, m) }) router.Run() } func bodySizeMiddleware(c *gin.Context) { var w http.ResponseWriter = c.Writer c.Request.Body = http.MaxBytesReader(w, c.Request.Body, MaxBodyBytes) c.Next() } |
ginのミドルウェアの機構を利用してリクエストがあった場合に事前にRequest.Bodyを MaxBytesReader でWrapしておいて、 実際の読みだし(このケースの場合は ctx.BindJSON )でエラーを取得しています。
このサンプルを実行して10バイト以上のデータを送ると以下のエラーが返されます。
1 |
{"message":"http: request body too large"}{} |
さいごに
いかがだったでしょうか。 関数が用意されているので意外と簡単にリクエストのサイズを制限する事ができました。 実際に運用する場合はwebサーバ側やContent-Lengthなど他の制限手段と併せてリクエストに制限を加えるのがいいと思います。

竹内 和也

最新記事 by 竹内 和也 (全て見る)
- Go WebAssemblyでブラウザ上でGoを実行する その2 Go側からJavascriptを操作する - 2022年6月21日
- GoのコードをWebAssenblyにコンパイルしてブラウザ上でGoを実行する - 2022年4月25日
- 【Go】go.modのreplaceでローカルのモジュールを参照する - 2022年2月22日
- goでHTTPリクエストボディのサイズを制限する「MaxBytesReader」 - 2021年12月15日
- 【Go言語】jsonデータをstreamで扱うEncoder、Decoder型を試してみる - 2021年10月17日
関連記事
最新記事
FOLLOW US
最新の情報をお届けします
- facebookでフォロー
- Twitterでフォロー
- Feedlyでフォロー