Files
quic-go/h2quic/server_test.go
2016-06-02 21:11:11 +02:00

304 lines
8.9 KiB
Go

package h2quic
import (
"net/http"
"os"
"sync"
"time"
"golang.org/x/net/http2"
"golang.org/x/net/http2/hpack"
"github.com/lucas-clemente/quic-go/protocol"
"github.com/lucas-clemente/quic-go/testdata"
"github.com/lucas-clemente/quic-go/utils"
. "github.com/onsi/ginkgo"
. "github.com/onsi/gomega"
)
type mockSession struct {
closed bool
dataStream *mockStream
}
func (s *mockSession) GetOrOpenStream(id protocol.StreamID) (utils.Stream, error) {
return s.dataStream, nil
}
func (s *mockSession) Close(error) error { s.closed = true; return nil }
var _ = Describe("H2 server", func() {
const port = "4826"
const addr = "127.0.0.1:" + port
var (
s *Server
session *mockSession
dataStream *mockStream
)
BeforeEach(func() {
s = &Server{
Server: &http.Server{
TLSConfig: testdata.GetTLSConfig(),
},
}
dataStream = &mockStream{}
session = &mockSession{dataStream: dataStream}
})
Context("handling requests", func() {
var (
h2framer *http2.Framer
hpackDecoder *hpack.Decoder
headerStream *mockStream
)
BeforeEach(func() {
headerStream = &mockStream{}
hpackDecoder = hpack.NewDecoder(4096, nil)
h2framer = http2.NewFramer(nil, headerStream)
})
It("handles a sample GET request", func() {
var handlerCalled bool
s.Handler = http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
Expect(r.Host).To(Equal("www.example.com"))
handlerCalled = true
})
headerStream.Write([]byte{
0x0, 0x0, 0x11, 0x1, 0x5, 0x0, 0x0, 0x0, 0x5,
// Taken from https://http2.github.io/http2-spec/compression.html#request.examples.with.huffman.coding
0x82, 0x86, 0x84, 0x41, 0x8c, 0xf1, 0xe3, 0xc2, 0xe5, 0xf2, 0x3a, 0x6b, 0xa0, 0xab, 0x90, 0xf4, 0xff,
})
err := s.handleRequest(session, headerStream, &sync.Mutex{}, hpackDecoder, h2framer)
Expect(err).NotTo(HaveOccurred())
Eventually(func() bool { return handlerCalled }).Should(BeTrue())
Expect(dataStream.remoteClosed).To(BeTrue())
})
It("does not close the dataStream when end of stream is not set", func() {
var handlerCalled bool
s.Handler = http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
Expect(r.Host).To(Equal("www.example.com"))
handlerCalled = true
})
headerStream.Write([]byte{
0x0, 0x0, 0x11, 0x1, 0x4, 0x0, 0x0, 0x0, 0x5,
// Taken from https://http2.github.io/http2-spec/compression.html#request.examples.with.huffman.coding
0x82, 0x86, 0x84, 0x41, 0x8c, 0xf1, 0xe3, 0xc2, 0xe5, 0xf2, 0x3a, 0x6b, 0xa0, 0xab, 0x90, 0xf4, 0xff,
})
err := s.handleRequest(session, headerStream, &sync.Mutex{}, hpackDecoder, h2framer)
Expect(err).NotTo(HaveOccurred())
Eventually(func() bool { return handlerCalled }).Should(BeTrue())
Expect(dataStream.remoteClosed).To(BeFalse())
})
})
It("handles the header stream", func() {
var handlerCalled bool
s.Handler = http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
Expect(r.Host).To(Equal("www.example.com"))
handlerCalled = true
})
headerStream := &mockStream{id: 3}
headerStream.Write([]byte{
0x0, 0x0, 0x11, 0x1, 0x4, 0x0, 0x0, 0x0, 0x5,
// Taken from https://http2.github.io/http2-spec/compression.html#request.examples.with.huffman.coding
0x82, 0x86, 0x84, 0x41, 0x8c, 0xf1, 0xe3, 0xc2, 0xe5, 0xf2, 0x3a, 0x6b, 0xa0, 0xab, 0x90, 0xf4, 0xff,
})
s.handleStream(session, headerStream)
Eventually(func() bool { return handlerCalled }).Should(BeTrue())
})
It("ignores other streams", func() {
var handlerCalled bool
s.Handler = http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
Expect(r.Host).To(Equal("www.example.com"))
handlerCalled = true
})
headerStream := &mockStream{id: 5}
headerStream.Write([]byte{
0x0, 0x0, 0x11, 0x1, 0x4, 0x0, 0x0, 0x0, 0x5,
// Taken from https://http2.github.io/http2-spec/compression.html#request.examples.with.huffman.coding
0x82, 0x86, 0x84, 0x41, 0x8c, 0xf1, 0xe3, 0xc2, 0xe5, 0xf2, 0x3a, 0x6b, 0xa0, 0xab, 0x90, 0xf4, 0xff,
})
s.handleStream(session, headerStream)
Consistently(func() bool { return handlerCalled }).Should(BeFalse())
})
It("supports closing after first request", func() {
s.CloseAfterFirstRequest = true
s.Handler = http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {})
headerStream := &mockStream{id: 3}
headerStream.Write([]byte{
0x0, 0x0, 0x11, 0x1, 0x4, 0x0, 0x0, 0x0, 0x5,
// Taken from https://http2.github.io/http2-spec/compression.html#request.examples.with.huffman.coding
0x82, 0x86, 0x84, 0x41, 0x8c, 0xf1, 0xe3, 0xc2, 0xe5, 0xf2, 0x3a, 0x6b, 0xa0, 0xab, 0x90, 0xf4, 0xff,
})
Expect(session.closed).To(BeFalse())
s.handleStream(session, headerStream)
Eventually(func() bool { return session.closed }).Should(BeTrue())
})
It("uses the default handler as fallback", func() {
var handlerCalled bool
http.Handle("/", http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
Expect(r.Host).To(Equal("www.example.com"))
handlerCalled = true
}))
headerStream := &mockStream{id: 3}
headerStream.Write([]byte{
0x0, 0x0, 0x11, 0x1, 0x4, 0x0, 0x0, 0x0, 0x5,
// Taken from https://http2.github.io/http2-spec/compression.html#request.examples.with.huffman.coding
0x82, 0x86, 0x84, 0x41, 0x8c, 0xf1, 0xe3, 0xc2, 0xe5, 0xf2, 0x3a, 0x6b, 0xa0, 0xab, 0x90, 0xf4, 0xff,
})
s.handleStream(session, headerStream)
Eventually(func() bool { return handlerCalled }).Should(BeTrue())
})
It("should panic when Serve() is called", func() {
Expect(func() {
Server{}.Serve(nil)
}).To(Panic())
})
Context("setting http headers", func() {
expected := http.Header{
"Alt-Svc": {`quic=":443"; ma=2592000; v="33,32,31,30"`},
"Alternate-Protocol": {`443:quic`},
}
It("sets proper headers with numeric port", func() {
s.Server.Addr = ":443"
hdr := http.Header{}
err := s.SetQuicHeaders(hdr)
Expect(err).NotTo(HaveOccurred())
Expect(hdr).To(Equal(expected))
})
It("sets proper headers with full addr", func() {
s.Server.Addr = "127.0.0.1:443"
hdr := http.Header{}
err := s.SetQuicHeaders(hdr)
Expect(err).NotTo(HaveOccurred())
Expect(hdr).To(Equal(expected))
})
It("sets proper headers with string port", func() {
s.Server.Addr = ":https"
hdr := http.Header{}
err := s.SetQuicHeaders(hdr)
Expect(err).NotTo(HaveOccurred())
Expect(hdr).To(Equal(expected))
})
It("works multiple times", func() {
s.Server.Addr = ":https"
hdr := http.Header{}
err := s.SetQuicHeaders(hdr)
Expect(err).NotTo(HaveOccurred())
Expect(hdr).To(Equal(expected))
hdr = http.Header{}
err = s.SetQuicHeaders(hdr)
Expect(err).NotTo(HaveOccurred())
Expect(hdr).To(Equal(expected))
})
})
It("should error when ListenAndServe is called with s.Server nil", func() {
err := (&Server{}).ListenAndServe()
Expect(err).To(MatchError("use of h2quic.Server without http.Server"))
})
It("should nop-Close() when s.server is nil", func() {
err := (&Server{}).Close()
Expect(err).NotTo(HaveOccurred())
})
Context("ListenAndServe", func() {
BeforeEach(func() {
s.Server.Addr = addr
})
AfterEach(func() {
time.Sleep(10 * time.Millisecond)
err := s.Close()
Expect(err).NotTo(HaveOccurred())
})
It("works", func(done Done) {
go func() {
defer GinkgoRecover()
err := s.ListenAndServe()
Expect(err).NotTo(HaveOccurred())
close(done)
}()
time.Sleep(10 * time.Millisecond)
err := s.Close()
Expect(err).NotTo(HaveOccurred())
}, 0.5)
It("may only be called once", func(done Done) {
go func() {
defer GinkgoRecover()
err := s.ListenAndServe()
Expect(err).NotTo(HaveOccurred())
close(done)
}()
time.Sleep(10 * time.Millisecond)
err := s.ListenAndServe()
Expect(err).To(MatchError("ListenAndServe may only be called once"))
err = s.Close()
Expect(err).NotTo(HaveOccurred())
}, 0.5)
})
Context("ListenAndServeTLS", func() {
path := os.Getenv("GOPATH")
path += "/src/github.com/lucas-clemente/quic-go/example/"
BeforeEach(func() {
s.Server.Addr = addr
})
AfterEach(func() {
time.Sleep(10 * time.Millisecond)
err := s.Close()
Expect(err).NotTo(HaveOccurred())
})
It("works", func(done Done) {
go func() {
defer GinkgoRecover()
err := s.ListenAndServeTLS(path+"fullchain.pem", path+"privkey.pem")
Expect(err).NotTo(HaveOccurred())
close(done)
}()
time.Sleep(10 * time.Millisecond)
err := s.Close()
Expect(err).NotTo(HaveOccurred())
}, 0.5)
It("may only be called once", func(done Done) {
go func() {
defer GinkgoRecover()
err := s.ListenAndServeTLS(path+"fullchain.pem", path+"privkey.pem")
Expect(err).NotTo(HaveOccurred())
close(done)
}()
time.Sleep(10 * time.Millisecond)
err := s.ListenAndServeTLS(path+"fullchain.pem", path+"privkey.pem")
Expect(err).To(MatchError("ListenAndServe may only be called once"))
err = s.Close()
Expect(err).NotTo(HaveOccurred())
}, 0.5)
})
It("closes gracefully", func() {
err := s.CloseGracefully(0)
Expect(err).NotTo(HaveOccurred())
})
})