uquic/example/main.go
Ivan Trubach 0ec8ee3cff
do not embed http.Server in http3.Server (#3397)
This change removes the embedded http.Server struct from http3.Server.
It slightly changes some error behavior, in particular, it mandates
TLSConfig for methods that create QUIC listeners.

Before this change, only Addr, TLSConfig, Handler and MaxHeaderBytes
options were used from the http.Server. These are now defined directly
in http3.Server with an improved documentation.
2022-05-20 02:53:23 -07:00

201 lines
5.1 KiB
Go

package main
import (
"bufio"
"crypto/md5"
"errors"
"flag"
"fmt"
"io"
"io/ioutil"
"log"
"mime/multipart"
"net/http"
"os"
"strconv"
"strings"
"sync"
_ "net/http/pprof"
"github.com/lucas-clemente/quic-go"
"github.com/lucas-clemente/quic-go/http3"
"github.com/lucas-clemente/quic-go/internal/testdata"
"github.com/lucas-clemente/quic-go/internal/utils"
"github.com/lucas-clemente/quic-go/logging"
"github.com/lucas-clemente/quic-go/qlog"
)
type binds []string
func (b binds) String() string {
return strings.Join(b, ",")
}
func (b *binds) Set(v string) error {
*b = strings.Split(v, ",")
return nil
}
// Size is needed by the /demo/upload handler to determine the size of the uploaded file
type Size interface {
Size() int64
}
// See https://en.wikipedia.org/wiki/Lehmer_random_number_generator
func generatePRData(l int) []byte {
res := make([]byte, l)
seed := uint64(1)
for i := 0; i < l; i++ {
seed = seed * 48271 % 2147483647
res[i] = byte(seed)
}
return res
}
func setupHandler(www string) http.Handler {
mux := http.NewServeMux()
if len(www) > 0 {
mux.Handle("/", http.FileServer(http.Dir(www)))
} else {
mux.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
fmt.Printf("%#v\n", r)
const maxSize = 1 << 30 // 1 GB
num, err := strconv.ParseInt(strings.ReplaceAll(r.RequestURI, "/", ""), 10, 64)
if err != nil || num <= 0 || num > maxSize {
w.WriteHeader(400)
return
}
w.Write(generatePRData(int(num)))
})
}
mux.HandleFunc("/demo/tile", func(w http.ResponseWriter, r *http.Request) {
// Small 40x40 png
w.Write([]byte{
0x89, 0x50, 0x4e, 0x47, 0x0d, 0x0a, 0x1a, 0x0a, 0x00, 0x00, 0x00, 0x0d,
0x49, 0x48, 0x44, 0x52, 0x00, 0x00, 0x00, 0x28, 0x00, 0x00, 0x00, 0x28,
0x01, 0x03, 0x00, 0x00, 0x00, 0xb6, 0x30, 0x2a, 0x2e, 0x00, 0x00, 0x00,
0x03, 0x50, 0x4c, 0x54, 0x45, 0x5a, 0xc3, 0x5a, 0xad, 0x38, 0xaa, 0xdb,
0x00, 0x00, 0x00, 0x0b, 0x49, 0x44, 0x41, 0x54, 0x78, 0x01, 0x63, 0x18,
0x61, 0x00, 0x00, 0x00, 0xf0, 0x00, 0x01, 0xe2, 0xb8, 0x75, 0x22, 0x00,
0x00, 0x00, 0x00, 0x49, 0x45, 0x4e, 0x44, 0xae, 0x42, 0x60, 0x82,
})
})
mux.HandleFunc("/demo/tiles", func(w http.ResponseWriter, r *http.Request) {
io.WriteString(w, "<html><head><style>img{width:40px;height:40px;}</style></head><body>")
for i := 0; i < 200; i++ {
fmt.Fprintf(w, `<img src="/demo/tile?cachebust=%d">`, i)
}
io.WriteString(w, "</body></html>")
})
mux.HandleFunc("/demo/echo", func(w http.ResponseWriter, r *http.Request) {
body, err := ioutil.ReadAll(r.Body)
if err != nil {
fmt.Printf("error reading body while handling /echo: %s\n", err.Error())
}
w.Write(body)
})
// accept file uploads and return the MD5 of the uploaded file
// maximum accepted file size is 1 GB
mux.HandleFunc("/demo/upload", func(w http.ResponseWriter, r *http.Request) {
if r.Method == http.MethodPost {
err := r.ParseMultipartForm(1 << 30) // 1 GB
if err == nil {
var file multipart.File
file, _, err = r.FormFile("uploadfile")
if err == nil {
var size int64
if sizeInterface, ok := file.(Size); ok {
size = sizeInterface.Size()
b := make([]byte, size)
file.Read(b)
md5 := md5.Sum(b)
fmt.Fprintf(w, "%x", md5)
return
}
err = errors.New("couldn't get uploaded file size")
}
}
utils.DefaultLogger.Infof("Error receiving upload: %#v", err)
}
io.WriteString(w, `<html><body><form action="/demo/upload" method="post" enctype="multipart/form-data">
<input type="file" name="uploadfile"><br>
<input type="submit">
</form></body></html>`)
})
return mux
}
func main() {
// defer profile.Start().Stop()
go func() {
log.Println(http.ListenAndServe("localhost:6060", nil))
}()
// runtime.SetBlockProfileRate(1)
verbose := flag.Bool("v", false, "verbose")
bs := binds{}
flag.Var(&bs, "bind", "bind to")
www := flag.String("www", "", "www data")
tcp := flag.Bool("tcp", false, "also listen on TCP")
enableQlog := flag.Bool("qlog", false, "output a qlog (in the same directory)")
flag.Parse()
logger := utils.DefaultLogger
if *verbose {
logger.SetLogLevel(utils.LogLevelDebug)
} else {
logger.SetLogLevel(utils.LogLevelInfo)
}
logger.SetLogTimeFormat("")
if len(bs) == 0 {
bs = binds{"localhost:6121"}
}
handler := setupHandler(*www)
quicConf := &quic.Config{}
if *enableQlog {
quicConf.Tracer = qlog.NewTracer(func(_ logging.Perspective, connID []byte) io.WriteCloser {
filename := fmt.Sprintf("server_%x.qlog", connID)
f, err := os.Create(filename)
if err != nil {
log.Fatal(err)
}
log.Printf("Creating qlog file %s.\n", filename)
return utils.NewBufferedWriteCloser(bufio.NewWriter(f), f)
})
}
var wg sync.WaitGroup
wg.Add(len(bs))
for _, b := range bs {
bCap := b
go func() {
var err error
if *tcp {
certFile, keyFile := testdata.GetCertificatePaths()
err = http3.ListenAndServe(bCap, certFile, keyFile, handler)
} else {
server := http3.Server{
Handler: handler,
Addr: bCap,
QuicConfig: quicConf,
}
err = server.ListenAndServeTLS(testdata.GetCertificatePaths())
}
if err != nil {
fmt.Println(err)
}
wg.Done()
}()
}
wg.Wait()
}