105 lines
3.0 KiB
Go
105 lines
3.0 KiB
Go
package native
|
|
|
|
import (
|
|
"bufio"
|
|
"context"
|
|
"errors"
|
|
"io"
|
|
"net"
|
|
"net/http"
|
|
"strings"
|
|
"testing"
|
|
"time"
|
|
)
|
|
|
|
type observedReadConn struct {
|
|
net.Conn
|
|
reading chan struct{}
|
|
}
|
|
|
|
func (c *observedReadConn) Read(data []byte) (int, error) {
|
|
select {
|
|
case c.reading <- struct{}{}:
|
|
default:
|
|
}
|
|
return c.Conn.Read(data)
|
|
}
|
|
|
|
func TestDisconnectWatchStopsBeforeReaderReuse(t *testing.T) {
|
|
server, client := net.Pipe()
|
|
defer server.Close()
|
|
defer client.Close()
|
|
conn := &observedReadConn{Conn: server, reading: make(chan struct{}, 1)}
|
|
reader := bufio.NewReader(conn)
|
|
ctx, cancel := context.WithCancel(context.Background())
|
|
defer cancel()
|
|
watch := newClientDisconnectWatch(reader, conn, cancel)
|
|
select {
|
|
case <-conn.reading:
|
|
case <-time.After(time.Second):
|
|
t.Fatal("disconnect watcher did not start reading")
|
|
}
|
|
watch.stop()
|
|
if ctx.Err() != nil {
|
|
t.Fatalf("interrupting the watcher canceled a healthy request: %v", ctx.Err())
|
|
}
|
|
if err := server.SetReadDeadline(time.Now().Add(time.Second)); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
writeDone := make(chan error, 1)
|
|
go func() {
|
|
_, err := io.WriteString(client, "GET /next HTTP/1.1\r\n")
|
|
writeDone <- err
|
|
}()
|
|
line, err := reader.ReadString('\n')
|
|
if err != nil || line != "GET /next HTTP/1.1\r\n" {
|
|
t.Fatalf("reader retained watcher data or deadline: line=%q err=%v", line, err)
|
|
}
|
|
if err := <-writeDone; err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
}
|
|
|
|
func TestDisconnectWatchPreservesPipelineAndWriteFailureCancels(t *testing.T) {
|
|
server, client := net.Pipe()
|
|
defer server.Close()
|
|
defer client.Close()
|
|
pipeline := "GET /next HTTP/1.1\r\nHost: test\r\n\r\n"
|
|
reader := bufio.NewReader(strings.NewReader(pipeline))
|
|
ctx, cancel := context.WithCancel(context.Background())
|
|
defer cancel()
|
|
watch := newClientDisconnectWatch(reader, server, cancel)
|
|
select {
|
|
case <-watch.done:
|
|
case <-time.After(time.Second):
|
|
t.Fatal("watcher should stop when pipelined data is available")
|
|
}
|
|
watch.stop()
|
|
if ctx.Err() != nil {
|
|
t.Fatal("pipeline data canceled the current request")
|
|
}
|
|
remaining, err := io.ReadAll(reader)
|
|
if err != nil || string(remaining) != pipeline {
|
|
t.Fatalf("pipeline bytes changed: data=%q err=%v", remaining, err)
|
|
}
|
|
// With pending pipeline data, EOF detection is deferred. A failed stream
|
|
// write must still cancel downstream work even when the handler ignores it.
|
|
client.Close()
|
|
writer := newResponseWriter(http.MethodGet)
|
|
writer.configure(&http.Request{ProtoMajor: 1, ProtoMinor: 1}, server, false, time.Second, cancel)
|
|
writer.Flush()
|
|
if !errors.Is(ctx.Err(), context.Canceled) {
|
|
t.Fatal("failed Flush did not cancel request context")
|
|
}
|
|
if _, err := writer.Write([]byte("more")); err == nil {
|
|
t.Fatal("failed Flush was not returned by subsequent Write")
|
|
}
|
|
if err := writer.writeTo(server, false, time.Second); err == nil {
|
|
t.Fatal("failed Flush was not returned on response completion")
|
|
}
|
|
writer.reset("")
|
|
if writer.cancel != nil || writer.writeErr != nil || writer.conn != nil {
|
|
t.Fatal("pooled response retained the previous request")
|
|
}
|
|
}
|