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" "github.com/lucas-clemente/quic-go/quictrace" ) 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 } var tracer quictrace.Tracer func init() { tracer = quictrace.NewTracer() } func exportTraces() error { traces := tracer.GetAllTraces() if len(traces) != 1 { return errors.New("expected exactly one trace") } for _, trace := range traces { f, err := os.Create("trace.qtr") if err != nil { return err } if _, err := f.Write(trace); err != nil { return err } f.Close() fmt.Println("Wrote trace to", f.Name()) } return nil } type tracingHandler struct { handler http.Handler } var _ http.Handler = &tracingHandler{} func (h *tracingHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) { h.handler.ServeHTTP(w, r) if err := exportTraces(); err != nil { log.Fatal(err) } } func setupHandler(www string, trace bool) 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, "") for i := 0; i < 200; i++ { fmt.Fprintf(w, ``, i) } io.WriteString(w, "") }) 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") } } if err != nil { utils.DefaultLogger.Infof("Error receiving upload: %#v", err) } } io.WriteString(w, `

`) }) if !trace { return mux } return &tracingHandler{handler: 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") trace := flag.Bool("trace", false, "enable quic-trace") 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, *trace) quicConf := &quic.Config{} if *trace { quicConf.QuicTracer = tracer } 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, nil) } else { server := http3.Server{ Server: &http.Server{Handler: handler, Addr: bCap}, QuicConfig: quicConf, } err = server.ListenAndServeTLS(testdata.GetCertificatePaths()) } if err != nil { fmt.Println(err) } wg.Done() }() } wg.Wait() }