move h2 server stuff from main to h2quic package

This commit is contained in:
Lucas Clemente 2016-05-03 14:26:46 +02:00
parent c3da72d498
commit e3a4d75fc1
3 changed files with 103 additions and 163 deletions

View file

@ -1,21 +1,11 @@
package main
import (
"bytes"
"errors"
"flag"
"fmt"
"net/http"
"net/url"
"os"
"strconv"
"golang.org/x/net/http2"
"golang.org/x/net/http2/hpack"
"github.com/lucas-clemente/quic-go"
"github.com/lucas-clemente/quic-go/protocol"
"github.com/lucas-clemente/quic-go/utils"
"github.com/lucas-clemente/quic-go/h2quic"
)
func main() {
@ -25,161 +15,15 @@ func main() {
www := flag.String("www", "/var/www", "www data")
flag.Parse()
server, err := quic.NewServer(*certPath+"cert.der", *certPath+"key.der", handleStream)
if err != nil {
panic(err)
}
http.Handle("/", http.FileServer(http.Dir(*www)))
err = server.ListenAndServe(*bindTo + ":6121")
server, err := h2quic.NewServer(*certPath)
if err != nil {
panic(err)
}
err = server.ListenAndServe(*bindTo+":6121", nil)
if err != nil {
panic(err)
}
}
type responseWriter struct {
session *quic.Session
dataStreamID protocol.StreamID
headerStream utils.Stream
dataStream utils.Stream
header http.Header
headerWritten bool
}
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)
h2framer.WriteHeaders(http2.HeadersFrameParam{
StreamID: uint32(w.dataStreamID),
EndHeaders: true,
BlockFragment: headers.Bytes(),
})
}
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
}
func handleStream(session *quic.Session, headerStream utils.Stream) {
hpackDecoder := hpack.NewDecoder(4096, nil)
h2framer := http2.NewFramer(nil, headerStream)
go func() {
for {
if err := handleRequest(session, headerStream, hpackDecoder, h2framer); err != nil {
fmt.Printf("error handling h2 request: %s\n", err.Error())
return
}
}
}()
}
func handleRequest(session *quic.Session, headerStream utils.Stream, hpackDecoder *hpack.Decoder, h2framer *http2.Framer) error {
h2frame, err := h2framer.ReadFrame()
if err != nil {
return err
}
h2headersFrame := h2frame.(*http2.HeadersFrame)
if !h2headersFrame.HeadersEnded() {
return errors.New("http2 header continuation not implemented")
}
headers, err := hpackDecoder.DecodeFull(h2headersFrame.HeaderBlockFragment())
if err != nil {
fmt.Printf("invalid http2 headers encoding: %s\n", err.Error())
return err
}
req, err := requestFromHeaders(headers)
if err != nil {
return err
}
fmt.Printf("Request: %#v\n", req)
responseWriter := &responseWriter{
header: http.Header{},
headerStream: headerStream,
dataStreamID: protocol.StreamID(h2headersFrame.StreamID),
session: session,
}
go func() {
http.DefaultServeMux.ServeHTTP(responseWriter, req)
if responseWriter.dataStream != nil {
responseWriter.dataStream.Close()
}
}()
return nil
}
func requestFromHeaders(headers []hpack.HeaderField) (*http.Request, error) {
var path, authority, method string
httpHeaders := http.Header{}
for _, h := range headers {
switch h.Name {
case ":path":
path = h.Value
case ":method":
method = h.Value
case ":authority":
authority = h.Value
default:
if !h.IsPseudo() {
httpHeaders.Add(h.Name, h.Value)
}
}
}
if len(path) == 0 || len(authority) == 0 || len(method) == 0 {
return nil, errors.New(":path, :authority and :method must not be empty")
}
u, err := url.Parse(path)
if err != nil {
return nil, err
}
return &http.Request{
Method: method,
URL: u,
Proto: "HTTP/2.0",
ProtoMajor: 2,
ProtoMinor: 0,
Header: httpHeaders,
Body: nil,
// ContentLength: -1,
Host: authority,
RequestURI: path,
}, nil
}

93
h2quic/server.go Normal file
View file

@ -0,0 +1,93 @@
package h2quic
import (
"errors"
"fmt"
"net/http"
"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"
)
// Server is a HTTP2 server listening for QUIC connections
type Server struct {
server *quic.Server
handler http.Handler
}
// NewServer creates a new server instance
func NewServer(certPath string) (*Server, error) {
s := &Server{}
var err error
s.server, err = quic.NewServer(certPath+"cert.der", certPath+"key.der", s.handleStream)
if err != nil {
return nil, err
}
return s, nil
}
//ListenAndServe listens on the network address and calls the handler.
func (s *Server) ListenAndServe(addr string, handler http.Handler) error {
if handler != nil {
s.handler = handler
} else {
s.handler = http.DefaultServeMux
}
return s.server.ListenAndServe(addr)
}
func (s *Server) handleStream(session *quic.Session, headerStream utils.Stream) {
hpackDecoder := hpack.NewDecoder(4096, nil)
h2framer := http2.NewFramer(nil, headerStream)
go func() {
for {
if err := s.handleRequest(session, headerStream, hpackDecoder, h2framer); err != nil {
fmt.Printf("error handling h2 request: %s\n", err.Error())
return
}
}
}()
}
func (s *Server) handleRequest(session *quic.Session, headerStream utils.Stream, hpackDecoder *hpack.Decoder, h2framer *http2.Framer) error {
h2frame, err := h2framer.ReadFrame()
if err != nil {
return err
}
h2headersFrame := h2frame.(*http2.HeadersFrame)
if !h2headersFrame.HeadersEnded() {
return errors.New("http2 header continuation not implemented")
}
headers, err := hpackDecoder.DecodeFull(h2headersFrame.HeaderBlockFragment())
if err != nil {
fmt.Printf("invalid http2 headers encoding: %s\n", err.Error())
return err
}
req, err := requestFromHeaders(headers)
if err != nil {
return err
}
fmt.Printf("Request: %#v\n", req)
responseWriter := &responseWriter{
header: http.Header{},
headerStream: headerStream,
dataStreamID: protocol.StreamID(h2headersFrame.StreamID),
session: session,
}
go func() {
s.handler.ServeHTTP(responseWriter, req)
if responseWriter.dataStream != nil {
responseWriter.dataStream.Close()
}
}()
return nil
}

3
h2quic/server_test.go Normal file
View file

@ -0,0 +1,3 @@
package h2quic
// TODO: Write server tests