Documentation
¶
Overview ¶
Package qilat is a high-performance HTTP library with net/http-style APIs and native HTTP/1.1, HTTP/2, and HTTP/3 support.
Index ¶
- Constants
- Variables
- func Chain(mw ...func(Handler) Handler) func(Handler) Handler
- func Compress(level int) func(Handler) Handler
- func CompressWith(cfg CompressConfig) func(Handler) Handler
- func Error(w ResponseWriter, msg string, code int)
- func FromStdMiddleware(mw func(http.Handler) http.Handler) func(Handler) Handler
- func GenerateDevCert(hosts ...string) (tls.Certificate, error)
- func Get(url string, resp *Response) error
- func NotFound(w ResponseWriter, _ *Request)
- func ProxyFromEnvironment(req *Request) (*url.URL, error)
- func ReleaseRequest(r *Request)
- func ReleaseResponse(resp *Response)
- func ServeContent(w ResponseWriter, r *Request, name string, modtime time.Time, ...)
- func ServeFile(w ResponseWriter, r *Request, name string)
- func ToStd(h Handler) http.Handler
- type Args
- type Client
- type CompressConfig
- type CompressWriter
- type Encoder
- type HTTP2Config
- type HTTP3Config
- type Handler
- type HandlerFunc
- type Header
- func (h *Header) Add(name, value string)
- func (h *Header) AddBytes(name string, value []byte)
- func (h *Header) All() iter.Seq2[[]byte, []byte]
- func (h *Header) CopyTo(dst *Header)
- func (h *Header) Del(name string)
- func (h *Header) Get(name string) string
- func (h *Header) Has(name string) bool
- func (h *Header) Len() int
- func (h *Header) Peek(name string) []byte
- func (h *Header) PeekAll(name string) [][]byte
- func (h *Header) Reset()
- func (h *Header) Set(name, value string)
- func (h *Header) SetBytes(name string, value []byte)
- type Hijacker
- type Request
- func (r *Request) Clone(ctx context.Context) *Request
- func (r *Request) ContentLength() int64
- func (r *Request) Context() context.Context
- func (r *Request) Host() []byte
- func (r *Request) Method() string
- func (r *Request) Path() []byte
- func (r *Request) PathValue(name string) string
- func (r *Request) Pattern() string
- func (r *Request) PeekPathValue(name string) []byte
- func (r *Request) Proto() string
- func (r *Request) RemoteAddr() net.Addr
- func (r *Request) SetBody(body io.Reader, contentLength int64)
- func (r *Request) SetHost(host string) error
- func (r *Request) SetMethod(method string)
- func (r *Request) SetPathValue(name, value string)
- func (r *Request) SetRequestURI(uri string) error
- func (r *Request) TLS() *tls.ConnectionState
- func (r *Request) URI() *URI
- func (r *Request) WithContext(ctx context.Context) *Request
- type Response
- type ResponseWriter
- type RoundTripper
- type ServeMux
- type Server
- func (s *Server) Close() error
- func (s *Server) ListenAndServe(addr string) error
- func (s *Server) ListenAndServeTLS(addr, certFile, keyFile string) error
- func (s *Server) Serve(ln net.Listener) error
- func (s *Server) ServeQUIC(pc net.PacketConn) error
- func (s *Server) ServeTLS(ln net.Listener, certFile, keyFile string) error
- func (s *Server) Shutdown(ctx context.Context) error
- type TrailerSetter
- type Transport
- type URI
- func (u *URI) CopyTo(dst *URI)
- func (u *URI) Host() []byte
- func (u *URI) Parse(target []byte) error
- func (u *URI) Path() []byte
- func (u *URI) Query() *Args
- func (u *URI) Raw() []byte
- func (u *URI) RawPath() []byte
- func (u *URI) RawQuery() []byte
- func (u *URI) Scheme() []byte
- func (u *URI) String() string
Examples ¶
Constants ¶
const ( DefaultHTTP2MaxConcurrentStreams = 250 DefaultHTTP2MaxReadFrameSize = 16 << 10 DefaultHTTP2MaxReceiveBufferPerConnection = 1 << 20 DefaultHTTP2MaxReceiveBufferPerStream = 1 << 20 DefaultHTTP2MaxDecoderHeaderTableSize = 4 << 10 )
Default HTTP/2 limits used when the corresponding HTTP2Config field is zero.
const ( DefaultHTTP3MaxConcurrentStreams = 250 DefaultHTTP3MaxReceiveBufferPerConnection = 4 << 20 DefaultHTTP3MaxReceiveBufferPerStream = 1 << 20 DefaultAltSvcMaxAge = 24 * time.Hour )
Default HTTP/3 settings used when the corresponding HTTP3Config field is zero.
const ( DefaultReadHeaderTimeout = 10 * time.Second DefaultIdleTimeout = 120 * time.Second DefaultMaxHeaderBytes = 64 << 10 DefaultMaxRequestBodySize = 4 << 20 )
Default limits used when the corresponding Server field is zero.
const ( DefaultMaxIdleConnsPerHost = 100 DefaultIdleConnTimeout = 90 * time.Second DefaultDialTimeout = 30 * time.Second DefaultTLSHandshakeTimeout = 10 * time.Second DefaultMaxResponseHeaderBytes = 64 << 10 DefaultHTTP3DialTimeout = time.Second )
Defaults used when the corresponding Transport field is zero.
const DefaultCompressMinSize = 1024
DefaultCompressMinSize is the smallest response body Compress compresses when MinSize is zero.
const DefaultMaxResponseBodySize = 64 << 20
DefaultMaxResponseBodySize is the limit of Response.ReadBody when Transport.MaxResponseBodySize is zero.
Variables ¶
var ( // ErrUseLastResponse can be returned by Client.CheckRedirect to stop at the redirect response. ErrUseLastResponse = errors.New("qilat: use last response") // ErrTooManyRedirects is returned by Client.Do after 10 redirects when CheckRedirect is nil. ErrTooManyRedirects = errors.New("qilat: stopped after 10 redirects") // ErrResponseBodyTooLarge is returned by Response.ReadBody when the body exceeds Transport.MaxResponseBodySize. ErrResponseBodyTooLarge = errors.New("qilat: response body too large") )
var ( // ErrNotSupported is returned when the protocol does not support a feature, such as Hijack on HTTP/2. ErrNotSupported = errors.New("qilat: feature not supported") // ErrHijacked is returned by ResponseWriter methods after the connection has been hijacked. ErrHijacked = errors.New("qilat: connection has been hijacked") // ErrBodyNotAllowed is returned by Write when the status code does not allow a body. ErrBodyNotAllowed = errors.New("qilat: request method or response status code does not allow body") // ErrContentLength is returned when the body length differs from a declared Content-Length. ErrContentLength = errors.New("qilat: body length does not match declared Content-Length") )
var DefaultClient = &Client{}
DefaultClient is the Client used by Get.
var DefaultTransport = &Transport{}
DefaultTransport is the Transport used by a Client whose Transport is nil.
var ErrAbortHandler = errors.New("qilat: abort Handler")
ErrAbortHandler is a sentinel panic value to abort a handler. The connection or stream is closed and, unlike other panics, nothing is logged.
var ErrBodyTooLarge = exchange.ErrBodyTooLarge
ErrBodyTooLarge is returned when reading a request body that exceeds Server.MaxRequestBodySize.
var ErrInvalidURI = errors.New("qilat: invalid request URI")
ErrInvalidURI is returned when a request-target is malformed.
var ErrServerClosed = errors.New("qilat: Server closed")
ErrServerClosed is returned by Serve and ListenAndServe after Shutdown or Close.
Functions ¶
func Chain ¶
Chain composes middleware so that Chain(a, b)(h) is a(b(h)): a sees the request first.
Example ¶
package main
import (
"fmt"
"net"
"strings"
"github.com/erizeta/qilat"
)
// serve runs h on a loopback port and returns its base URL and a function that stops it.
func serve(h qilat.Handler) (string, func()) {
ln, err := net.Listen("tcp", "127.0.0.1:0")
if err != nil {
panic(err)
}
srv := &qilat.Server{Handler: h}
done := make(chan struct{})
go func() {
defer close(done)
_ = srv.Serve(ln)
}()
return "http://" + ln.Addr().String(), func() {
_ = srv.Close()
<-done
}
}
// get fetches url with a client of its own and returns the status and body.
func get(url string) (int, string) {
tr := &qilat.Transport{}
defer tr.CloseIdleConnections()
req := qilat.AcquireRequest()
defer qilat.ReleaseRequest(req)
if err := req.SetRequestURI(url); err != nil {
panic(err)
}
resp := qilat.AcquireResponse()
defer qilat.ReleaseResponse(resp)
if err := (&qilat.Client{Transport: tr}).Do(req, resp); err != nil {
panic(err)
}
body, err := resp.ReadBody()
if err != nil {
panic(err)
}
return resp.StatusCode(), string(body)
}
func main() {
tag := func(name string) func(qilat.Handler) qilat.Handler {
return func(next qilat.Handler) qilat.Handler {
return qilat.HandlerFunc(func(w qilat.ResponseWriter, r *qilat.Request) {
w.Header().Add("X-Order", name)
next.ServeHTTP(w, r)
})
}
}
h := qilat.Chain(tag("first"), tag("second"))(qilat.HandlerFunc(func(w qilat.ResponseWriter, _ *qilat.Request) {
var order []string
for _, v := range w.Header().PeekAll("X-Order") {
order = append(order, string(v))
}
_, _ = w.WriteString(strings.Join(order, " then "))
}))
url, stop := serve(h)
defer stop()
fmt.Println(get(url))
}
Output: 200 first then second
func Compress ¶
Compress returns middleware that compresses response bodies with gzip at level. See CompressWith.
func CompressWith ¶
func CompressWith(cfg CompressConfig) func(Handler) Handler
CompressWith returns middleware that compresses response bodies with the coding the client prefers in Accept-Encoding. It skips small and already encoded bodies, Range requests, partial content, server-sent events, Cache-Control: no-transform, and types that do not compress, such as images. Compressible responses get Vary: Accept-Encoding, and a strong ETag becomes weak. It panics on an invalid Level.
func Error ¶
func Error(w ResponseWriter, msg string, code int)
Error replies with the plain-text message msg and status code, like net/http.Error.
func FromStdMiddleware ¶
FromStdMiddleware adapts net/http middleware, such as CORS or request logging, to qilat handlers. Each request pays for both FromStd and ToStd.
func GenerateDevCert ¶
func GenerateDevCert(hosts ...string) (tls.Certificate, error)
GenerateDevCert returns a self-signed certificate for local development, valid for 30 days for the given host names and IP addresses ("localhost", "127.0.0.1", and "::1" if none are given). Browsers and clients will not trust it without an exception; never use it in production.
func NotFound ¶
func NotFound(w ResponseWriter, _ *Request)
NotFound replies with a 404 Not Found error.
func ProxyFromEnvironment ¶
ProxyFromEnvironment returns the proxy for req from the HTTP_PROXY, HTTPS_PROXY, and NO_PROXY environment variables, like net/http.ProxyFromEnvironment. Set it as Transport.Proxy. The variables are read once.
func ReleaseRequest ¶
func ReleaseRequest(r *Request)
ReleaseRequest returns r to the pool. r must not be used afterwards.
func ReleaseResponse ¶
func ReleaseResponse(resp *Response)
ReleaseResponse closes resp's body and returns resp to the pool. resp must not be used afterwards.
func ServeContent ¶
func ServeContent(w ResponseWriter, r *Request, name string, modtime time.Time, content io.ReadSeeker)
ServeContent replies with content like net/http.ServeContent: it handles Range and conditional requests, sets Content-Type from name's extension or by sniffing, and sets Last-Modified unless modtime is zero. An ETag set on w before the call is used for If-Match, If-None-Match, and If-Range.
func ServeFile ¶
func ServeFile(w ResponseWriter, r *Request, name string)
ServeFile replies with the named file or directory from the operating system, like net/http.ServeFile. Requests whose path contains a ".." element are rejected, in case name was built from the path.
func ToStd ¶
ToStd adapts a Handler to a net/http.Handler, so qilat handlers can run on a net/http server. Path values set by net/http.ServeMux are copied to the request.
Types ¶
type Args ¶
type Args struct {
// contains filtered or unexported fields
}
Args holds decoded query or form arguments in order. Keys are case-sensitive. Returned byte slices are valid until the next Parse or until the handler returns.
Example ¶
package main
import (
"fmt"
"github.com/erizeta/qilat"
)
func main() {
var a qilat.Args
a.Parse([]byte("tag=go&tag=http%2F3&q=fast+server"))
fmt.Println(a.Get("q"))
for _, v := range a.PeekAll("tag") {
fmt.Println(string(v))
}
}
Output: fast server go http/3
func (*Args) Get ¶
Get returns a copy of the first value for key, or "" if there is none. It allocates.
func (*Args) Parse ¶
Parse replaces the arguments with the decoded pairs of an application/x-www-form-urlencoded string. Like net/url.ParseQuery, pairs with invalid escapes or a ';' are skipped and the rest are kept.
type Client ¶
type Client struct {
// Transport sends requests; nil means DefaultTransport.
Transport RoundTripper
// Timeout limits the whole request, including reading the response body; zero means no limit.
// The request context, set with Request.WithContext, can end it sooner.
Timeout time.Duration
// CheckRedirect decides whether to follow a redirect response; redirects counts those already followed.
// It sees the request just sent and its redirect response. Returning ErrUseLastResponse makes Do return
// that response; any other error is returned by Do. Nil means follow up to 10 redirects.
CheckRedirect func(req *Request, resp *Response, redirects int) error
}
Client sends HTTP requests over HTTP/1.1, HTTP/2, and HTTP/3. Requests and responses are pooled: get them with AcquireRequest and AcquireResponse and give them back with ReleaseRequest and ReleaseResponse. A Client is safe for concurrent use.
func (*Client) Do ¶
Do sends req and fills resp, following redirects. The response body streams from resp.Body until resp is released or closed. After a followed redirect, req describes the last request sent.
Example ¶
package main
import (
"fmt"
"net"
"time"
"github.com/erizeta/qilat"
)
// serve runs h on a loopback port and returns its base URL and a function that stops it.
func serve(h qilat.Handler) (string, func()) {
ln, err := net.Listen("tcp", "127.0.0.1:0")
if err != nil {
panic(err)
}
srv := &qilat.Server{Handler: h}
done := make(chan struct{})
go func() {
defer close(done)
_ = srv.Serve(ln)
}()
return "http://" + ln.Addr().String(), func() {
_ = srv.Close()
<-done
}
}
func main() {
url, stop := serve(qilat.HandlerFunc(func(w qilat.ResponseWriter, r *qilat.Request) {
_, _ = w.WriteString(r.Method() + " " + r.Header.Get("X-Request-Id"))
}))
defer stop()
tr := &qilat.Transport{}
defer tr.CloseIdleConnections()
client := &qilat.Client{Transport: tr, Timeout: 5 * time.Second}
// Requests and responses are pooled; release them when done.
req := qilat.AcquireRequest()
defer qilat.ReleaseRequest(req)
req.SetMethod("DELETE")
req.Header.Set("X-Request-Id", "abc")
if err := req.SetRequestURI(url + "/items/1"); err != nil {
panic(err)
}
resp := qilat.AcquireResponse()
defer qilat.ReleaseResponse(resp)
if err := client.Do(req, resp); err != nil {
panic(err)
}
body, err := resp.ReadBody() // owned by resp: valid until ReleaseResponse
if err != nil {
panic(err)
}
fmt.Println(resp.Proto(), resp.StatusCode(), string(body))
}
Output: HTTP/1.1 200 DELETE abc
type CompressConfig ¶
type CompressConfig struct {
// Level is the gzip level, from gzip.HuffmanOnly to gzip.BestCompression. Zero means gzip.DefaultCompression.
Level int
// MinSize is the smallest body to compress; zero means DefaultCompressMinSize. Smaller bodies are not worth it.
MinSize int
// Encoders adds content codings such as "zstd" or "br". When the client accepts several codings equally,
// they are preferred in this order, then gzip.
Encoders []Encoder
}
CompressConfig configures CompressWith. The zero value compresses with gzip at the default level.
type CompressWriter ¶
type CompressWriter interface {
io.WriteCloser
Flush() error
Reset(w io.Writer)
}
CompressWriter is a reusable compressing writer, such as *gzip.Writer, *zstd.Encoder from github.com/klauspost/compress, or *brotli.Writer from github.com/andybalholm/brotli.
type Encoder ¶
type Encoder struct {
// Name is the content coding, as sent in Content-Encoding, such as "zstd".
Name string
// New returns a writer that compresses to w. Writers are pooled and reused with Reset.
New func(w io.Writer) CompressWriter
}
Encoder is a content coding for CompressWith.
type HTTP2Config ¶
type HTTP2Config struct {
// MaxConcurrentStreams limits open streams, and running handlers, per connection.
MaxConcurrentStreams int
// MaxReadFrameSize is the largest frame the server accepts (16 KiB to 16 MiB).
MaxReadFrameSize int
// MaxReceiveBufferPerConnection and MaxReceiveBufferPerStream bound request body data buffered before
// handlers read it, which is how much the client may send ahead (at least 64 KiB).
MaxReceiveBufferPerConnection int
MaxReceiveBufferPerStream int
// MaxDecoderHeaderTableSize is the HPACK dynamic table size the client may use for request headers.
MaxDecoderHeaderTableSize int
}
HTTP2Config configures HTTP/2. Zero fields mean the defaults above; values outside the range HTTP/2 allows are clamped. Server.MaxHeaderBytes limits the decoded header list, and Server.MaxRequestBodySize the body.
Protections against known HTTP/2 attacks are always on: streams reset faster than their handlers finish (Rapid Reset, CVE-2023-44487), CONTINUATION floods, and floods of PING, SETTINGS, RST_STREAM, PRIORITY, or empty frames end the connection with GOAWAY ENHANCE_YOUR_CALM.
type HTTP3Config ¶
type HTTP3Config struct {
// MaxConcurrentStreams limits concurrent request streams, and running handlers, per connection.
MaxConcurrentStreams int
// MaxReceiveBufferPerConnection and MaxReceiveBufferPerStream bound the QUIC flow-control windows, which is
// how much request data a client may send ahead of the handlers reading it.
MaxReceiveBufferPerConnection int
MaxReceiveBufferPerStream int
// Allow0RTT accepts requests in 0-RTT data, saving a round trip for returning clients. 0-RTT data can be
// replayed, so requests other than GET, HEAD, and OPTIONS are answered 425 Too Early (RFC 8470).
Allow0RTT bool
// AltSvcMaxAge is how long clients may remember that HTTP/3 is available (the Alt-Svc "ma" parameter).
AltSvcMaxAge time.Duration
// DisableAltSvc stops adding the Alt-Svc header to HTTP/1.1 and HTTP/2 responses.
DisableAltSvc bool
}
HTTP3Config configures HTTP/3. Zero fields mean the defaults above. Server.MaxHeaderBytes limits the field section, Server.MaxRequestBodySize the body, Server.IdleTimeout the QUIC idle timeout, and Server.ReadHeaderTimeout the QUIC handshake.
type Handler ¶
type Handler interface {
ServeHTTP(w ResponseWriter, r *Request)
}
Handler responds to an HTTP request. Like net/http.Handler, it must not use w or r after ServeHTTP returns; in qilat both are reused for later requests.
func FileServer ¶
FileServer returns a handler that serves files from fsys, like net/http.FileServerFS: Range requests, conditional requests, Content-Type detection, directory listings, and index.html. Paths cannot escape fsys. Files that implement io.Seeker support ranges; an *os.File is sent with sendfile on HTTP/1.1.
func FromStd ¶
FromStd adapts a net/http.Handler to a Handler. Each request allocates a net/http.Request with its URL and header map; like net/http, the request may be kept after the handler returns, but its Body may not. The ResponseWriter implements net/http.Flusher, FlushError, and on HTTP/1.x net/http.Hijacker.
Example ¶
package main
import (
"fmt"
"net"
"net/http"
"github.com/erizeta/qilat"
)
// serve runs h on a loopback port and returns its base URL and a function that stops it.
func serve(h qilat.Handler) (string, func()) {
ln, err := net.Listen("tcp", "127.0.0.1:0")
if err != nil {
panic(err)
}
srv := &qilat.Server{Handler: h}
done := make(chan struct{})
go func() {
defer close(done)
_ = srv.Serve(ln)
}()
return "http://" + ln.Addr().String(), func() {
_ = srv.Close()
<-done
}
}
// get fetches url with a client of its own and returns the status and body.
func get(url string) (int, string) {
tr := &qilat.Transport{}
defer tr.CloseIdleConnections()
req := qilat.AcquireRequest()
defer qilat.ReleaseRequest(req)
if err := req.SetRequestURI(url); err != nil {
panic(err)
}
resp := qilat.AcquireResponse()
defer qilat.ReleaseResponse(resp)
if err := (&qilat.Client{Transport: tr}).Do(req, resp); err != nil {
panic(err)
}
body, err := resp.ReadBody()
if err != nil {
panic(err)
}
return resp.StatusCode(), string(body)
}
func main() {
std := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
_, _ = fmt.Fprintf(w, "net/http handler at %s", r.URL.Path)
})
url, stop := serve(qilat.FromStd(std))
defer stop()
fmt.Println(get(url + "/legacy"))
}
Output: 200 net/http handler at /legacy
func StripPrefix ¶
StripPrefix serves requests whose path starts with prefix by removing it and calling h, like net/http.StripPrefix; other requests get 404. The remaining path always starts with a slash.
type HandlerFunc ¶
type HandlerFunc func(w ResponseWriter, r *Request)
HandlerFunc adapts an ordinary function to a Handler.
func (HandlerFunc) ServeHTTP ¶
func (f HandlerFunc) ServeHTTP(w ResponseWriter, r *Request)
ServeHTTP calls f(w, r).
type Header ¶
type Header struct {
// contains filtered or unexported fields
}
Header holds HTTP header fields in arrival order with lowercase names and case-insensitive lookups. Returned byte slices are valid only until the header changes or the handler returns.
Example ¶
package main
import (
"fmt"
"github.com/erizeta/qilat"
)
func main() {
var h qilat.Header
h.Set("Content-Type", "application/json")
h.Add("Vary", "Accept-Encoding")
h.Add("vary", "Origin")
fmt.Println(h.Get("CONTENT-TYPE"))
fmt.Println(len(h.PeekAll("Vary")))
for name, value := range h.All() {
fmt.Printf("%s: %s\n", name, value)
}
}
Output: application/json 2 content-type: application/json vary: Accept-Encoding vary: Origin
func (*Header) Add ¶
Add appends a field. Existing fields with the same name are kept. It panics and sanitizes like Header.Set.
func (*Header) AddBytes ¶
AddBytes is like Header.Add but takes the value as bytes. value is copied.
func (*Header) All ¶
All returns an iterator over every field in order. Names are lowercase. The yielded slices follow the same rules as Header.Peek.
func (*Header) Get ¶
Get returns a copy of the first value for name, or "" if there is none. Unlike Header.Peek the result stays valid after the handler returns, at the cost of an allocation.
func (*Header) Peek ¶
Peek returns the value of the first field with the given name, or nil if there is none. The result must not be modified and is valid only until the header changes or the handler returns.
func (*Header) PeekAll ¶
PeekAll returns the values of every field with the given name, in order. It allocates the returned slice; the values follow the same rules as Header.Peek.
func (*Header) Reset ¶
func (h *Header) Reset()
Reset removes all fields while keeping allocated memory for reuse.
type Hijacker ¶
type Hijacker interface {
Hijack() (net.Conn, *bufio.ReadWriter, error)
}
Hijacker lets a handler take over the connection. Every qilat ResponseWriter implements it; Hijack returns ErrNotSupported when the protocol has no connection to hand over (HTTP/2, HTTP/3).
type Request ¶
type Request struct {
// Header holds the request header fields. The Host header is available from [Request.Host] instead.
Header *Header
// Body is the request body. It is never nil and returns io.EOF immediately when there is no body.
Body io.Reader
// contains filtered or unexported fields
}
Request is an HTTP request received by a server, or one sent by a Client. It is pooled: a server's Request and every byte slice it returns are valid only until ServeHTTP returns, and a client's until ReleaseRequest. Use Request.Clone to keep a request longer.
func AcquireRequest ¶
func AcquireRequest() *Request
AcquireRequest returns an empty client request from a pool. Give it back with ReleaseRequest.
func (*Request) Clone ¶
Clone returns a deep copy of r with context ctx that stays valid after the handler returns. The body is not copied: the clone's Body is empty. It panics if ctx is nil.
func (*Request) ContentLength ¶
ContentLength returns the declared body length, or -1 if it is unknown.
func (*Request) Context ¶
Context returns the request context. It is canceled when the client disconnects or ServeHTTP returns. The first call allocates a small context that is never reused, so it is safe to pass to goroutines.
func (*Request) Host ¶
Host returns the Host header (HTTP/1.1) or :authority pseudo-header (HTTP/2, HTTP/3).
func (*Request) PathValue ¶
PathValue returns a copy of the value of the named wildcard in the ServeMux pattern that matched the request, or a value set by Request.SetPathValue. It returns "" if there is no such value. Like net/http.Request.PathValue, the value is percent-decoded.
func (*Request) Pattern ¶
Pattern returns the ServeMux pattern that matched the request, or "" if none did.
func (*Request) PeekPathValue ¶
PeekPathValue is like Request.PathValue but returns the value without copying it, or nil if there is none. The result must not be modified and is valid only until ServeHTTP returns.
func (*Request) Proto ¶
Proto returns the protocol version: "HTTP/1.0", "HTTP/1.1", "HTTP/2.0", or "HTTP/3.0".
func (*Request) RemoteAddr ¶
RemoteAddr returns the network address of the client.
func (*Request) SetBody ¶
SetBody sets the request body. contentLength is -1 when unknown, which sends a chunked body on HTTP/1.1. A body that implements io.Seeker can be sent again after a 307 or 308 redirect.
func (*Request) SetMethod ¶
SetMethod sets the request method. It panics if method is not an RFC 9110 token.
func (*Request) SetPathValue ¶
SetPathValue sets the value returned by PathValue(name). value is copied and not unescaped.
func (*Request) SetRequestURI ¶
SetRequestURI sets the URL a client request is sent to, such as "https://example.com/path?q=1", and its Host header.
func (*Request) TLS ¶
func (r *Request) TLS() *tls.ConnectionState
TLS returns the TLS connection state, or nil for unencrypted connections.
type Response ¶
type Response struct {
// Header holds the response header fields.
Header *Header
// Body streams the response body. It is never nil. Read it to EOF, or release the response, so the
// connection can be reused.
Body io.Reader
// contains filtered or unexported fields
}
Response is an HTTP response received by a Client. It is pooled: the Response and every byte slice it returns are valid until ReleaseResponse.
func AcquireResponse ¶
func AcquireResponse() *Response
AcquireResponse returns an empty response from a pool. Give it back with ReleaseResponse.
func (*Response) Close ¶
Close abandons the unread body and frees its connection for reuse when possible. ReleaseResponse calls it.
func (*Response) ContentLength ¶
ContentLength returns the declared body length, or -1 if it is unknown.
func (*Response) ReadBody ¶
ReadBody reads the whole body into a buffer owned by resp and returns it. It fails with ErrResponseBodyTooLarge past Transport.MaxResponseBodySize.
func (*Response) StatusCode ¶
StatusCode returns the status code, such as 200.
type ResponseWriter ¶
type ResponseWriter interface {
// Header returns the response header. Content-Length is read when WriteHeader is called, and changes made
// after the header is sent (on Flush, a large Write, or when ServeHTTP returns) have no effect.
Header() *Header
// WriteHeader sends the status code. Only the first call has an effect; it panics on codes outside 100–999.
WriteHeader(statusCode int)
// Write sends body bytes, calling WriteHeader(200) first if needed.
Write(p []byte) (int, error)
WriteString(s string) (int, error)
// Flush sends the header and any buffered body bytes to the client.
Flush() error
}
ResponseWriter is used by a Handler to construct a response. It must not be used after ServeHTTP returns.
type RoundTripper ¶
RoundTripper sends one request and receives its response, like net/http.RoundTripper. The response body is read through resp afterwards, and resp must be released or closed when done.
type ServeMux ¶
type ServeMux struct {
// contains filtered or unexported fields
}
ServeMux is an HTTP request multiplexer with the pattern syntax, precedence, and redirect behavior of net/http.ServeMux (Go 1.22 and later). Matching a request does not allocate.
Patterns have the form "[METHOD ][HOST]/[PATH]", for example "GET /users/{id}", "example.com/", or "/files/{path...}". A wildcard {name} matches one path segment and {name...} the rest of the path; their values are available from Request.PathValue. {$} matches only the end of the path, and a pattern ending in a slash matches every path below it. GET patterns also match HEAD requests.
When several patterns match, the most specific one wins; a pattern with a host beats one without. Registering a pattern that conflicts with an existing one (neither is more specific) panics.
Requests whose path is not clean (containing ".", "..", or repeated slashes) are redirected to the clean path, and a request for "/dir" is redirected to "/dir/" when only "/dir/" is registered. Both redirects use 307 Temporary Redirect, like net/http since Go 1.26. A path that matches no pattern gets 404, and one that matches only with another method gets 405 with an Allow header. CONNECT requests are matched without cleaning the path or stripping the port.
Example ¶
package main
import (
"fmt"
"net"
"github.com/erizeta/qilat"
)
// serve runs h on a loopback port and returns its base URL and a function that stops it.
func serve(h qilat.Handler) (string, func()) {
ln, err := net.Listen("tcp", "127.0.0.1:0")
if err != nil {
panic(err)
}
srv := &qilat.Server{Handler: h}
done := make(chan struct{})
go func() {
defer close(done)
_ = srv.Serve(ln)
}()
return "http://" + ln.Addr().String(), func() {
_ = srv.Close()
<-done
}
}
// get fetches url with a client of its own and returns the status and body.
func get(url string) (int, string) {
tr := &qilat.Transport{}
defer tr.CloseIdleConnections()
req := qilat.AcquireRequest()
defer qilat.ReleaseRequest(req)
if err := req.SetRequestURI(url); err != nil {
panic(err)
}
resp := qilat.AcquireResponse()
defer qilat.ReleaseResponse(resp)
if err := (&qilat.Client{Transport: tr}).Do(req, resp); err != nil {
panic(err)
}
body, err := resp.ReadBody()
if err != nil {
panic(err)
}
return resp.StatusCode(), string(body)
}
func main() {
mux := qilat.NewServeMux()
mux.HandleFunc("GET /users/{id}", func(w qilat.ResponseWriter, r *qilat.Request) {
_, _ = w.WriteString("user " + r.PathValue("id"))
})
url, stop := serve(mux)
defer stop()
fmt.Println(get(url + "/users/42"))
fmt.Println(get(url + "/nope"))
}
Output: 200 user 42 404 404 page not found
func (*ServeMux) Handle ¶
Handle registers handler for pattern. It panics if pattern is invalid or conflicts with a registered pattern.
func (*ServeMux) HandleFunc ¶
func (mux *ServeMux) HandleFunc(pattern string, handler func(ResponseWriter, *Request))
HandleFunc registers handler for pattern. It panics like ServeMux.Handle.
func (*ServeMux) Handler ¶
Handler returns the handler to use for r and the pattern that matched it. It never returns nil: when nothing matches, it returns a handler that replies 404 or 405 and an empty pattern, and when the request must be redirected, a redirecting handler and the pattern that will match after the redirect. Handler does not set path values, so r.PathValue keeps returning what it did before.
func (*ServeMux) ServeHTTP ¶
func (mux *ServeMux) ServeHTTP(w ResponseWriter, r *Request)
ServeHTTP dispatches the request to the handler whose pattern most closely matches it, setting Request.Pattern and the values returned by Request.PathValue.
type Server ¶
type Server struct {
// Handler serves every request. It must not be nil.
Handler Handler
// TLSConfig is used by ServeTLS and ListenAndServeTLS. It is cloned, never modified.
TLSConfig *tls.Config
// ReadHeaderTimeout bounds reading a request line and header. Zero means DefaultReadHeaderTimeout.
ReadHeaderTimeout time.Duration
// ReadTimeout bounds reading a whole request, body included. Zero means no limit, so slow uploads work.
ReadTimeout time.Duration
// WriteTimeout bounds writing a response, measured from the end of the request header. Zero means no limit.
WriteTimeout time.Duration
// IdleTimeout bounds the wait for the next keep-alive request. Zero means DefaultIdleTimeout.
IdleTimeout time.Duration
// MaxHeaderBytes limits the request line and header. Zero means DefaultMaxHeaderBytes.
MaxHeaderBytes int
// MaxRequestBodySize limits the request body; larger bodies get 413. Zero means DefaultMaxRequestBodySize.
MaxRequestBodySize int64
// MaxConcurrentConns limits open connections; Accept waits while the limit is reached. Zero means no limit.
MaxConcurrentConns int
// HTTP2 configures HTTP/2, which TLS connections negotiate through ALPN unless DisableHTTP2 is set.
HTTP2 HTTP2Config
// H2C serves cleartext HTTP/2 to clients that start with the HTTP/2 preface (prior knowledge), on the
// same listener as HTTP/1.1. The deprecated HTTP/1.1 Upgrade to h2c is not supported.
H2C bool
// DisableHTTP2 serves HTTP/1.1 only, even if TLSConfig.NextProtos lists "h2".
DisableHTTP2 bool
// HTTP3 configures HTTP/3, which ListenAndServeTLS and ServeQUIC serve over UDP.
HTTP3 HTTP3Config
// DisableHTTP3 makes ListenAndServeTLS serve TCP only.
DisableHTTP3 bool
// ReduceMemoryUsage returns read buffers to a pool while connections are idle, costing one extra read per request.
ReduceMemoryUsage bool
// DebugPoison makes handlers that use a request or ResponseWriter after ServeHTTP returns panic. It disables pooling.
DebugPoison bool
// BaseContext returns the base context for requests from ln, which is nil for HTTP/3. If BaseContext is nil,
// context.Background is used.
BaseContext func(ln net.Listener) context.Context
// Logger receives handler panics and connection errors. If nil, slog.Default is used.
Logger *slog.Logger
// contains filtered or unexported fields
}
Server is an HTTP server. Unlike net/http, zero limits mean safe defaults rather than no limit; set a negative value to disable a limit. A Server must not be copied after first use.
func (*Server) Close ¶
Close immediately closes all listeners and connections, except hijacked ones, and cancels running requests.
func (*Server) ListenAndServe ¶
ListenAndServe listens on the TCP address addr and serves HTTP/1.1, and cleartext HTTP/2 if H2C is set. An empty addr means ":http".
func (*Server) ListenAndServeTLS ¶
ListenAndServeTLS listens on addr and serves HTTPS: HTTP/2 and HTTP/1.1 over TCP, chosen through ALPN, and HTTP/3 over UDP on the same port unless DisableHTTP3 is set. HTTP/1.1 and HTTP/2 responses then carry an Alt-Svc header so clients can switch to HTTP/3. If either listener fails, the other is closed and the error is returned. An empty addr means ":https". The certificate files may be empty if TLSConfig already holds certificates.
func (*Server) Serve ¶
Serve accepts connections on ln and serves each in its own goroutine. It always closes ln and returns ErrServerClosed after Shutdown or Close.
func (*Server) ServeQUIC ¶
func (s *Server) ServeQUIC(pc net.PacketConn) error
ServeQUIC serves HTTP/3 on the UDP connection pc, using TLSConfig, which must hold certificates. It always closes pc, after the connections it accepted have finished, and returns ErrServerClosed after Shutdown or Close. While it runs, HTTP/1.1 and HTTP/2 responses from this Server advertise it with Alt-Svc.
func (*Server) ServeTLS ¶
ServeTLS serves HTTPS on ln. See Server.ListenAndServeTLS for the certificate arguments. It serves HTTP/1.1 and HTTP/2 only; use Server.ServeQUIC to add HTTP/3.
func (*Server) Shutdown ¶
Shutdown stops accepting connections, closes idle ones, and waits for active requests to finish. If ctx ends first, the contexts of running requests are canceled and ctx's error is returned.
Example ¶
package main
import (
"context"
"fmt"
"net"
"time"
"github.com/erizeta/qilat"
)
// get fetches url with a client of its own and returns the status and body.
func get(url string) (int, string) {
tr := &qilat.Transport{}
defer tr.CloseIdleConnections()
req := qilat.AcquireRequest()
defer qilat.ReleaseRequest(req)
if err := req.SetRequestURI(url); err != nil {
panic(err)
}
resp := qilat.AcquireResponse()
defer qilat.ReleaseResponse(resp)
if err := (&qilat.Client{Transport: tr}).Do(req, resp); err != nil {
panic(err)
}
body, err := resp.ReadBody()
if err != nil {
panic(err)
}
return resp.StatusCode(), string(body)
}
func main() {
ln, err := net.Listen("tcp", "127.0.0.1:0")
if err != nil {
panic(err)
}
srv := &qilat.Server{Handler: qilat.HandlerFunc(func(w qilat.ResponseWriter, _ *qilat.Request) {
_, _ = w.WriteString("ok")
})}
served := make(chan error, 1)
go func() { served <- srv.Serve(ln) }()
fmt.Println(get("http://" + ln.Addr().String()))
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
defer cancel()
if err := srv.Shutdown(ctx); err != nil {
panic(err)
}
fmt.Println(<-served)
}
Output: 200 ok qilat: Server closed
type TrailerSetter ¶
type TrailerSetter interface {
Trailer() *Header
}
TrailerSetter lets a handler send trailer fields after the body. Every qilat ResponseWriter implements it. Fields set on the returned Header before ServeHTTP returns are sent as trailers. HTTP/2 always delivers them. HTTP/1.1 sends them in a chunked body, so they are dropped when the handler sets Content-Length itself or the client speaks HTTP/1.0.
type Transport ¶
type Transport struct {
// DialContext opens connections; nil means a net.Dialer. It is called with network "tcp", and with "udp"
// for HTTP/3, where returning an error makes the request fall back to TCP.
DialContext func(ctx context.Context, network, addr string) (net.Conn, error)
// DialTimeout limits connecting; zero means DefaultDialTimeout.
DialTimeout time.Duration
// TLSClientConfig configures TLS for HTTPS; nil means the defaults. It is cloned for each connection.
TLSClientConfig *tls.Config
// TLSHandshakeTimeout limits the TLS handshake; zero means DefaultTLSHandshakeTimeout.
TLSHandshakeTimeout time.Duration
// Proxy returns the proxy for a request, or nil for none; nil means no proxy. HTTPS requests go through
// an HTTP CONNECT tunnel, plain HTTP requests are sent to the proxy in absolute form. See ProxyFromEnvironment.
Proxy func(req *Request) (*url.URL, error)
// MaxConnsPerHost limits HTTP/1.1 connections per host, counting those in use; zero means no limit.
// Requests wait for a free connection.
MaxConnsPerHost int
// MaxIdleConnsPerHost limits idle HTTP/1.1 connections kept per host; zero means DefaultMaxIdleConnsPerHost.
// A negative value disables keep-alive.
MaxIdleConnsPerHost int
// IdleConnTimeout closes connections idle for this long; zero means DefaultIdleConnTimeout.
IdleConnTimeout time.Duration
// ResponseHeaderTimeout limits waiting for the response header after the request is sent; zero means no limit.
ResponseHeaderTimeout time.Duration
// MaxResponseHeaderBytes limits the response header; zero means DefaultMaxResponseHeaderBytes.
MaxResponseHeaderBytes int
// MaxResponseBodySize limits Response.ReadBody; zero means DefaultMaxResponseBodySize, negative no limit.
// Reading Response.Body directly is not limited.
MaxResponseBodySize int64
// DisableHTTP2 keeps HTTPS requests on HTTP/1.1.
DisableHTTP2 bool
// H2C sends plain http:// requests over HTTP/2 with prior knowledge (RFC 9113 section 3.3), for servers
// known to accept it, such as a qilat Server with H2C set. It is not used through a proxy.
H2C bool
// EnableHTTP3 sends HTTPS requests over HTTP/3 to hosts that advertised it with Alt-Svc in an earlier
// response. When QUIC cannot connect within HTTP3DialTimeout, as when UDP is blocked, the request falls
// back to TCP and the host is reached over TCP for a while. HTTP/3 is not used through a proxy.
EnableHTTP3 bool
// HTTP3DialTimeout limits the QUIC handshake before falling back to TCP; zero means DefaultHTTP3DialTimeout.
HTTP3DialTimeout time.Duration
// contains filtered or unexported fields
}
Transport is a RoundTripper that keeps connections open for reuse. HTTPS requests use HTTP/2 when the server offers it through ALPN, multiplexed on one connection per host; other requests use a pool of HTTP/1.1 connections. A zero Transport is ready to use, and safe for concurrent use.
Like the Server, zero fields mean safe defaults rather than "unlimited".
func (*Transport) CloseIdleConnections ¶
func (t *Transport) CloseIdleConnections()
CloseIdleConnections closes connections that are not carrying a request.
type URI ¶
type URI struct {
// contains filtered or unexported fields
}
URI is a parsed request-target (RFC 9112 section 3.2). Byte slices it returns are valid until the next Parse or until the handler returns. The zero value is an empty origin-form target.
Example ¶
package main
import (
"fmt"
"github.com/erizeta/qilat"
)
func main() {
var u qilat.URI
if err := u.Parse([]byte("/users/j%C3%B6rg?tab=posts&page=2")); err != nil {
panic(err)
}
fmt.Println(string(u.RawPath()))
fmt.Println(string(u.Path()))
fmt.Println(u.Query().Get("page"))
}
Output: /users/j%C3%B6rg /users/jörg 2
func (*URI) Host ¶
Host returns the authority of an absolute- or authority-form target, or nil for other forms.
func (*URI) Parse ¶
Parse copies target and parses it in origin, absolute, authority, or asterisk form. Path decoding and query parsing are deferred until URI.Path and URI.Query are called.
func (*URI) Path ¶
Path returns the percent-decoded path. An encoded slash (%2F) decodes to '/'; use URI.RawPath when the distinction matters. '+' is not decoded in paths.
func (*URI) RawPath ¶
RawPath returns the path still percent-encoded. It is empty for authority-form targets.
Source Files
¶
Directories
¶
| Path | Synopsis |
|---|---|
|
examples
|
|
|
client
command
Command client fetches a URL with qilat's Client and prints the protocol, status, and body size.
|
Command client fetches a URL with qilat's Client and prints the protocol, status, and body size. |
|
core
command
Command core shows the HTTP building blocks available in qilat today.
|
Command core shows the HTTP building blocks available in qilat today. |
|
h3-dev-cert
command
Command h3-dev-cert serves HTTP/1.1, HTTP/2, and HTTP/3 on one port with a generated development certificate.
|
Command h3-dev-cert serves HTTP/1.1, HTTP/2, and HTTP/3 on one port with a generated development certificate. |
|
hello
command
Command hello serves a minimal HTTP/1.1 application with qilat.
|
Command hello serves a minimal HTTP/1.1 application with qilat. |
|
http2
command
Command http2 serves HTTP/2 over TLS (with HTTP/1.1 fallback) and cleartext HTTP/2 (h2c) with qilat.
|
Command http2 serves HTTP/2 over TLS (with HTTP/1.1 fallback) and cleartext HTTP/2 (h2c) with qilat. |
|
routing
command
Command routing serves a small in-memory key-value API with qilat's ServeMux.
|
Command routing serves a small in-memory key-value API with qilat's ServeMux. |
|
static
command
Command static serves the files embedded from ./public with gzip compression, next to a small JSON API.
|
Command static serves the files embedded from ./public with gzip compression, next to a small JSON API. |
|
internal
|
|
|
bytesconv
Package bytesconv provides allocation-free conversions and ASCII helpers for HTTP parsing.
|
Package bytesconv provides allocation-free conversions and ASCII helpers for HTTP parsing. |
|
exchange
Package exchange is the contract between protocol implementations and the qilat core.
|
Package exchange is the contract between protocol implementations and the qilat core. |
|
h1
Package h1 implements the HTTP/1.1 server protocol (RFC 9112) on top of the exchange contract.
|
Package h1 implements the HTTP/1.1 server protocol (RFC 9112) on top of the exchange contract. |
|
h2
Package h2 implements the server side of HTTP/2 (RFC 9113) on top of internal/exchange.
|
Package h2 implements the server side of HTTP/2 (RFC 9113) on top of internal/exchange. |
|
h3
Package h3 implements the server side of HTTP/3 (RFC 9114) on raw QUIC streams from quic-go.
|
Package h3 implements the server side of HTTP/3 (RFC 9114) on raw QUIC streams from quic-go. |
|
httpfields
Package httpfields holds the header field rules HTTP/2 and HTTP/3 share (RFC 9113 section 8, RFC 9114 section 4) on top of their different header compression.
|
Package httpfields holds the header field rules HTTP/2 and HTTP/3 share (RFC 9113 section 8, RFC 9114 section 4) on top of their different header compression. |