1
0
mirror of https://github.com/fumiama/terasu.git synced 2026-06-05 01:00:23 +08:00
Files
terasu/http3/http.go
2026-02-16 15:20:45 +08:00

108 lines
3.4 KiB
Go

// Package http3 is the same as the standard http lib with HTTP3 client support
package http3
import (
"bytes"
"context"
"crypto/rand"
"crypto/tls"
"errors"
"io"
mrand "math/rand"
"net"
"net/http"
"net/netip"
"net/url"
base14 "github.com/fumiama/go-base16384"
"github.com/fumiama/terasu/dialer"
"github.com/fumiama/terasu/dns"
"github.com/quic-go/quic-go"
"github.com/quic-go/quic-go/http3"
)
// ErrEmptyHostAddress is returned when DNS lookup for a host returns no addresses
var ErrEmptyHostAddress = errors.New("empty host addr")
// DefaultClient is the default HTTP2 client that supports HTTP/2 and DNS resolution
var DefaultClient = http.Client{
Transport: &http3.Transport{
Dial: func(ctx context.Context, addr string, tlsCfg *tls.Config, cfg *quic.Config) (quic.EarlyConnection, error) {
host, port, err := net.SplitHostPort(addr)
if err != nil {
return nil, err
}
addrs, err := dns.LookupHost(ctx, host)
if err != nil {
return nil, err
}
if len(addrs) == 0 {
return nil, ErrEmptyHostAddress
}
var conn net.Conn
var qConn quic.EarlyConnection
for _, a := range addrs {
if dialer.DefaultDialer.Timeout != 0 {
var cancel context.CancelFunc
ctx, cancel = context.WithTimeout(context.Background(), dialer.DefaultDialer.Timeout)
defer cancel()
} else if !dialer.DefaultDialer.Deadline.IsZero() {
var cancel context.CancelFunc
ctx, cancel = context.WithDeadline(context.Background(), dialer.DefaultDialer.Deadline)
defer cancel()
}
conn, err = net.ListenUDP("udp", nil)
if err != nil {
continue
}
ucon := conn.(*net.UDPConn)
raddr := net.UDPAddrFromAddrPort(
netip.MustParseAddrPort(net.JoinHostPort(a, port)),
)
n := (mrand.Intn(128) + 128) / 7 * 14
w := bytes.NewBuffer(make([]byte, 0, base14.EncodeLen(n)))
e := base14.NewEncoder(w)
_, _ = io.CopyN(e, rand.Reader, int64(n))
_ = e.Close()
_, _ = ucon.WriteToUDP(w.Bytes(), raddr)
// re-init ctx due to deadline settings in tcp dial
if dialer.DefaultDialer.Timeout != 0 {
var cancel context.CancelFunc
ctx, cancel = context.WithTimeout(context.Background(), dialer.DefaultDialer.Timeout)
defer cancel()
} else if !dialer.DefaultDialer.Deadline.IsZero() {
var cancel context.CancelFunc
ctx, cancel = context.WithDeadline(context.Background(), dialer.DefaultDialer.Deadline)
defer cancel()
}
qConn, err = quic.DialEarly(ctx, ucon, raddr, tlsCfg, cfg)
if err == nil {
break
}
panic(err)
}
return qConn, err
},
},
}
// Get sends an HTTP GET request to the specified URL using the default HTTP2 client
func Get(url string) (resp *http.Response, err error) {
return DefaultClient.Get(url)
}
// Head sends an HTTP HEAD request to the specified URL using the default HTTP2 client
func Head(url string) (resp *http.Response, err error) {
return DefaultClient.Head(url)
}
// Post sends an HTTP POST request to the specified URL with the given content type and body using the default HTTP2 client
func Post(url string, contentType string, body io.Reader) (resp *http.Response, err error) {
return DefaultClient.Post(url, contentType, body)
}
// PostForm sends an HTTP POST request with form data to the specified URL using the default HTTP2 client
func PostForm(url string, data url.Values) (resp *http.Response, err error) {
return DefaultClient.PostForm(url, data)
}