Merge pull request #2949 from lucas-clemente/http3-control-streams

implement HTTP/3 control stream handling
This commit is contained in:
Marten Seemann 2020-12-23 14:58:51 +07:00 committed by GitHub
commit f68dfd5c3b
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
6 changed files with 444 additions and 53 deletions

View file

@ -2,6 +2,7 @@ package http3
import (
"bytes"
"context"
"crypto/tls"
"errors"
"fmt"
@ -109,7 +110,7 @@ func (c *client) dial() error {
return err
}
// run the sesssion setup using 0-RTT data
// send the SETTINGs frame, using 0-RTT data, if possible
go func() {
if err := c.setupSession(); err != nil {
c.logger.Debugf("Setting up session failed: %s", err)
@ -117,6 +118,7 @@ func (c *client) dial() error {
}
}()
go c.handleUnidirectionalStreams()
return nil
}
@ -127,15 +129,47 @@ func (c *client) setupSession() error {
return err
}
buf := &bytes.Buffer{}
// write the type byte
buf.Write([]byte{0x0})
utils.WriteVarInt(buf, streamTypeControlStream)
// send the SETTINGS frame
(&settingsFrame{}).Write(buf)
if _, err := str.Write(buf.Bytes()); err != nil {
return err
}
_, err = str.Write(buf.Bytes())
return err
}
return nil
func (c *client) handleUnidirectionalStreams() {
for {
str, err := c.session.AcceptUniStream(context.Background())
if err != nil {
c.logger.Debugf("accepting unidirectional stream failed: %s", err)
return
}
go func() {
streamType, err := utils.ReadVarInt(&byteReaderImpl{str})
if err != nil {
c.logger.Debugf("reading stream type on stream %d failed: %s", str.StreamID(), err)
return
}
// We're only interested in the control stream here.
switch streamType {
case streamTypeControlStream:
case streamTypePushStream:
// We never increased the Push ID, so we don't expect any push streams.
c.session.CloseWithError(quic.ErrorCode(errorIDError), "")
return
default:
return
}
f, err := parseNextFrame(str)
if err != nil {
c.session.CloseWithError(quic.ErrorCode(errorFrameError), "")
return
}
if _, ok := f.(*settingsFrame); !ok {
c.session.CloseWithError(quic.ErrorCode(errorMissingSettings), "")
}
}()
}
}
func (c *client) Close() error {

View file

@ -147,35 +147,185 @@ var _ = Describe("Client", func() {
Expect(err).To(MatchError(testErr))
})
It("errors if it can't open a stream", func() {
testErr := errors.New("stream open error")
client, err := newClient("localhost:1337", nil, &roundTripperOpts{}, nil, nil)
Expect(err).ToNot(HaveOccurred())
session := mockquic.NewMockEarlySession(mockCtrl)
session.EXPECT().OpenUniStream().Return(nil, testErr).MaxTimes(1)
session.EXPECT().HandshakeComplete().Return(handshakeCtx).MaxTimes(1)
session.EXPECT().OpenStreamSync(context.Background()).Return(nil, testErr).MaxTimes(1)
session.EXPECT().CloseWithError(gomock.Any(), gomock.Any()).MaxTimes(1)
dialAddr = func(hostname string, _ *tls.Config, _ *quic.Config) (quic.EarlySession, error) {
return session, nil
}
defer GinkgoRecover()
_, err = client.RoundTrip(req)
Expect(err).To(MatchError(testErr))
})
It("closes correctly if session was not created", func() {
client, err := newClient("localhost:1337", nil, &roundTripperOpts{}, nil, nil)
Expect(err).ToNot(HaveOccurred())
Expect(client.Close()).To(Succeed())
})
Context("validating the address", func() {
It("refuses to do requests for the wrong host", func() {
req, err := http.NewRequest("https", "https://quic.clemente.io:1336/foobar.html", nil)
Expect(err).ToNot(HaveOccurred())
_, err = client.RoundTrip(req)
Expect(err).To(MatchError("http3 client BUG: RoundTrip called for the wrong client (expected quic.clemente.io:1337, got quic.clemente.io:1336)"))
})
It("refuses to do plain HTTP requests", func() {
req, err := http.NewRequest("https", "http://quic.clemente.io:1337/foobar.html", nil)
Expect(err).ToNot(HaveOccurred())
_, err = client.RoundTrip(req)
Expect(err).To(MatchError("http3: unsupported scheme"))
})
})
Context("control stream handling", func() {
var (
request *http.Request
sess *mockquic.MockEarlySession
settingsFrameWritten chan struct{}
)
testDone := make(chan struct{})
BeforeEach(func() {
settingsFrameWritten = make(chan struct{})
controlStr := mockquic.NewMockStream(mockCtrl)
controlStr.EXPECT().Write(gomock.Any()).Do(func(b []byte) {
defer GinkgoRecover()
close(settingsFrameWritten)
})
sess = mockquic.NewMockEarlySession(mockCtrl)
sess.EXPECT().OpenUniStream().Return(controlStr, nil)
sess.EXPECT().HandshakeComplete().Return(handshakeCtx)
sess.EXPECT().OpenStreamSync(gomock.Any()).Return(nil, errors.New("done"))
dialAddr = func(hostname string, _ *tls.Config, _ *quic.Config) (quic.EarlySession, error) { return sess, nil }
var err error
request, err = http.NewRequest("GET", "https://quic.clemente.io:1337/file1.dat", nil)
Expect(err).ToNot(HaveOccurred())
})
AfterEach(func() {
testDone <- struct{}{}
Eventually(settingsFrameWritten).Should(BeClosed())
})
It("parses the SETTINGS frame", func() {
buf := &bytes.Buffer{}
utils.WriteVarInt(buf, streamTypeControlStream)
(&settingsFrame{}).Write(buf)
controlStr := mockquic.NewMockStream(mockCtrl)
controlStr.EXPECT().Read(gomock.Any()).DoAndReturn(buf.Read).AnyTimes()
sess.EXPECT().AcceptUniStream(gomock.Any()).DoAndReturn(func(context.Context) (quic.ReceiveStream, error) {
return controlStr, nil
})
sess.EXPECT().AcceptUniStream(gomock.Any()).DoAndReturn(func(context.Context) (quic.ReceiveStream, error) {
<-testDone
return nil, errors.New("test done")
})
_, err := client.RoundTrip(request)
Expect(err).To(MatchError("done"))
time.Sleep(scaleDuration(20 * time.Millisecond)) // don't EXPECT any calls to sess.CloseWithError
})
It("ignores streams other than the control stream", func() {
controlBuf := &bytes.Buffer{}
utils.WriteVarInt(controlBuf, streamTypeControlStream)
(&settingsFrame{}).Write(controlBuf)
controlStr := mockquic.NewMockStream(mockCtrl)
controlStr.EXPECT().Read(gomock.Any()).DoAndReturn(controlBuf.Read).AnyTimes()
otherBuf := &bytes.Buffer{}
utils.WriteVarInt(otherBuf, 1337)
otherStr := mockquic.NewMockStream(mockCtrl)
otherStr.EXPECT().Read(gomock.Any()).DoAndReturn(otherBuf.Read).AnyTimes()
sess.EXPECT().AcceptUniStream(gomock.Any()).DoAndReturn(func(context.Context) (quic.ReceiveStream, error) {
return otherStr, nil
})
sess.EXPECT().AcceptUniStream(gomock.Any()).DoAndReturn(func(context.Context) (quic.ReceiveStream, error) {
return controlStr, nil
})
sess.EXPECT().AcceptUniStream(gomock.Any()).DoAndReturn(func(context.Context) (quic.ReceiveStream, error) {
<-testDone
return nil, errors.New("test done")
})
_, err := client.RoundTrip(request)
Expect(err).To(MatchError("done"))
time.Sleep(scaleDuration(20 * time.Millisecond)) // don't EXPECT any calls to sess.CloseWithError
})
It("errors when the first frame on the control stream is not a SETTINGS frame", func() {
buf := &bytes.Buffer{}
utils.WriteVarInt(buf, streamTypeControlStream)
(&dataFrame{}).Write(buf)
controlStr := mockquic.NewMockStream(mockCtrl)
controlStr.EXPECT().Read(gomock.Any()).DoAndReturn(buf.Read).AnyTimes()
sess.EXPECT().AcceptUniStream(gomock.Any()).DoAndReturn(func(context.Context) (quic.ReceiveStream, error) {
return controlStr, nil
})
sess.EXPECT().AcceptUniStream(gomock.Any()).DoAndReturn(func(context.Context) (quic.ReceiveStream, error) {
<-testDone
return nil, errors.New("test done")
})
done := make(chan struct{})
sess.EXPECT().CloseWithError(gomock.Any(), gomock.Any()).Do(func(code quic.ErrorCode, _ string) {
defer GinkgoRecover()
Expect(code).To(BeEquivalentTo(errorMissingSettings))
close(done)
})
_, err := client.RoundTrip(request)
Expect(err).To(MatchError("done"))
Eventually(done).Should(BeClosed())
})
It("errors when parsing the frame on the control stream fails", func() {
buf := &bytes.Buffer{}
utils.WriteVarInt(buf, streamTypeControlStream)
b := &bytes.Buffer{}
(&settingsFrame{}).Write(b)
buf.Write(b.Bytes()[:b.Len()-1])
controlStr := mockquic.NewMockStream(mockCtrl)
controlStr.EXPECT().Read(gomock.Any()).DoAndReturn(buf.Read).AnyTimes()
sess.EXPECT().AcceptUniStream(gomock.Any()).DoAndReturn(func(context.Context) (quic.ReceiveStream, error) {
return controlStr, nil
})
sess.EXPECT().AcceptUniStream(gomock.Any()).DoAndReturn(func(context.Context) (quic.ReceiveStream, error) {
<-testDone
return nil, errors.New("test done")
})
done := make(chan struct{})
sess.EXPECT().CloseWithError(gomock.Any(), gomock.Any()).Do(func(code quic.ErrorCode, _ string) {
defer GinkgoRecover()
Expect(code).To(BeEquivalentTo(errorFrameError))
close(done)
})
_, err := client.RoundTrip(request)
Expect(err).To(MatchError("done"))
Eventually(done).Should(BeClosed())
})
It("errors when parsing the server opens a push stream", func() {
buf := &bytes.Buffer{}
utils.WriteVarInt(buf, streamTypePushStream)
controlStr := mockquic.NewMockStream(mockCtrl)
controlStr.EXPECT().Read(gomock.Any()).DoAndReturn(buf.Read).AnyTimes()
sess.EXPECT().AcceptUniStream(gomock.Any()).DoAndReturn(func(context.Context) (quic.ReceiveStream, error) {
return controlStr, nil
})
sess.EXPECT().AcceptUniStream(gomock.Any()).DoAndReturn(func(context.Context) (quic.ReceiveStream, error) {
<-testDone
return nil, errors.New("test done")
})
done := make(chan struct{})
sess.EXPECT().CloseWithError(gomock.Any(), gomock.Any()).Do(func(code quic.ErrorCode, _ string) {
defer GinkgoRecover()
Expect(code).To(BeEquivalentTo(errorIDError))
close(done)
})
_, err := client.RoundTrip(request)
Expect(err).To(MatchError("done"))
Eventually(done).Should(BeClosed())
})
})
Context("Doing requests", func() {
var (
request *http.Request
str *mockquic.MockStream
sess *mockquic.MockEarlySession
request *http.Request
str *mockquic.MockStream
sess *mockquic.MockEarlySession
settingsFrameWritten chan struct{}
)
testDone := make(chan struct{})
decodeHeader := func(str io.Reader) map[string]string {
fields := make(map[string]string)
@ -197,20 +347,43 @@ var _ = Describe("Client", func() {
}
BeforeEach(func() {
settingsFrameWritten = make(chan struct{})
controlStr := mockquic.NewMockStream(mockCtrl)
controlStr.EXPECT().Write([]byte{0x0}).Return(1, nil).MaxTimes(1)
controlStr.EXPECT().Write(gomock.Any()).MaxTimes(1) // SETTINGS frame
controlStr.EXPECT().Write(gomock.Any()).Do(func(b []byte) {
defer GinkgoRecover()
r := bytes.NewReader(b)
streamType, err := utils.ReadVarInt(r)
Expect(err).ToNot(HaveOccurred())
Expect(streamType).To(BeEquivalentTo(streamTypeControlStream))
close(settingsFrameWritten)
}) // SETTINGS frame
str = mockquic.NewMockStream(mockCtrl)
sess = mockquic.NewMockEarlySession(mockCtrl)
sess.EXPECT().OpenUniStream().Return(controlStr, nil).MaxTimes(1)
dialAddr = func(hostname string, _ *tls.Config, _ *quic.Config) (quic.EarlySession, error) {
return sess, nil
}
sess.EXPECT().OpenUniStream().Return(controlStr, nil)
sess.EXPECT().AcceptUniStream(gomock.Any()).DoAndReturn(func(context.Context) (quic.ReceiveStream, error) {
<-testDone
return nil, errors.New("test done")
})
dialAddr = func(hostname string, _ *tls.Config, _ *quic.Config) (quic.EarlySession, error) { return sess, nil }
var err error
request, err = http.NewRequest("GET", "https://quic.clemente.io:1337/file1.dat", nil)
Expect(err).ToNot(HaveOccurred())
})
AfterEach(func() {
testDone <- struct{}{}
Eventually(settingsFrameWritten).Should(BeClosed())
})
It("errors if it can't open a stream", func() {
testErr := errors.New("stream open error")
sess.EXPECT().OpenStreamSync(context.Background()).Return(nil, testErr)
sess.EXPECT().CloseWithError(gomock.Any(), gomock.Any()).MaxTimes(1)
sess.EXPECT().HandshakeComplete().Return(handshakeCtx)
_, err := client.RoundTrip(request)
Expect(err).To(MatchError(testErr))
})
It("performs a 0-RTT request", func() {
testErr := errors.New("stream open error")
request.Method = MethodGet0RTT
@ -251,22 +424,6 @@ var _ = Describe("Client", func() {
Expect(rsp.StatusCode).To(Equal(418))
})
Context("validating the address", func() {
It("refuses to do requests for the wrong host", func() {
req, err := http.NewRequest("https", "https://quic.clemente.io:1336/foobar.html", nil)
Expect(err).ToNot(HaveOccurred())
_, err = client.RoundTrip(req)
Expect(err).To(MatchError("http3 client BUG: RoundTrip called for the wrong client (expected quic.clemente.io:1337, got quic.clemente.io:1336)"))
})
It("refuses to do plain HTTP requests", func() {
req, err := http.NewRequest("https", "http://quic.clemente.io:1337/foobar.html", nil)
Expect(err).ToNot(HaveOccurred())
_, err = client.RoundTrip(req)
Expect(err).To(MatchError("http3: unsupported scheme"))
})
})
Context("requests containing a Body", func() {
var strBuf *bytes.Buffer

View file

@ -1,7 +1,10 @@
package http3
import (
"os"
"strconv"
"testing"
"time"
"github.com/golang/mock/gomock"
@ -23,3 +26,13 @@ var _ = BeforeEach(func() {
var _ = AfterEach(func() {
mockCtrl.Finish()
})
//nolint:unparam
func scaleDuration(t time.Duration) time.Duration {
scaleFactor := 1
if f, err := strconv.Atoi(os.Getenv("TIMESCALE_FACTOR")); err == nil { // parsing "" errors, so this works fine if the env is not set
scaleFactor = f
}
Expect(scaleFactor).ToNot(BeZero())
return time.Duration(scaleFactor) * t
}

View file

@ -101,6 +101,10 @@ var _ = Describe("RoundTripper", func() {
session.EXPECT().OpenUniStream().AnyTimes().Return(nil, testErr)
session.EXPECT().HandshakeComplete().Return(handshakeCtx)
session.EXPECT().OpenStreamSync(context.Background()).Return(nil, testErr)
session.EXPECT().AcceptUniStream(gomock.Any()).DoAndReturn(func(context.Context) (quic.ReceiveStream, error) {
<-closed
return nil, errors.New("test done")
}).MaxTimes(1)
session.EXPECT().CloseWithError(gomock.Any(), gomock.Any()).Do(func(quic.ErrorCode, string) { close(closed) })
_, err = rt.RoundTrip(req)
Expect(err).To(MatchError(testErr))
@ -139,6 +143,10 @@ var _ = Describe("RoundTripper", func() {
session.EXPECT().OpenUniStream().AnyTimes().Return(nil, testErr)
session.EXPECT().HandshakeComplete().Return(handshakeCtx).Times(2)
session.EXPECT().OpenStreamSync(context.Background()).Return(nil, testErr).Times(2)
session.EXPECT().AcceptUniStream(gomock.Any()).DoAndReturn(func(context.Context) (quic.ReceiveStream, error) {
<-closed
return nil, errors.New("test done")
}).MaxTimes(1)
session.EXPECT().CloseWithError(gomock.Any(), gomock.Any()).Do(func(quic.ErrorCode, string) { close(closed) })
req, err := http.NewRequest("GET", "https://quic.clemente.io/file1.html", nil)
Expect(err).ToNot(HaveOccurred())

View file

@ -37,8 +37,10 @@ var (
)
const (
nextProtoH3Draft29 = "h3-29"
nextProtoH3Draft32 = "h3-32"
nextProtoH3Draft29 = "h3-29"
nextProtoH3Draft32 = "h3-32"
streamTypeControlStream = 0
streamTypePushStream = 1
)
func versionToALPN(v protocol.VersionNumber) string {
@ -219,10 +221,13 @@ func (s *Server) handleConn(sess quic.EarlySession) {
s.logger.Debugf("Opening the control stream failed.")
return
}
buf := bytes.NewBuffer([]byte{0})
buf := &bytes.Buffer{}
utils.WriteVarInt(buf, streamTypeControlStream) // stream type
(&settingsFrame{}).Write(buf)
str.Write(buf.Bytes())
go s.handleUnidirectionalStreams(sess)
// Process all requests immediately.
// It's the client's responsibility to decide which requests are eligible for 0-RTT.
for {
@ -254,6 +259,41 @@ func (s *Server) handleConn(sess quic.EarlySession) {
}
}
func (s *Server) handleUnidirectionalStreams(sess quic.EarlySession) {
for {
str, err := sess.AcceptUniStream(context.Background())
if err != nil {
s.logger.Debugf("accepting unidirectional stream failed: %s", err)
return
}
go func(str quic.ReceiveStream) {
streamType, err := utils.ReadVarInt(&byteReaderImpl{str})
if err != nil {
s.logger.Debugf("reading stream type on stream %d failed: %s", str.StreamID(), err)
return
}
// We're only interested in the control stream here.
switch streamType {
case streamTypeControlStream:
case streamTypePushStream: // only the server can push
sess.CloseWithError(quic.ErrorCode(errorStreamCreationError), "")
return
default:
return
}
f, err := parseNextFrame(str)
if err != nil {
sess.CloseWithError(quic.ErrorCode(errorFrameError), "")
return
}
if _, ok := f.(*settingsFrame); !ok {
sess.CloseWithError(quic.ErrorCode(errorMissingSettings), "")
}
}(str)
}
}
func (s *Server) maxHeaderBytes() uint64 {
if s.Server.MaxHeaderBytes <= 0 {
return http.DefaultMaxHeaderBytes

View file

@ -175,21 +175,160 @@ var _ = Describe("Server", func() {
Expect(hfs).To(HaveKeyWithValue(":status", []string{"500"}))
})
Context("stream- and connection-level errors", func() {
Context("control stream handling", func() {
var sess *mockquic.MockEarlySession
testDone := make(chan struct{})
BeforeEach(func() {
sess = mockquic.NewMockEarlySession(mockCtrl)
controlStr := mockquic.NewMockStream(mockCtrl)
controlStr.EXPECT().Write(gomock.Any())
sess.EXPECT().OpenUniStream().Return(controlStr, nil)
sess.EXPECT().AcceptStream(gomock.Any()).Return(nil, errors.New("done"))
sess.EXPECT().RemoteAddr().Return(&net.UDPAddr{IP: net.IPv4(127, 0, 0, 1), Port: 1337}).AnyTimes()
sess.EXPECT().LocalAddr().AnyTimes()
})
AfterEach(func() { testDone <- struct{}{} })
It("parses the SETTINGS frame", func() {
buf := &bytes.Buffer{}
utils.WriteVarInt(buf, streamTypeControlStream)
(&settingsFrame{}).Write(buf)
controlStr := mockquic.NewMockStream(mockCtrl)
controlStr.EXPECT().Read(gomock.Any()).DoAndReturn(buf.Read).AnyTimes()
sess.EXPECT().AcceptUniStream(gomock.Any()).DoAndReturn(func(context.Context) (quic.ReceiveStream, error) {
return controlStr, nil
})
sess.EXPECT().AcceptUniStream(gomock.Any()).DoAndReturn(func(context.Context) (quic.ReceiveStream, error) {
<-testDone
return nil, errors.New("test done")
})
s.handleConn(sess)
time.Sleep(scaleDuration(20 * time.Millisecond)) // don't EXPECT any calls to sess.CloseWithError
})
It("ignores streams other than the control stream", func() {
controlBuf := &bytes.Buffer{}
utils.WriteVarInt(controlBuf, streamTypeControlStream)
(&settingsFrame{}).Write(controlBuf)
controlStr := mockquic.NewMockStream(mockCtrl)
controlStr.EXPECT().Read(gomock.Any()).DoAndReturn(controlBuf.Read).AnyTimes()
otherBuf := &bytes.Buffer{}
utils.WriteVarInt(otherBuf, 1337)
otherStr := mockquic.NewMockStream(mockCtrl)
otherStr.EXPECT().Read(gomock.Any()).DoAndReturn(otherBuf.Read).AnyTimes()
sess.EXPECT().AcceptUniStream(gomock.Any()).DoAndReturn(func(context.Context) (quic.ReceiveStream, error) {
return otherStr, nil
})
sess.EXPECT().AcceptUniStream(gomock.Any()).DoAndReturn(func(context.Context) (quic.ReceiveStream, error) {
return controlStr, nil
})
sess.EXPECT().AcceptUniStream(gomock.Any()).DoAndReturn(func(context.Context) (quic.ReceiveStream, error) {
<-testDone
return nil, errors.New("test done")
})
s.handleConn(sess)
time.Sleep(scaleDuration(20 * time.Millisecond)) // don't EXPECT any calls to sess.CloseWithError
})
It("errors when the first frame on the control stream is not a SETTINGS frame", func() {
buf := &bytes.Buffer{}
utils.WriteVarInt(buf, streamTypeControlStream)
(&dataFrame{}).Write(buf)
controlStr := mockquic.NewMockStream(mockCtrl)
controlStr.EXPECT().Read(gomock.Any()).DoAndReturn(buf.Read).AnyTimes()
sess.EXPECT().AcceptUniStream(gomock.Any()).DoAndReturn(func(context.Context) (quic.ReceiveStream, error) {
return controlStr, nil
})
sess.EXPECT().AcceptUniStream(gomock.Any()).DoAndReturn(func(context.Context) (quic.ReceiveStream, error) {
<-testDone
return nil, errors.New("test done")
})
done := make(chan struct{})
sess.EXPECT().CloseWithError(gomock.Any(), gomock.Any()).Do(func(code quic.ErrorCode, _ string) {
defer GinkgoRecover()
Expect(code).To(BeEquivalentTo(errorMissingSettings))
close(done)
})
s.handleConn(sess)
Eventually(done).Should(BeClosed())
})
It("errors when parsing the frame on the control stream fails", func() {
buf := &bytes.Buffer{}
utils.WriteVarInt(buf, streamTypeControlStream)
b := &bytes.Buffer{}
(&settingsFrame{}).Write(b)
buf.Write(b.Bytes()[:b.Len()-1])
controlStr := mockquic.NewMockStream(mockCtrl)
controlStr.EXPECT().Read(gomock.Any()).DoAndReturn(buf.Read).AnyTimes()
sess.EXPECT().AcceptUniStream(gomock.Any()).DoAndReturn(func(context.Context) (quic.ReceiveStream, error) {
return controlStr, nil
})
sess.EXPECT().AcceptUniStream(gomock.Any()).DoAndReturn(func(context.Context) (quic.ReceiveStream, error) {
<-testDone
return nil, errors.New("test done")
})
done := make(chan struct{})
sess.EXPECT().CloseWithError(gomock.Any(), gomock.Any()).Do(func(code quic.ErrorCode, _ string) {
defer GinkgoRecover()
Expect(code).To(BeEquivalentTo(errorFrameError))
close(done)
})
s.handleConn(sess)
Eventually(done).Should(BeClosed())
})
It("errors when the client opens a push stream", func() {
buf := &bytes.Buffer{}
utils.WriteVarInt(buf, streamTypePushStream)
(&dataFrame{}).Write(buf)
controlStr := mockquic.NewMockStream(mockCtrl)
controlStr.EXPECT().Read(gomock.Any()).DoAndReturn(buf.Read).AnyTimes()
sess.EXPECT().AcceptUniStream(gomock.Any()).DoAndReturn(func(context.Context) (quic.ReceiveStream, error) {
return controlStr, nil
})
sess.EXPECT().AcceptUniStream(gomock.Any()).DoAndReturn(func(context.Context) (quic.ReceiveStream, error) {
<-testDone
return nil, errors.New("test done")
})
done := make(chan struct{})
sess.EXPECT().CloseWithError(gomock.Any(), gomock.Any()).Do(func(code quic.ErrorCode, _ string) {
defer GinkgoRecover()
Expect(code).To(BeEquivalentTo(errorStreamCreationError))
close(done)
})
s.handleConn(sess)
Eventually(done).Should(BeClosed())
})
})
Context("stream- and connection-level errors", func() {
var sess *mockquic.MockEarlySession
testDone := make(chan struct{})
BeforeEach(func() {
testDone = make(chan struct{})
addr := &net.UDPAddr{IP: net.IPv4(127, 0, 0, 1), Port: 1337}
sess = mockquic.NewMockEarlySession(mockCtrl)
controlStr := mockquic.NewMockStream(mockCtrl)
controlStr.EXPECT().Write(gomock.Any())
sess.EXPECT().OpenUniStream().Return(controlStr, nil)
sess.EXPECT().AcceptUniStream(gomock.Any()).DoAndReturn(func(context.Context) (quic.ReceiveStream, error) {
<-testDone
return nil, errors.New("test done")
})
sess.EXPECT().AcceptStream(gomock.Any()).Return(str, nil)
sess.EXPECT().AcceptStream(gomock.Any()).Return(nil, errors.New("done"))
sess.EXPECT().RemoteAddr().Return(addr).AnyTimes()
sess.EXPECT().LocalAddr().AnyTimes()
})
AfterEach(func() { testDone <- struct{}{} })
It("cancels reading when client sends a body in GET request", func() {
handlerCalled := make(chan struct{})
s.Handler = http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {