From 0781e1b1b0e7ea4f70a95142e0b01aa6f9388f9d Mon Sep 17 00:00:00 2001 From: Lucas Clemente Date: Tue, 3 May 2016 14:03:35 +0200 Subject: [PATCH] add h2quic package with response writer --- h2quic/h2quic_suite_test.go | 13 ++++++ h2quic/response_writer.go | 78 ++++++++++++++++++++++++++++++++++ h2quic/response_writer_test.go | 44 +++++++++++++++++++ 3 files changed, 135 insertions(+) create mode 100644 h2quic/h2quic_suite_test.go create mode 100644 h2quic/response_writer.go create mode 100644 h2quic/response_writer_test.go diff --git a/h2quic/h2quic_suite_test.go b/h2quic/h2quic_suite_test.go new file mode 100644 index 000000000..f0b265524 --- /dev/null +++ b/h2quic/h2quic_suite_test.go @@ -0,0 +1,13 @@ +package h2quic + +import ( + . "github.com/onsi/ginkgo" + . "github.com/onsi/gomega" + + "testing" +) + +func TestH2quic(t *testing.T) { + RegisterFailHandler(Fail) + RunSpecs(t, "H2quic Suite") +} diff --git a/h2quic/response_writer.go b/h2quic/response_writer.go new file mode 100644 index 000000000..fe78c3d07 --- /dev/null +++ b/h2quic/response_writer.go @@ -0,0 +1,78 @@ +package h2quic + +import ( + "bytes" + "fmt" + "net/http" + "strconv" + + "github.com/lucas-clemente/quic-go" + "github.com/lucas-clemente/quic-go/protocol" + "github.com/lucas-clemente/quic-go/utils" + "golang.org/x/net/http2" + "golang.org/x/net/http2/hpack" +) + +type responseWriter struct { + session *quic.Session + dataStreamID protocol.StreamID + headerStream utils.Stream + dataStream utils.Stream + + header http.Header + headerWritten bool +} + +func newResponseWriter(headerStream utils.Stream, dataStreamID protocol.StreamID, session *quic.Session) *responseWriter { + return &responseWriter{ + header: http.Header{}, + headerStream: headerStream, + dataStreamID: dataStreamID, + session: session, + } +} + +func (w *responseWriter) Header() http.Header { + return w.header +} + +func (w *responseWriter) WriteHeader(status int) { + w.headerWritten = true + + var headers bytes.Buffer + enc := hpack.NewEncoder(&headers) + enc.WriteField(hpack.HeaderField{Name: ":status", Value: strconv.Itoa(status)}) + + for k, v := range w.header { + enc.WriteField(hpack.HeaderField{Name: k, Value: v[0]}) + } + + fmt.Printf("Responding with %d %#v\n", status, w.header) + h2framer := http2.NewFramer(w.headerStream, nil) + err := h2framer.WriteHeaders(http2.HeadersFrameParam{ + StreamID: uint32(w.dataStreamID), + EndHeaders: true, + BlockFragment: headers.Bytes(), + }) + if err != nil { + panic(err) + } +} + +func (w *responseWriter) Write(p []byte) (int, error) { + if !w.headerWritten { + w.WriteHeader(200) + } + + if len(p) != 0 { + if w.dataStream == nil { + var err error + w.dataStream, err = w.session.NewStream(w.dataStreamID) + if err != nil { + return 0, fmt.Errorf("error creating data stream: %s\n", err.Error()) + } + } + return w.dataStream.Write(p) + } + return 0, nil +} diff --git a/h2quic/response_writer_test.go b/h2quic/response_writer_test.go new file mode 100644 index 000000000..2c6e69cce --- /dev/null +++ b/h2quic/response_writer_test.go @@ -0,0 +1,44 @@ +package h2quic + +import ( + "bytes" + "net/http" + + . "github.com/onsi/ginkgo" + . "github.com/onsi/gomega" +) + +type mockStream struct { + bytes.Buffer +} + +func (mockStream) Close() error { return nil } + +var _ = Describe("Response Writer", func() { + var ( + w *responseWriter + headerStream *mockStream + ) + + BeforeEach(func() { + headerStream = &mockStream{} + w = newResponseWriter(headerStream, 5, nil) + }) + + It("writes status", func() { + w.WriteHeader(http.StatusTeapot) + Expect(headerStream.Bytes()).To(Equal([]byte{ + 0x0, 0x0, 0x5, 0x1, 0x4, 0x0, 0x0, 0x0, 0x5, 'H', 0x3, '4', '1', '8', + })) + }) + + It("writes headers", func() { + w.Header().Add("content-length", "42") + w.WriteHeader(http.StatusTeapot) + Expect(headerStream.Bytes()).To(Equal([]byte{ + 0x0, 0x0, 0x14, 0x1, 0x4, 0x0, 0x0, 0x0, 0x5, 0x48, 0x3, 0x34, 0x31, 0x38, + 0x40, 0x8a, 0xbc, 0x7a, 0x92, 0x5a, 0x92, 0xb6, 0x72, 0xd5, 0x32, 0x67, + 0x2, 0x34, 0x32, + })) + }) +})