グローバルナビゲーションへ

本文へ

フッターへ

お役立ち情報Blog



goでHTTPリクエストボディのサイズを制限する「MaxBytesReader」

webアプリケーションを作成していると、ユーザが誤って、もしくは悪意を持って巨大なリクエストを送ってくる場合があります。 何も対応しないとリクエストを受け付けてしまい、アプリケーションが不安定になったり無駄にメモリを確保してしまいます。

そこで今回はgoでHTTPのリクエストボディを制限するMaxBytesReaderを紹介します。

MaxBytesReaderとは?

MaxBytesReader is similar to io.LimitReader but is intended for limiting the size of incoming request bodies. In contrast to io.LimitReader, MaxBytesReader’s result is a ReadCloser, returns a non-EOF error for a Read beyond the limit, and closes the underlying reader when its Close method is called.

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

関数の定義は以下のようになっています。

func MaxBytesReader(w ResponseWriter, r io.ReadCloser, n int64) io.ReadCloser

使い方

以下の例ではmyHandler関数内でリクエストのBodyをバイト数10で指定してMaxBytesReaderでWrapしています。

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)
	}
}

上記を実行すると以下の出力が得られます。

$ 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バイトとして定義しています。

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バイト以上のデータを送ると以下のエラーが返されます。

{"message":"http: request body too large"}{}

さいごに

いかがだったでしょうか。 関数が用意されているので意外と簡単にリクエストのサイズを制限する事ができました。 実際に運用する場合はwebサーバ側やContent-Lengthなど他の制限手段と併せてリクエストに制限を加えるのがいいと思います。

この記事を書いた人

アーティス
アーティス
創造性を最大限に発揮するとともに、インターネットに代表されるITを活用し、みんなの生活が便利で、豊かで、楽しいものになるようなサービスやコンテンツを考え、創り出し提供しています。
この記事のカテゴリ

FOLLOW US

最新の情報をお届けします