mirror of
https://git.sr.ht/~adnano/go-gemini
synced 2024-11-23 21:02:28 +01:00
113 lines
2.2 KiB
Go
113 lines
2.2 KiB
Go
package gemini
|
|
|
|
import (
|
|
"bytes"
|
|
"context"
|
|
"sync"
|
|
"time"
|
|
)
|
|
|
|
// TimeoutHandler returns a Handler that runs h with the given time limit.
|
|
//
|
|
// The new Handler calls h.ServeGemini to handle each request, but
|
|
// if a call runs for longer than its time limit, the handler responds with a
|
|
// 40 Temporary Failure error. After such a timeout, writes by h to its
|
|
// ResponseWriter will return ErrHandlerTimeout.
|
|
func TimeoutHandler(h Handler, dt time.Duration) Handler {
|
|
return &timeoutHandler{
|
|
h: h,
|
|
dt: dt,
|
|
}
|
|
}
|
|
|
|
type timeoutHandler struct {
|
|
h Handler
|
|
dt time.Duration
|
|
}
|
|
|
|
func (t *timeoutHandler) ServeGemini(ctx context.Context, w ResponseWriter, r *Request) {
|
|
ctx, cancel := context.WithTimeout(ctx, t.dt)
|
|
defer cancel()
|
|
|
|
done := make(chan struct{})
|
|
tw := &timeoutWriter{}
|
|
panicChan := make(chan interface{}, 1)
|
|
go func() {
|
|
defer func() {
|
|
if p := recover(); p != nil {
|
|
panicChan <- p
|
|
}
|
|
}()
|
|
t.h.ServeGemini(ctx, tw, r)
|
|
close(done)
|
|
}()
|
|
|
|
select {
|
|
case p := <-panicChan:
|
|
panic(p)
|
|
case <-done:
|
|
tw.mu.Lock()
|
|
defer tw.mu.Unlock()
|
|
if !tw.wroteHeader {
|
|
tw.status = StatusSuccess
|
|
}
|
|
w.WriteHeader(tw.status, tw.meta)
|
|
w.Write(tw.b.Bytes())
|
|
case <-ctx.Done():
|
|
tw.mu.Lock()
|
|
defer tw.mu.Unlock()
|
|
w.WriteHeader(StatusTemporaryFailure, "Timeout")
|
|
tw.timedOut = true
|
|
}
|
|
}
|
|
|
|
type timeoutWriter struct {
|
|
mu sync.Mutex
|
|
b bytes.Buffer
|
|
status Status
|
|
meta string
|
|
mediatype string
|
|
wroteHeader bool
|
|
timedOut bool
|
|
}
|
|
|
|
func (w *timeoutWriter) SetMediaType(mediatype string) {
|
|
w.mu.Lock()
|
|
defer w.mu.Unlock()
|
|
w.mediatype = mediatype
|
|
}
|
|
|
|
func (w *timeoutWriter) Write(b []byte) (int, error) {
|
|
w.mu.Lock()
|
|
defer w.mu.Unlock()
|
|
if w.timedOut {
|
|
return 0, ErrHandlerTimeout
|
|
}
|
|
if !w.wroteHeader {
|
|
w.writeHeaderLocked(StatusSuccess, w.mediatype)
|
|
}
|
|
return w.b.Write(b)
|
|
}
|
|
|
|
func (w *timeoutWriter) WriteHeader(status Status, meta string) {
|
|
w.mu.Lock()
|
|
defer w.mu.Unlock()
|
|
if w.timedOut {
|
|
return
|
|
}
|
|
w.writeHeaderLocked(status, meta)
|
|
}
|
|
|
|
func (w *timeoutWriter) writeHeaderLocked(status Status, meta string) {
|
|
if w.wroteHeader {
|
|
return
|
|
}
|
|
w.status = status
|
|
w.meta = meta
|
|
w.wroteHeader = true
|
|
}
|
|
|
|
func (w *timeoutWriter) Flush() error {
|
|
return nil
|
|
}
|