Spec -- ZLink HTTP Client For .NET¶
See the user guide for a usage-focused document. The language-neutral common contract is owned by the common spec, and this document only describes the .NET-specific deviation and implementation mapping for the common contract. The public contract's standard is the common spec and this per-language spec. The public types under
src/Zlink.HttpClient/**and theZlink.HttpClient.UnitTestsregression test verify whether the implementation keeps that contract.
1. Purpose¶
Zlink.HttpClient is a separate client-side deliverable for sending an
HTTP request in .NET. It's not a JSON-only client — it's a
general-purpose HTTP client that absorbs System.Net.Http's low-level
configuration in zlink fluent builder style. The typed path
(Body(dto)/Async<T>()/Fetch<T>()) is a convenience layer laid on
top of it.
This client references the error/codec contracts in Zlink.Framework.Contracts and
does not reference the Zlink.Framework server runtime assembly. This is the .NET
package split; 01 Scope And Architecture §1.3 owns the common dependency direction.
The HTTP codec registry only handles serializer registration. A
registered contentType is a parameter-free ASCII type/subtype. The
registry removes leading and trailing SP and TAB, converts ASCII
uppercase letters to lowercase, and uses that canonical media type as
the key.
An HTTP response Content-Type enters through a different boundary
from registration. The client first separates RFC parameters such as
charset, then lowercases the parameter-free media type and looks up
the canonical key. A normal response such as
application/json; charset=utf-8 therefore uses the
application/json serializer.
Even if the same extension also implements
IZlinkStreamCodecRegistration, it ignores the STREAM descriptor. The
HTTP client package doesn't depend on the Stream Connector runtime or
the compression package.
2. Deliverable Boundary¶
| Role | Location | Public? |
|---|---|---|
| Public contract | src/Zlink.HttpClient/*.cs, Contracts/* |
public |
| Shared error/codec contract | src/Zlink.Framework.Contracts/Codecs, Errors |
public dependency |
| Runtime implementation | src/Zlink.HttpClient/Runtime/* |
internal |
| Regression test | tests/Zlink.HttpClient.UnitTests/* |
private |
| Project | Zlink.HttpClient |
public package |
The public surface doesn't expose System.Net.Http types such as
SocketsHttpHandler, HttpClientHandler, HttpRequestMessage,
HttpResponseMessage.
3. Public Types¶
ZLinkHttpClient— a standalone client built from a static factory. ProvidesGet/Post/Put/Delete/Patch/Head/Options,IDisposable.ZLinkHttpServerClient— the client the framework server provides through DI. Each verb returns aZLinkHttpServerRequestBuilder.ZLinkHttpClientBuilder—BaseUrl,Codecs,Timeout,DefaultHeader,BasicAuth,BearerToken,MaxResponseBodySize,TrustCertificateFile,ClientCertificateFile,FollowRedirects,Retry,Cookies,Proxy,ProxyBasicAuth,Compression,Build,BuildServer, and a one-shot verb shortcut. (Codecsis framework codec extension registration — a .NET-specific extension point, a language deviation of common spec Chapter 2.3)ZLinkHttpRequestBuilder— the standalone surface. ProvidesHeader,Query,Timeout,Body<T>,Body(content, contentType),BodyStream,Form,Multipart,MultipartFile,AsyncRaw,DownloadAsync,Async<T>,Fetch<T>(directly returns the decoded body), and a callback overload.ZLinkHttpServerRequestBuilder— includes the standalone surface and adds the one-wayValueTask Async(CancellationToken cancellationToken = default). The returnedValueTaskonly delivers async completion and failure, not the transport result or admission status. It also addsValueTask<HttpResponse<T>> Yield<T>(CancellationToken cancellationToken = default), which returns the shared Spot gate and picks it back up on a new turn. Used only in aSpotWideUser Spot or Instance Spot, where gate return is allowed.IZLinkHttpExecutionScheduler/IZLinkHttpExecutionTurn— a public injection point where the DI integration captures the current Spot turn and places callback completion on the original execution queue's new turn.RawHttpResponse{Status,Headers,Body}.HttpResponse<T>{Status,Headers,Body,RawBody}.ZLinkHttpMethodenum.
The delegate the callback overload receives is also included in the
public contract. On completion, exactly one of error and response
is non-null.
4. Execution Model¶
Async<T>returnsValueTask<HttpResponse<T>>and keeps the Spot turn.Fetch<T>returnsValueTask<T>. An application sample that doesn't need status and header uses this terminal instead of pulling.Bodyafter receiving the response.
Yield<T>. To return the
shared Spot gate, call Async<T> inside RunIoWorker(...) and wait
with the Worker call's Yield.
- A callback overload doesn't return an awaitable. The completion
callback is placed as a new turn on the execution queue of the Spot
turn that made the request. In a standalone client, it's called
directly in an async completion context.
- A blocking terminator that pulls the completion value synchronously
isn't provided.
5. Transport Semantics¶
Default value/redirect/retry/cookie/compression/auth-scrubbing/body- source-exclusion semantics follow the common spec Chapters 2-8. .NET implementation mapping:
- Native automatic feature disabled:
AllowAutoRedirect=false,AutomaticDecompression=None,UseCookies=falseonSocketsHttpHandler— semantics implemented by the wrapper. - Per-attempt timeout is enforced with a linked
CancellationTokenSource.CancelAfterinstead ofHttpClient.Timeout(distinguishing caller cancellation from timeout). - TLS:
SslClientAuthenticationOptions(trust addition + mTLS). proxy:WebProxy. - Decompression: performed by the wrapper with
System.IO.Compression.
6. Error Mapping¶
Follows common spec Chapter 9. .NET uses
ZLinkFrameworkErrorKind, and the public exception doesn't provide a
retry indicator.
- Timeout is reported as
DeadlineExceededwith an innerTimeoutException. Caller cancellation propagates asOperationCanceledExceptionas is.
7. Regression Test Axis¶
Zlink.HttpClient.UnitTests's HttpClientContractTests verifies the
transport contract scenario. Chunked upload is verified with a
raw-socket server (RawCaptureServer), since the managed Linux
HttpListener can't receive a chunked request body.