refactor(http)!: one error model and a robust XRPC transport #117
Labels
No labels
breaking-change
bug
documentation
duplicate
enhancement
good first issue
help wanted
performance
question
wontfix
No milestone
No project
No assignees
1 participant
Notifications
Due date
No due date set.
Dependencies
No dependencies set.
Reference
Grandiras/ATProto.NET#117
Loading…
Add table
Add a link
Reference in a new issue
No description provided.
Delete branch "%!s()"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
Part of the v0.7.0 update cycle (wave 2). One exception model, and an XRPC transport that is correct under shared or singleton use, fast on large responses, and bounded when rate-limited.
Error model
AtProtoExceptionbase for every SDK exception. The coreXrpcException : AtProtoExceptioncarriesNsid,Error(the XRPC error name),ErrorMessage,HttpStatusCode StatusCode,ResponseBody,Headersandbool Is(string error). AddXrpcRateLimitException(RetryAfter,RateLimit),XrpcAuthenticationException,XrpcResponseFormatException, andXrpcErrorsconstants (RecordNotFound,InvalidSwap,ExpiredToken, …).ATProtoNet.Server.Xrpc.XrpcExceptionbecomes the core type, so one type serves both sides.AtProtoException, with consistentErrornaming where cheap:OAuthException.ErrorCode,SpaceCredentialException,SpaceTokenException,SpaceRepoVerificationException,JetstreamConnectException,JetstreamArchiveException, serverSpaceVerificationException. The DID-resolution exceptions are unified by the identity issue.XrpcClient,AtProtoHttpException,SpaceCredentialProvider, whose duplicateXrpcErrorBodygoes,JetstreamArchiveClient,JetstreamDictionaryClient).XrpcErrorsconstants, never string literals.RecordCollection"not found" matchesRecordNotFoundonly (today anyInvalidRequestcounts).Transport (
XrpcClient)Uriand build absolute request URIs; never mutateHttpClient.BaseAddress. TodaySetPdsUrl/ApplyOAuthSessionAsyncthrow once theHttpClienthas sent a request, and an injected client'sBaseAddresssilently overridesInstanceUrl. Replace them withUri ServiceUrl { get; }/SetServiceUrl(Uri).User-Agentper request (AtProtoClientOptions.UserAgent) and never append to a sharedDefaultRequestHeaders. Today 100 clients on oneHttpClientproduce a 1,899-character header.atproto-proxyandatproto-accept-labelerson unauthenticated calls too. Add per-callXrpcCallOptions(Proxy,AcceptLabelers,Headers,Timeout) so concurrent callers on a shared client don't race on global setters.HttpCompletionOption.ResponseHeadersReadand deserialize from the stream. For a 185 KB timeline this measured −20 % time, −32 % allocation, and no Gen2 GCs (58 per 1,000 calls before).HttpClientuses one internal sharedSocketsHttpHandlerwithAutomaticDecompression = All,PooledConnectionLifetime = 5 minand a connect timeout. The existing named DI clients get the same primary-handler settings. The SDK sends noAccept-Encodingtoday, and a timeline is 185 KB uncompressed vs 12 KB gzipped.MaxRateLimitDelay, default about 30 s) and throwXrpcRateLimitExceptionbeyond it. Today a daily-window limit can sleep for hours. Parse the HTTP-date form ofRetry-After, add jitter, and put this inAtProtoClientOptions.RateLimit.DateTime/DateTimeOffsetas ISO-8601 UTC (today09/24/2026 12:00:00 +00:00); enums via their JSON names; identifier types viaToString(). AddXrpcParamsoverloads forlong?,DateTimeOffset?andISpanFormattable. MergeXrpcQueryBuilderintoXrpcParams.XrpcStreamResponse : IAsyncDisposable(Content,ContentType,ContentLength) that owns and disposes theHttpResponseMessage.0. A non-seekable stream that needs a retry (DPoP nonce, 429) fails clearly.XrpcResponseFormatException.QueryAsyncand oneProcedureAsyncfamily plus one JSON reader. Remove the 0-reference members and the no-opIDisposable.ILogger?becomes optional. Decide whetherXrpcClientbecomes internal, coordinating with the records/custom-XRPC issue, which wants an extension seam.Out of scope: auth-header and DPoP application internals (the primitives issue), session refresh (the session issue), and DI and package changes (the restructure issue).
CHANGELOG:
Breaking changes(exception types,Uri-typed URL members, removed setters) with migration notes;Changed(compression, rate-limit cap);Fixed(BaseAddress, User-Agent, headers, formatting).