Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Go's http.MaxBytesReader, why pass in writer?

Tags:

http

go

Intuitively, I would think that when you create a MaxByteReader and pass in the http.ResponseWriter, it would write out the status code for you. But that isn't the case, what does the writer actually do?

example:

func maxBytesMiddleware(next http.Handler) http.Handler {
    return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
        r.Body = http.MaxBytesReader(w, r.Body, 1)

        next.ServeHTTP(w, r)
    })
}

func mainHandler(w http.ResponseWriter, r *http.Request) {
    var i interface{}
    err := json.NewDecoder(r.Body).Decode(&i)
    if err != nil {
        fmt.Println(err.Error())
    }
}

func TestMaxBytesMiddleware(t *testing.T) {
    handlerToTest := maxBytesMiddleware(http.HandlerFunc(mainHandler))

    req := httptest.NewRequest(http.MethodPost, "http://test.com", bytes.NewReader(json.RawMessage(`{"hello":"world"}`)))

    recorder := httptest.NewRecorder()
    handlerToTest.ServeHTTP(recorder, req)

    if recorder.Result().StatusCode != http.StatusRequestEntityTooLarge {
        t.Errorf("expected %d got %d", http.StatusRequestEntityTooLarge, recorder.Result().StatusCode)
    }
}

but when this test runs I get this:

http: request body too large
--- FAIL: TestMaxBytesMiddleware (0.00s)
    main_test.go:37: expected 413 got 200

if I want the desired functionality of what I thought this function did, I need to change my mainHandler to something like this:

func mainHandler(w http.ResponseWriter, r *http.Request) {
    var i interface{}
    err := json.NewDecoder(r.Body).Decode(&i)
    if err != nil {
        if err.Error() == "http: request body too large" {
            w.WriteHeader(http.StatusRequestEntityTooLarge)
            return
        }

        fmt.Println(err.Error())
    }
}

So what is that writer even there for?

like image 886
thestephenstanton Avatar asked Aug 12 '20 14:08

thestephenstanton


1 Answers

If the MaxBytesReader stops before reading the whole body, it sets some flags on the writer that make sure that the HTTP connection will be closed after the response is sent. Normally the server would be willing to read another request from the same connection (HTTP keepalive), but it can't do that if there are unread bits of the previous request still in the pipeline, so it has to close the connection, forcing the client to make a new connection if it wants to send more requests.

This is accomplished using the private requestTooLarge method of http.ResponseWriter.

like image 147
hobbs Avatar answered Nov 15 '22 09:11

hobbs