mirror of
https://github.com/refraction-networking/uquic.git
synced 2025-04-02 19:57:35 +03:00
uTLS is not yet bumped to the new version, so this commit breaks the dependencies relationship by getting rid of the local replace.
201 lines
5.2 KiB
Go
201 lines
5.2 KiB
Go
package main
|
|
|
|
import (
|
|
"bufio"
|
|
"context"
|
|
"crypto/md5"
|
|
"errors"
|
|
"flag"
|
|
"fmt"
|
|
"io"
|
|
"log"
|
|
"mime/multipart"
|
|
"net/http"
|
|
"os"
|
|
"strconv"
|
|
"strings"
|
|
"sync"
|
|
|
|
_ "net/http/pprof"
|
|
|
|
quic "github.com/refraction-networking/uquic"
|
|
"github.com/refraction-networking/uquic/http3"
|
|
"github.com/refraction-networking/uquic/internal/testdata"
|
|
"github.com/refraction-networking/uquic/internal/utils"
|
|
"github.com/refraction-networking/uquic/logging"
|
|
"github.com/refraction-networking/uquic/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 := io.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 = func(ctx context.Context, p logging.Perspective, connID quic.ConnectionID) logging.ConnectionTracer {
|
|
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 qlog.NewConnectionTracer(utils.NewBufferedWriteCloser(bufio.NewWriter(f), f), p, connID)
|
|
}
|
|
}
|
|
|
|
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()
|
|
}
|