genericapi

package
v0.0.18 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Aug 22, 2026 License: MIT Imports: 47 Imported by: 0

Documentation

Index

Constants

View Source
const (
	CodeAPINotFound                = "api_not_found"
	CodeCacheNotReady              = "api_cache_not_ready"
	CodeInvalidRequest             = "invalid_request"
	CodeInvalidUpgrade             = "invalid_upgrade"
	CodeMethodNotAllowed           = "method_not_allowed"
	CodeAPIForbidden               = "api_forbidden"
	CodeInsufficientQuota          = "insufficient_quota"
	CodePermissionFactsUnavailable = "permission_facts_unavailable"
	CodeQuotaFactsUnavailable      = "quota_facts_unavailable"
	CodeUnavailable                = "api_unavailable"
	CodeExecutionAgentIncompatible = "execution_agent_incompatible"
	CodeRateLimited                = "rate_limited"
)
View Source
const (
	ProtocolHTTP      = "http"
	ProtocolWebSocket = "websocket"
)

Variables

View Source
var (
	ErrAPICacheNotReady           = cache.ErrAPICacheNotReady
	ErrPermissionFactsUnavailable = errors.New("permission facts unavailable")
	ErrAPIForbidden               = errors.New("API forbidden")
	ErrQuotaFactsUnavailable      = errors.New("quota facts unavailable")
	ErrInsufficientQuota          = errors.New("insufficient quota")
	ErrExecutionUnavailable       = errors.New("api execution unavailable")
	ErrExecutionAgentIncompatible = errors.New("execution agent incompatible")
	ErrAPIRateLimited             = errors.New("generic API rate limited")
)
View Source
var (
	ErrInvalidUpstreamRequest   = errors.New("invalid upstream request")
	ErrUnsafeUpstreamHeader     = errors.New("unsafe upstream header")
	ErrUnsafeUpstreamTrailer    = errors.New("unsafe upstream trailer")
	ErrUnsafeUpstreamCredential = errors.New("unsafe upstream credential")
)
View Source
var (
	ErrInvalidUpstreamResponse = errors.New("invalid upstream response")
)
View Source
var ErrUploadIdleTimeout = errors.New("generic API upload idle timeout")

ErrUploadIdleTimeout means an HTTP request body made no forwarding progress for the configured continuous idle interval.

Functions

func ErrorAllow

func ErrorAllow(err error) string

func ErrorCode

func ErrorCode(err error) string

func RegisterRoutes

func RegisterRoutes(router gin.IRoutes, handler *Handler)

RegisterRoutes binds the service root and its catch-all under an authenticated /v1 group.

func RejectRedirect

func RejectRedirect(*http.Request, []*http.Request) error

RejectRedirect is the Generic API http.Client redirect policy.

func RequestIDMiddleware

func RequestIDMiddleware() gin.HandlerFunc

RequestIDMiddleware canonicalizes the request ID before Generic API auth can produce a gateway-shaped failure response.

func WriteTokenAuthFailure

func WriteTokenAuthFailure(c *gin.Context, failure auth.TokenAuthFailure)

WriteTokenAuthFailure adapts the shared TokenAuth failure fact to the Generic API error envelope without coupling auth back to genericapi.

Types

type APIAgentRouteFinder

type APIAgentRouteFinder interface {
	FindTokenRoute(tokenID uint, model string) *models.AgentRoute
	FindAPIRouteRoute(routeID uint) *models.AgentRoute
	FindAPIServiceRoute(serviceID uint) *models.AgentRoute
}

type APIBreakerCompletion

type APIBreakerCompletion struct {
	Result      *apiattempt.APIExecutionResult
	Err         error
	ClientAbort APIClientAbortReason
}

APIBreakerCompletion explicitly separates a client-originated abort from upstream transport errors. Err is never classified by error text or by context sentinels alone.

type APIBreakerFinder

type APIBreakerFinder interface {
	Healthy(upstreamID uint) bool
	TryAcquire(upstreamID uint) (APIBreakerPermit, bool)
}

type APIBreakerPermit

type APIBreakerPermit interface {
	Finish(completion APIBreakerCompletion)
}

type APIBreakerRegistry

type APIBreakerRegistry struct {
	// contains filtered or unexported fields
}

func NewAPIBreakerRegistry

func NewAPIBreakerRegistry(settings APIBreakerSettingsReader, failureStatus APIStatusFailurePredicate) *APIBreakerRegistry

func (*APIBreakerRegistry) Clear

func (r *APIBreakerRegistry) Clear()

func (*APIBreakerRegistry) Delete

func (r *APIBreakerRegistry) Delete(upstreamID uint)

func (*APIBreakerRegistry) Healthy

func (r *APIBreakerRegistry) Healthy(upstreamID uint) bool

Healthy is a non-consuming health snapshot used before weighted selection. A concurrent state change may still make TryAcquire fail, in which case the request fails closed instead of selecting a second upstream.

func (*APIBreakerRegistry) SnapshotBreakers

func (r *APIBreakerRegistry) SnapshotBreakers() []APIBreakerSnapshot

func (*APIBreakerRegistry) TryAcquire

func (r *APIBreakerRegistry) TryAcquire(upstreamID uint) (APIBreakerPermit, bool)

type APIBreakerSettingsReader

type APIBreakerSettingsReader interface {
	Settings() settings.AgentSettings
}

type APIBreakerSnapshot

type APIBreakerSnapshot struct {
	APIUpstreamID uint    `json:"api_upstream_id"`
	State         string  `json:"state"`
	RemainingMs   int64   `json:"remaining_ms"`
	Failures      int     `json:"failures"`
	Successes     int     `json:"successes"`
	FailureRate   float64 `json:"failure_rate"`
}

type APIClientAbortReason

type APIClientAbortReason string
const (
	APIClientAbortCanceled         APIClientAbortReason = "canceled"
	APIClientAbortDeadlineExceeded APIClientAbortReason = "deadline_exceeded"
)

type APIExecution

type APIExecution struct {
	Request           *RequestContext
	Result            apiattempt.APIExecutionResult
	Err               error
	StatusCode        int
	DurationMs        int
	QuotaGateDecision string
	SourceAgentID     string
}

type APILimiterFinder

type APILimiterFinder interface {
	EffectiveSourceAPILimiters(userID, groupID, serviceID, routeID uint) []cache.APILimiter
	EffectiveUpstreamAPILimiters(upstreamID uint) []cache.APILimiter
}

type APIMetrics

type APIMetrics struct {
	Requests     *prometheus.CounterVec
	Dispatches   *prometheus.CounterVec
	Active       *prometheus.GaugeVec
	UsageDropped prometheus.Counter
	TraceSlimmed prometheus.Counter
}

APIMetrics exposes only bounded protocol, outcome, and transport dimensions. Per-service, route, token, and request identifiers belong in request logs, never Prometheus labels.

func NewAPIMetrics

func NewAPIMetrics(registerer prometheus.Registerer) *APIMetrics

func (*APIMetrics) AddTraceSlimmed

func (metrics *APIMetrics) AddTraceSlimmed(count uint64)

func (*APIMetrics) AddUsageDropped

func (metrics *APIMetrics) AddUsageDropped(count uint64)

type APIPermit

type APIPermit interface {
	Release()
}

type APIRequestFacts

type APIRequestFacts struct {
	UserID, GroupID, TokenID uint
	APIServiceID, APIRouteID uint
	APIUpstreamID            uint
	RequestID                string
	NoWait                   bool
}

type APIRoleSetFinder

type APIRoleSetFinder interface {
	FindUserAPIRoleSet(context.Context, uint) (*protocol.APIRoleSet, bool, error)
	FindTokenAPIRoleSet(context.Context, uint) (*protocol.APIRoleSet, bool, error)
}

type APIServiceRouteByIDFinder

type APIServiceRouteByIDFinder interface {
	FindServiceRouteByID(serviceID, routeID uint) (ServiceRoute, error)
}

type APIStatusFailurePredicate

type APIStatusFailurePredicate func(status int) bool

type APITargetHandler

type APITargetHandler struct {
	// contains filtered or unexported fields
}

APITargetHandler bridges one committed tunnel stream into the local HTTP executor. Source authorization, quota, and Agent selection are deliberately absent; the target consumes only the frozen service/route IDs.

func NewAPITargetHandler

func NewAPITargetHandler(finder APIServiceRouteByIDFinder, local ProtocolHandler) *APITargetHandler

func (*APITargetHandler) ServeHTTPAPI

func (h *APITargetHandler) ServeHTTPAPI(ctx context.Context, stream *agenttunnel.APITargetStream) error

type APITraceSettingsReader

type APITraceSettingsReader interface {
	Settings() settings.AgentSettings
}

type APIUpstreamIndex

type APIUpstreamIndex interface {
	UpstreamsForBackend(backendID uint) []protocol.SyncedAPIUpstream
}

type APIUpstreamLease

type APIUpstreamLease struct {
	Upstream protocol.SyncedAPIUpstream
	// contains filtered or unexported fields
}

APIUpstreamLease freezes the selected upstream and owns its breaker completion permit. Finish is idempotent through the bound permit.

func (*APIUpstreamLease) Finish

func (l *APIUpstreamLease) Finish(completion APIBreakerCompletion)

type APIUpstreamPicker

type APIUpstreamPicker struct {
	// contains filtered or unexported fields
}

func NewAPIUpstreamPicker

func NewAPIUpstreamPicker(index APIUpstreamIndex, breakers APIBreakerFinder) *APIUpstreamPicker

func (*APIUpstreamPicker) Pick

func (p *APIUpstreamPicker) Pick(backendID uint, requestedProtocol apiattempt.APIProtocol, requestID string) (*APIUpstreamLease, error)

type APIUsageBuilder

type APIUsageBuilder interface {
	Build(APIExecution) protocol.APIUsageEntry
}

type APIUsageReporter

type APIUsageReporter interface {
	EnqueueAPI(protocol.APIUsageEntry) error
}

type AgentPick

type AgentPick struct {
	ExecutionAgentID string
	AgentRouteID     uint
	AgentRoutePath   app.RoutePath
	Target           models.Agent
}

type AgentPicker

type AgentPicker struct {
	// contains filtered or unexported fields
}

func NewAgentPicker

func NewAgentPicker(routes APIAgentRouteFinder, agents agentproxy.AgentLookup, localAgentID string) *AgentPicker

func (*AgentPicker) Pick

func (p *AgentPicker) Pick(tokenID, routeID, serviceID uint, requestID string) (AgentPick, error)

type ExecutionAgentCapabilityFinder

type ExecutionAgentCapabilityFinder interface {
	SupportsGenericAPIExecution(agentID string) bool
}

type ExecutionAgentPicker

type ExecutionAgentPicker interface {
	Pick(tokenID, routeID, serviceID uint, requestID string) (AgentPick, error)
}

type ExecutionRouter

type ExecutionRouter struct {
	// contains filtered or unexported fields
}

ExecutionRouter chooses only the transport owner for an already frozen execution Agent. Neither branch may select an Agent again.

func NewExecutionRouter

func NewExecutionRouter(sourceAgentID string, local, remote ProtocolHandler) *ExecutionRouter

func (*ExecutionRouter) Serve

func (r *ExecutionRouter) Serve(ctx context.Context, request *RequestContext) error

type Executor

type Executor struct {
	// contains filtered or unexported fields
}

Executor dispatches requests by their registered transport protocol.

func NewExecutor

func NewExecutor(handlers map[string]ProtocolHandler) *Executor

func (*Executor) Execute

func (e *Executor) Execute(ctx context.Context, request *RequestContext) error

type GatewayError

type GatewayError struct {
	// contains filtered or unexported fields
}

GatewayError is the externally safe form of an API gateway failure. Its code is stable; the wrapped error is only for local classification.

func (*GatewayError) Error

func (e *GatewayError) Error() string

func (*GatewayError) Unwrap

func (e *GatewayError) Unwrap() error

type GenericAPIUsageSupport

type GenericAPIUsageSupport interface {
	SupportsGenericAPIUsage() bool
}

type HTTPHandler

type HTTPHandler struct {
	// contains filtered or unexported fields
}

HTTPHandler executes one local Generic API HTTP request against one frozen upstream lease. It never retries, replays, falls back, or re-picks.

func NewHTTPHandler

func NewHTTPHandler(picker HTTPUpstreamPicker, transport *HTTPTransport, limiters ...SourceLimiter) *HTTPHandler

func (*HTTPHandler) Serve

func (h *HTTPHandler) Serve(ctx context.Context, rc *RequestContext) (returnErr error)

func (*HTTPHandler) WithSettings

func (h *HTTPHandler) WithSettings(finder SettingsFinder) *HTTPHandler

WithSettings injects the Agent's lock-free settings snapshot into the handler and its HTTP transport. It is configured during server assembly.

type HTTPStream

type HTTPStream struct {
	// contains filtered or unexported fields
}

HTTPStream copies one upstream HTTP response to the local client without retaining its body. The zero value is ready to use.

func (HTTPStream) Copy

type HTTPTransport

type HTTPTransport struct {
	// contains filtered or unexported fields
}

func NewHTTPTransport

func NewHTTPTransport(proxyURL string) *HTTPTransport

func (*HTTPTransport) Do

func (*HTTPTransport) WithSettings

func (t *HTTPTransport) WithSettings(finder SettingsFinder) *HTTPTransport

WithSettings makes every future request obtain its timeout tuple from the latest Agent settings snapshot. A changed tuple receives a fresh immutable http.Transport; the retired transport only has idle connections closed.

type HTTPUpstreamPicker

type HTTPUpstreamPicker interface {
	Pick(uint, apiattempt.APIProtocol, string) (*APIUpstreamLease, error)
}

type Handler

type Handler struct {
	// contains filtered or unexported fields
}

func NewHandler

func NewHandler(options HandlerOptions) *Handler

func (*Handler) Serve

func (h *Handler) Serve(c *gin.Context)

type HandlerOptions

type HandlerOptions struct {
	Finder                ServiceRouteFinder
	Permission            PermissionChecker
	Quota                 QuotaChecker
	Limiter               SourceLimiter
	AgentPicker           ExecutionAgentPicker
	ExecutionCapabilities ExecutionAgentCapabilityFinder
	Usage                 APIUsageBuilder
	Reporter              APIUsageReporter
	MasterUsageSupport    GenericAPIUsageSupport
	SourceAgentID         string
	TraceSettings         APITraceSettingsReader
	Metrics               *APIMetrics
	Logger                *zap.Logger
	Executor              RequestExecutor
	// Handlers is retained for test and embedding compatibility. New production
	// assembly should inject Executor explicitly.
	Handlers map[string]ProtocolHandler
}

type HeaderBuilder

type HeaderBuilder struct{}

HeaderBuilder sanitizes client headers before applying administrator-owned overrides and the selected upstream's structured credential.

func (HeaderBuilder) Build

func (HeaderBuilder) BuildWebSocket

func (builder HeaderBuilder) BuildWebSocket(
	client http.Header,
	upstream protocol.SyncedAPIUpstream,
	credential protocol.APIUpstreamCredential,
	allowedSubprotocols []string,
) http.Header

type LimiterGate

type LimiterGate struct {
	// contains filtered or unexported fields
}

func NewLimiterGate

func NewLimiterGate(finder APILimiterFinder, store apiPermitStore) *LimiterGate

func (*LimiterGate) Acquire

func (g *LimiterGate) Acquire(ctx context.Context, facts APIRequestFacts) (APIPermit, error)

type PermissionChecker

type PermissionChecker interface {
	AllowInvoke(context.Context, uint, uint, uint, uint) error
}

type PermissionGate

type PermissionGate struct {
	// contains filtered or unexported fields
}

func NewPermissionGate

func NewPermissionGate(tokens TokenFactFinder, roleSets APIRoleSetFinder, index *cache.APIIndex) *PermissionGate

func (*PermissionGate) AllowInvoke

func (g *PermissionGate) AllowInvoke(
	ctx context.Context,
	tokenID, userID, serviceID, routeID uint,
) error

type ProtocolHandler

type ProtocolHandler interface {
	Serve(context.Context, *RequestContext) error
}

type Publication

type Publication struct {
	// contains filtered or unexported fields
}

Publication is the request-scoped terminal state created before any gate runs. Publish must be called exactly once for every request that reaches the Generic API handler.

func (*Publication) FinishMetrics

func (p *Publication) FinishMetrics(executionErr error)

FinishMetrics closes a request rejected before the Generic API usage contract is available. Such requests keep their historical no-usage behavior.

func (*Publication) Publish

func (p *Publication) Publish(c *gin.Context, request *RequestContext, permit APIPermit, executionErr error)

func (*Publication) StartExecution

func (p *Publication) StartExecution(request *RequestContext)

type Publisher

type Publisher struct {
	// contains filtered or unexported fields
}

Publisher owns the terminal lifecycle shared by every Generic API request. It does not execute requests or select routes.

func NewPublisher

func NewPublisher(options PublisherOptions) *Publisher

func (*Publisher) Begin

func (p *Publisher) Begin(requestID, protocol string) *Publication

type PublisherOptions

type PublisherOptions struct {
	Usage         APIUsageBuilder
	Reporter      APIUsageReporter
	SourceAgentID string
	Metrics       *APIMetrics
	Logger        *zap.Logger
}

type QuotaChecker

type QuotaChecker interface {
	Allow(context.Context, uint, protocol.SyncedAPIService) error
}

type QuotaGate

type QuotaGate struct {
	// contains filtered or unexported fields
}

func NewQuotaGate

func NewQuotaGate(users UserFactFinder, settings SettingsFinder) *QuotaGate

func (*QuotaGate) Allow

func (g *QuotaGate) Allow(ctx context.Context, userID uint, service protocol.SyncedAPIService) error

type RemoteHTTPHandler

type RemoteHTTPHandler struct {
	// contains filtered or unexported fields
}

func NewRemoteHTTPHandler

func NewRemoteHTTPHandler(options RemoteHTTPHandlerOptions) *RemoteHTTPHandler

func (*RemoteHTTPHandler) Serve

type RemoteHTTPHandlerOptions

type RemoteHTTPHandlerOptions struct {
	Direct       agentproxy.DirectHTTPAPITransportBuilder
	Relay        app.HTTPAPIStreamOpener
	GlobalProxy  string
	AddressTag   string
	PreferredTag string
}

type RemoteWebSocketHandler

type RemoteWebSocketHandler struct {
	// contains filtered or unexported fields
}

RemoteWebSocketHandler opens a typed stream to the already frozen execution Agent. It may fall back only when Direct target preparation fails. Once the Direct opener is called, that transport choice is terminal because the remote side may already have committed work.

func (*RemoteWebSocketHandler) Serve

type RemoteWebSocketHandlerOptions

type RemoteWebSocketHandlerOptions struct {
	Direct         agentproxy.DirectWebSocketAPIStreamOpener
	Relay          app.WebSocketAPIStreamOpener
	TargetSupports func(string) bool
	GlobalProxy    string
	AddressTag     string
	PreferredTag   string
	Settings       SettingsFinder
}

type RequestBuilder

type RequestBuilder struct{}

RequestBuilder builds one outbound request. It does not dial, read, cache, close, or make the input body replayable.

func (RequestBuilder) Build

type RequestBuilderInput

type RequestBuilderInput struct {
	Request  *http.Request
	Route    protocol.SyncedAPIRoute
	Upstream protocol.SyncedAPIUpstream
	Subpath  string
	RawQuery string
}

type RequestContext

type RequestContext struct {
	Context                *gin.Context
	Service                protocol.SyncedAPIService
	Route                  protocol.SyncedAPIRoute
	Protocol               string
	Subpath                string
	TokenID                uint
	TokenName              string
	UserID                 uint
	RequestID              string
	GroupID                uint
	Agent                  AgentPick
	Execution              apiattempt.APIExecutionResult
	UpstreamName           string
	QuotaGateDecision      string
	TracePolicy            apiattempt.APITracePolicy
	ClientUpgradeCommitted bool
	ClientStatusCode       int
	// contains filtered or unexported fields
}

type RequestExecutor

type RequestExecutor interface {
	Execute(context.Context, *RequestContext) error
}

RequestExecutor executes one request after its service, route, and execution Agent have been frozen by the source pipeline.

type RequestHash

type RequestHash func(requestID string) uint64

type ServiceFinder

type ServiceFinder struct {
	// contains filtered or unexported fields
}

ServiceFinder selects an explicit first-segment route or falls back to the service's empty-slug root route when that explicit route does not exist.

func NewServiceFinder

func NewServiceFinder(index *cache.APIIndex) *ServiceFinder

func (*ServiceFinder) Find

func (f *ServiceFinder) Find(serviceSlug, requestPath, method, requestedProtocol string) (ServiceRoute, string, error)

type ServiceRoute

type ServiceRoute struct {
	Service  protocol.SyncedAPIService
	Route    protocol.SyncedAPIRoute
	Protocol string
}

ServiceRoute is the validated API route selected for one request.

type ServiceRouteFinder

type ServiceRouteFinder interface {
	Find(serviceSlug, requestPath, method, requestedProtocol string) (ServiceRoute, string, error)
}

type SettingsFinder

type SettingsFinder interface {
	Settings() settings.AgentSettings
}

type SourceLimiter

type SourceLimiter interface {
	Acquire(context.Context, APIRequestFacts) (APIPermit, error)
}

type TokenFactFinder

type TokenFactFinder interface {
	FindTokenByID(context.Context, uint) (*models.Token, bool, error)
}

type UpstreamURLBuilder

type UpstreamURLBuilder struct{}

UpstreamURLBuilder builds an upstream URL and applies structured query credentials after base and client queries have been joined.

func (UpstreamURLBuilder) Build

func (UpstreamURLBuilder) Build(
	upstream protocol.SyncedAPIUpstream,
	upstreamPath string,
	subpath string,
	rawQuery string,
) (*url.URL, error)

type UsageBuilder

type UsageBuilder struct {
	// contains filtered or unexported fields
}

func NewUsageBuilder

func NewUsageBuilder(now func() time.Time) *UsageBuilder

func (*UsageBuilder) Build

func (b *UsageBuilder) Build(execution APIExecution) protocol.APIUsageEntry

type UserFactFinder

type UserFactFinder interface {
	FindUser(context.Context, uint) (*protocol.SyncedUser, bool, error)
}

type WebSocketBridge

type WebSocketBridge struct {
	// contains filtered or unexported fields
}

WebSocketBridge copies one committed client connection and one provider connection in both directions. It owns no retry or reconnect policy.

func (WebSocketBridge) ConnectionAndStream

func (b WebSocketBridge) ConnectionAndStream(ctx context.Context, client *websocket.Conn, stream app.WebSocketAPIStream) error

ConnectionAndStream bridges a source-side Gorilla connection to the typed cross-Agent stream without changing message or control-frame boundaries.

func (WebSocketBridge) ConnectionAndStreamWithResult

func (b WebSocketBridge) ConnectionAndStreamWithResult(
	ctx context.Context,
	client *websocket.Conn,
	stream app.WebSocketAPIStream,
) (WebSocketBridgeResult, error)

func (WebSocketBridge) Connections

func (b WebSocketBridge) Connections(ctx context.Context, client, upstream *websocket.Conn) error

func (WebSocketBridge) ConnectionsWithResult

func (b WebSocketBridge) ConnectionsWithResult(
	ctx context.Context,
	client, upstream *websocket.Conn,
) (WebSocketBridgeResult, error)

type WebSocketBridgeResult

type WebSocketBridgeResult struct{ CloseCode int }

type WebSocketDialer

type WebSocketDialer interface {
	DialContext(context.Context, string, http.Header) (*websocket.Conn, *http.Response, error)
}

type WebSocketHandler

type WebSocketHandler struct {
	// contains filtered or unexported fields
}

WebSocketHandler executes one local Generic API WebSocket against one frozen upstream. It upgrades and dials at most once and never retries or reconnects.

func NewWebSocketHandler

func NewWebSocketHandler(options WebSocketHandlerOptions) *WebSocketHandler

func (*WebSocketHandler) Serve

func (h *WebSocketHandler) Serve(ctx context.Context, rc *RequestContext) (returnErr error)

type WebSocketHandlerOptions

type WebSocketHandlerOptions struct {
	Picker   WebSocketUpstreamPicker
	Dialer   WebSocketDialer
	Limiter  SourceLimiter
	Settings SettingsFinder
}

type WebSocketTargetHandler

type WebSocketTargetHandler struct {
	// contains filtered or unexported fields
}

WebSocketTargetHandler is the execution-Agent consumer of one committed tunnel stream. Source authorization and Agent selection are absent here.

func (*WebSocketTargetHandler) ServeWebSocketAPI

func (h *WebSocketTargetHandler) ServeWebSocketAPI(ctx context.Context, stream *agenttunnel.WebSocketTargetStream) error

type WebSocketTargetHandlerOptions

type WebSocketTargetHandlerOptions struct {
	Finder   APIServiceRouteByIDFinder
	Picker   WebSocketUpstreamPicker
	Dialer   WebSocketDialer
	Limiter  SourceLimiter
	Settings SettingsFinder
}

type WebSocketUpstreamPicker

type WebSocketUpstreamPicker interface {
	Pick(uint, apiattempt.APIProtocol, string) (*APIUpstreamLease, error)
}

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL