Class MassiveHttpTransport
- Namespace
- MassiveDotNet.Http
- Assembly
- MassiveDotNet.dll
Issues authenticated requests against the Massive platform API and deserializes responses using source-generated metadata, or copies a document body to a caller's stream.
public sealed class MassiveHttpTransport : IDisposable
- Inheritance
-
MassiveHttpTransport
- Implements
- Inherited Members
Remarks
Responses are read with ResponseHeadersRead and deserialized straight off the network stream, so a large payload is never buffered into an intermediate string or byte array.
This type sits on the SDK's only BCL temporal boundary. HttpClient,
SocketsHttpHandler, and the Retry-After header all traffic in TimeSpan, which
no SDK can change. Constitution rule 12 therefore forbids naming the BCL type rather
than pretending it does not exist: every crossing below is an inline conversion through
NodaTime, so no BCL temporal type is ever declared. See the "Temporal types" section of
CLAUDE.md for the full policy and the table of known boundary points.
Constructors
MassiveHttpTransport(MassiveClientOptions)
Creates a transport that owns its own HttpClient, configured from
options.
public MassiveHttpTransport(MassiveClientOptions options)
Parameters
optionsMassiveClientOptionsThe client configuration.
Exceptions
- ArgumentNullException
optionsis null.- InvalidOperationException
optionsis incomplete.
MassiveHttpTransport(HttpClient)
Creates a transport over a caller-supplied HttpClient, for use with
IHttpClientFactory. The caller keeps ownership of the client's lifetime, and is
responsible for configuring its base address and authentication.
public MassiveHttpTransport(HttpClient httpClient)
Parameters
httpClientHttpClientThe configured client to send requests on.
Exceptions
- ArgumentNullException
httpClientis null.
Methods
CreateHandlerPipeline(MassiveClientOptions, HttpMessageHandler)
Builds the handler pipeline this SDK sends through, wrapping primaryHandler
with authentication and whatever resilience options asks for.
public static HttpMessageHandler CreateHandlerPipeline(MassiveClientOptions options, HttpMessageHandler primaryHandler)
Parameters
optionsMassiveClientOptionsThe client configuration.
primaryHandlerHttpMessageHandlerThe innermost handler, which performs the actual transport.
Returns
- HttpMessageHandler
The outermost handler, ready to hand to an HttpClient.
Remarks
The constructor taking a MassiveClientOptions uses this, and it is public so a caller supplying their own HttpClient can build the same pipeline instead of reconstructing it by hand. Order is the reason it is worth exposing rather than repeating.
Handlers compose authentication outermost, then retry, then the rate limiter. Authentication must be outermost because it rewrites the request URI under QueryString: a retry above it re-authenticates every attempt and appends the key once per try, which still succeeds and so is visible only as the key repeating in access logs (rule 11). The limiter is innermost because a retried attempt is a request the server counts, so it must spend a permit like any other (D30).
Exceptions
- ArgumentNullException
optionsorprimaryHandleris null.- InvalidOperationException
optionsis incomplete.
Dispose()
Performs application-defined tasks associated with freeing, releasing, or resetting unmanaged resources.
public void Dispose()
DownloadAsync(string, Stream, CancellationToken)
Issues a GET request and copies the response body to destination
unchanged, for a route that serves a document rather than JSON.
public Task DownloadAsync(string requestUri, Stream destination, CancellationToken cancellationToken = default)
Parameters
requestUristringThe request URI, relative to the configured base address.
destinationStreamThe stream the body is written to. The caller keeps ownership of it.
cancellationTokenCancellationTokenA token to cancel the request.
Returns
- Task
A task that completes once the whole body has been written.
Remarks
The content type is not inspected: the caller asked for the bytes, and the one route that
needs this, the SEC filing file, names each file's type and size in its listing (decision
D25). The body streams from the network into destination with no
intermediate buffer, as every other response does. A failure partway through the copy --
the connection drops, or destination itself errors -- leaves the
destination holding a partial body; nothing rewinds or truncates it, and the underlying
exception propagates to the caller unchanged.
Exceptions
- ArgumentNullException
requestUriordestinationis null.- ArgumentException
requestUriis empty or whitespace, ordestinationis not writable.- ObjectDisposedException
This transport has been disposed.
- MassiveRateLimitExceededException
The server responded with HTTP 429.
- MassiveApiException
The server responded with any other error status.
EnumerateAsync<TEnvelope, TItem>(string, JsonTypeInfo<TEnvelope>, CancellationToken)
Issues a GET request and then follows the response's next_url cursor, yielding every
item from every page.
public IAsyncEnumerable<TItem> EnumerateAsync<TEnvelope, TItem>(string requestUri, JsonTypeInfo<TEnvelope> typeInfo, CancellationToken cancellationToken = default) where TEnvelope : class, IPagedEnvelope<TItem>
Parameters
requestUristringThe first page's URI, relative to the configured base address.
typeInfoJsonTypeInfo<TEnvelope>Source-generated metadata describing
TEnvelope.cancellationTokenCancellationTokenA token to cancel the traversal.
Returns
- IAsyncEnumerable<TItem>
Every item across every page, in the order the server returned them.
Type Parameters
TEnvelopeThe paged response envelope type.
TItemThe result item type.
Remarks
Exactly one page is in flight at a time: the next request is issued only once the previous page has been fully consumed, so a caller who stops early stops the traffic too.
Cancellation is observed at page boundaries. The token is checked before each request, so a
cancelled traversal issues no further request, but the items already deserialized from the
page in hand are still yielded before the cancellation surfaces. Stopping mid-page is what
break is for, and costs nothing on the traversals that never cancel.
A server that returns the same next_url twice in a row would make the traversal
re-request one page forever, so that is detected and throws rather than followed. The check
compares against the cursor just followed and nothing older, which keeps the traversal's own
state constant however many pages it walks; the acknowledged cost is that a cycle through two
or more distinct cursors is not caught. There is no page cap: any limit high enough to be
safe for a genuine traversal is too high to bound a runaway usefully, and one low enough to
bound it would truncate real results.
Exceptions
- ArgumentNullException
requestUriortypeInfois null.- ArgumentException
requestUriis empty or whitespace.- ObjectDisposedException
This transport has been disposed.
- InvalidOperationException
A cursor was returned but the underlying client has no base address, so the cursor's origin cannot be checked. Raised while enumerating rather than from this call, since it depends on what the server sends back.
- MassiveApiException
The server responded with an error status, or returned a cursor that is not a usable URI, points outside the configured base address, or repeats the cursor just followed.
GetAsync<T>(string, JsonTypeInfo<T>, CancellationToken)
Issues a GET request and deserializes the response body.
public Task<T?> GetAsync<T>(string requestUri, JsonTypeInfo<T> typeInfo, CancellationToken cancellationToken = default)
Parameters
requestUristringThe request URI, relative to the configured base address.
typeInfoJsonTypeInfo<T>Source-generated metadata describing
T.cancellationTokenCancellationTokenA token to cancel the request.
Returns
Type Parameters
TThe response envelope type.
Exceptions
- MassiveRateLimitExceededException
The server responded with HTTP 429.
- MassiveApiException
The server responded with any other error status.
ThrowIfUnfollowableCursor(string?, string, string?)
Throws when a response offered a pagination cursor that this SDK cannot follow, because the operation's result is a single object rather than a page of items.
public static void ThrowIfUnfollowableCursor(string? nextUrl, string requestUri, string? requestId)
Parameters
nextUrlstringThe response's
next_url, or null when it sent none.requestUristringThe request that produced the response, named in the exception.
requestIdstringThe response's request identifier, carried by the exception when present.
Remarks
Called by generated code for the operations whose OpenAPI success schema declares
next_url on a result that is one object (decision D17). A blank cursor is the absence
it means, as it is everywhere else in this transport. A real one is a page the caller will
never receive, and missing data is reported loudly in this SDK rather than dropped.
Exceptions
- ArgumentException
requestUriis empty or whitespace.- MassiveApiException
nextUrlis a cursor.