feat: complete API-driven projects with infrastructure and business acceptance
This commit is contained in:
60
transport/native/earlyReject_test.go
Normal file
60
transport/native/earlyReject_test.go
Normal file
@@ -0,0 +1,60 @@
|
||||
package native_test
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"context"
|
||||
"fmt"
|
||||
"io"
|
||||
"net"
|
||||
"net/http"
|
||||
"sync/atomic"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"git.yuntuo.hk/k2safe/go-omni/transport/native"
|
||||
)
|
||||
|
||||
func TestApplicationRejectsUnreadBodyPromptly(t *testing.T) {
|
||||
for _, framing := range []string{"Content-Length: 1048577\r\n", "Transfer-Encoding: chunked\r\n"} {
|
||||
t.Run(framing[:8], func(t *testing.T) {
|
||||
var calls atomic.Int32
|
||||
server := native.New(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
calls.Add(1)
|
||||
w.Header().Set("Content-Type", "application/json")
|
||||
w.WriteHeader(http.StatusRequestEntityTooLarge)
|
||||
_, _ = io.WriteString(w, `{"code":"BODY_TOO_LARGE"}`)
|
||||
}))
|
||||
// Much longer than the client's deadline: a draining implementation fails.
|
||||
server.ReadTimeout = 10 * time.Second
|
||||
listener, err := net.Listen("tcp", "127.0.0.1:0")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
done := make(chan error, 1)
|
||||
go func() { done <- server.Serve(context.Background(), listener) }()
|
||||
defer stopServer(t, server, listener, done)
|
||||
conn, err := net.DialTimeout("tcp", listener.Addr().String(), time.Second)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
defer conn.Close()
|
||||
_ = conn.SetDeadline(time.Now().Add(time.Second))
|
||||
if _, err = fmt.Fprintf(conn, "POST /upload HTTP/1.1\r\nHost: local\r\n%s\r\n", framing); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
response, err := http.ReadResponse(bufio.NewReader(conn), &http.Request{Method: "POST"})
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
body, err := io.ReadAll(response.Body)
|
||||
_ = response.Body.Close()
|
||||
if err != nil || response.StatusCode != 413 || !response.Close || string(body) != `{"code":"BODY_TOO_LARGE"}` {
|
||||
t.Fatalf("%+v %s %v", response, body, err)
|
||||
}
|
||||
requirePeerClose(t, conn)
|
||||
if calls.Load() != 1 {
|
||||
t.Fatalf("handler calls=%d", calls.Load())
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -481,7 +481,17 @@ func (s *Server) serveConnection(baseCtx context.Context, state *connection) {
|
||||
disconnectWatch = newClientDisconnectWatch(reader, state.conn, cancel)
|
||||
}
|
||||
handler.ServeHTTP(writer, request)
|
||||
bodyErr := request.Body.Close()
|
||||
var bodyErr error
|
||||
if body, ok := request.Body.(*requestBody); ok && !body.done && body.err == nil && writer.status == http.StatusRequestEntityTooLarge {
|
||||
// An application can reject size from the headers alone when its
|
||||
// body limit is below the transport limit. Waiting to
|
||||
// drain a slow or absent body would withhold the rejection response.
|
||||
// Abandon it and close; unread bytes can never become a next request.
|
||||
body.closed = true
|
||||
closeAfter = true
|
||||
} else {
|
||||
bodyErr = request.Body.Close()
|
||||
}
|
||||
if disconnectWatch != nil {
|
||||
disconnectWatch.stop()
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user