Initial QSfera import
This commit is contained in:
+185
@@ -0,0 +1,185 @@
|
||||
package httprc
|
||||
|
||||
import (
|
||||
"context"
|
||||
"net/http"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/lestrrat-go/httprc/v3/errsink"
|
||||
"github.com/lestrrat-go/httprc/v3/proxysink"
|
||||
"github.com/lestrrat-go/httprc/v3/tracesink"
|
||||
)
|
||||
|
||||
// setupSink creates and starts a proxy for the given sink if it's not a Nop sink
|
||||
// Returns the sink to use and a cancel function that should be chained with the original cancel
|
||||
func setupSink[T any, S proxysink.Backend[T], NopType any](ctx context.Context, sink S, wg *sync.WaitGroup) (S, context.CancelFunc) {
|
||||
if _, ok := any(sink).(NopType); ok {
|
||||
return sink, func() {}
|
||||
}
|
||||
|
||||
proxy := proxysink.New[T](sink)
|
||||
wg.Add(1)
|
||||
go func(ctx context.Context, wg *sync.WaitGroup, proxy *proxysink.Proxy[T]) {
|
||||
defer wg.Done()
|
||||
proxy.Run(ctx)
|
||||
}(ctx, wg, proxy)
|
||||
|
||||
// proxy can be converted to one of the sink subtypes
|
||||
s, ok := any(proxy).(S)
|
||||
if !ok {
|
||||
panic("type assertion failed: proxy cannot be converted to type S")
|
||||
}
|
||||
return s, proxy.Close
|
||||
}
|
||||
|
||||
// Client is the main entry point for the httprc package.
|
||||
type Client struct {
|
||||
mu sync.Mutex
|
||||
httpcl HTTPClient
|
||||
numWorkers int
|
||||
running bool
|
||||
errSink ErrorSink
|
||||
traceSink TraceSink
|
||||
wl Whitelist
|
||||
defaultMaxInterval time.Duration
|
||||
defaultMinInterval time.Duration
|
||||
}
|
||||
|
||||
// NewClient creates a new `httprc.Client` object.
|
||||
//
|
||||
// By default ALL urls are allowed. This may not be suitable for you if
|
||||
// are using this in a production environment. You are encouraged to specify
|
||||
// a whitelist using the `WithWhitelist` option.
|
||||
//
|
||||
// NOTE: In future versions, this function signature should be changed to
|
||||
// return an error to properly handle option parsing failures.
|
||||
func NewClient(options ...NewClientOption) *Client {
|
||||
//nolint:staticcheck
|
||||
var errSink ErrorSink = errsink.NewNop()
|
||||
//nolint:staticcheck
|
||||
var traceSink TraceSink = tracesink.NewNop()
|
||||
var wl Whitelist = InsecureWhitelist{}
|
||||
var httpcl HTTPClient = http.DefaultClient
|
||||
|
||||
defaultMinInterval := DefaultMinInterval
|
||||
defaultMaxInterval := DefaultMaxInterval
|
||||
|
||||
numWorkers := DefaultWorkers
|
||||
for _, option := range options {
|
||||
switch option.Ident() {
|
||||
case identHTTPClient{}:
|
||||
_ = option.Value(&httpcl)
|
||||
case identWorkers{}:
|
||||
_ = option.Value(&numWorkers)
|
||||
case identErrorSink{}:
|
||||
_ = option.Value(&errSink)
|
||||
case identTraceSink{}:
|
||||
_ = option.Value(&traceSink)
|
||||
case identWhitelist{}:
|
||||
_ = option.Value(&wl)
|
||||
}
|
||||
}
|
||||
|
||||
if numWorkers <= 0 {
|
||||
numWorkers = 1
|
||||
}
|
||||
return &Client{
|
||||
httpcl: httpcl,
|
||||
numWorkers: numWorkers,
|
||||
errSink: errSink,
|
||||
traceSink: traceSink,
|
||||
wl: wl,
|
||||
|
||||
defaultMinInterval: defaultMinInterval,
|
||||
defaultMaxInterval: defaultMaxInterval,
|
||||
}
|
||||
}
|
||||
|
||||
// Start sets the client into motion. It will start a number of worker goroutines,
|
||||
// and return a Controller object that you can use to control the execution of
|
||||
// the client.
|
||||
//
|
||||
// If you attempt to call Start more than once, it will return an error.
|
||||
func (c *Client) Start(octx context.Context) (Controller, error) {
|
||||
c.mu.Lock()
|
||||
if c.running {
|
||||
c.mu.Unlock()
|
||||
return nil, errAlreadyRunning
|
||||
}
|
||||
c.running = true
|
||||
c.mu.Unlock()
|
||||
|
||||
// DON'T CANCEL THIS IN THIS METHOD! It's the responsibility of the
|
||||
// controller to cancel this context.
|
||||
ctx, cancel := context.WithCancel(octx)
|
||||
|
||||
var donewg sync.WaitGroup
|
||||
|
||||
// start proxy goroutines that will accept sink requests
|
||||
// and forward them to the appropriate sink
|
||||
errSink, errCancel := setupSink[error, ErrorSink, errsink.Nop](ctx, c.errSink, &donewg)
|
||||
traceSink, traceCancel := setupSink[string, TraceSink, tracesink.Nop](ctx, c.traceSink, &donewg)
|
||||
|
||||
// Chain the cancel functions
|
||||
ocancel := cancel
|
||||
cancel = func() {
|
||||
ocancel()
|
||||
errCancel()
|
||||
traceCancel()
|
||||
}
|
||||
|
||||
chbuf := c.numWorkers + 1
|
||||
incoming := make(chan any, chbuf)
|
||||
outgoing := make(chan Resource, chbuf)
|
||||
syncoutgoing := make(chan synchronousRequest, chbuf)
|
||||
|
||||
var readywg sync.WaitGroup
|
||||
readywg.Add(c.numWorkers)
|
||||
donewg.Add(c.numWorkers)
|
||||
for range c.numWorkers {
|
||||
wrk := worker{
|
||||
incoming: incoming,
|
||||
next: outgoing,
|
||||
nextsync: syncoutgoing,
|
||||
errSink: errSink,
|
||||
traceSink: traceSink,
|
||||
httpcl: c.httpcl,
|
||||
}
|
||||
go wrk.Run(ctx, &readywg, &donewg)
|
||||
}
|
||||
|
||||
tickInterval := oneDay
|
||||
ctrl := &controller{
|
||||
cancel: cancel,
|
||||
incoming: incoming,
|
||||
shutdown: make(chan struct{}),
|
||||
traceSink: traceSink,
|
||||
wl: c.wl,
|
||||
}
|
||||
|
||||
backend := &ctrlBackend{
|
||||
items: make(map[string]Resource),
|
||||
outgoing: outgoing,
|
||||
syncoutgoing: syncoutgoing,
|
||||
incoming: incoming,
|
||||
traceSink: traceSink,
|
||||
tickInterval: tickInterval,
|
||||
check: time.NewTicker(tickInterval),
|
||||
|
||||
defaultMinInterval: c.defaultMinInterval,
|
||||
defaultMaxInterval: c.defaultMaxInterval,
|
||||
}
|
||||
donewg.Add(1)
|
||||
readywg.Add(1)
|
||||
go backend.loop(ctx, &readywg, &donewg)
|
||||
|
||||
go func(wg *sync.WaitGroup, ch chan struct{}) {
|
||||
wg.Wait()
|
||||
close(ch)
|
||||
}(&donewg, ctrl.shutdown)
|
||||
|
||||
readywg.Wait()
|
||||
|
||||
return ctrl, nil
|
||||
}
|
||||
Reference in New Issue
Block a user