Execute request¶
This is the core request pipeline: validate initialization, build a RestSharp client and request from the instance's current state, execute the call, retry on configured failure statuses, and normalize the response into an IHttpRequestResult.
| Field | Value |
|---|---|
| Operation ID | OP-04 |
| Trigger | A consumer calls Delete/Get/Patch/Post/Put or their *Async equivalents |
| Inputs | HTTP verb, resource path, optional JSON request body; current instance state (base URL, ClientInfo, authentication, headers, options) |
| Outputs | An IHttpRequestResult describing status, content, headers, response date, and any error information; also stored on RequestResult |
| Primary components | HttpJsonClient.ValidateInitialization, HttpJsonClient.ExecuteRestCall, HttpJsonClient.CreateClient, HttpJsonClient.CreateRequest, HttpJsonClient.CreateRequestResult, HttpJsonClient.GetDateHeader, HttpJsonClient.PopulateRequestResultErrorContent |
| Entry point | src/SAIF.NetCore.HttpClients/SAIF.NetCore.HttpClients/SAIF.NetCore.HttpClients/HttpJsonClient/HttpJsonClient.cs |
Steps¶
- Validate that both
ClientInfoand the base URI are set. - On failure: throw
ClientNotInitializedExceptionbefore any network call (BR-HTTP-01). - Create the RestSharp
RestClient: attach the configured authenticator (NTLM, if any), select the serializer based onHttpClientOptions.Serializer, and apply the stored timeout override when valid (BR-HTTP-06, BR-HTTP-10, BR-HTTP-11). - Create the RestSharp
RestRequest: serialize the JSON body when provided, add the caller-identity headers (Client-Name,Application-ID,Client-User), add theAuthorizationheader fromBearerTokenwhen non-empty, copy any customHeaders, then clearHeadersunlessPersistHeadersAfterRequestis true (BR-HTTP-07, BR-HTTP-08, BR-HTTP-12). - Execute the request synchronously through the RestSharp client (
client.Execute(request)).ExecuteRestCallreadsresponse.StatusCodeimmediately afterward, before any null check on the response, so a null response from RestSharp throwsNullReferenceExceptionat this point rather than continuing to step 5 or 6. - Check the response status against
RetryOnStatusCodes(default 401, 502, 503): - If the status is retryable and retries remain (default 3 remaining, 4 attempts total): sleep
RetryDelayInMs(default 1000 ms) with a blockingThread.Sleep, then return to step 2 to rebuild the client and request from scratch (BR-HTTP-04, BR-HTTP-05). - If not retryable, or no retries remain, continue to step 6.
- Build the
IHttpRequestResultinCreateRequestResult: CreateRequestResultitself contains a defensive check that would setIsSuccessful = falseand content"Rest response was null"for a null RestSharp response (BR-HTTP-13), but this branch is not reachable through the pipeline described in step 4, because the null response is dereferenced earlier.- For a non-null response, copy status code, content, response headers, and status description.
- Parse
ResponseDatefrom thedateheader using a case-insensitiveSingleOrDefaultmatch followed byDateTime.TryParse, leaving it null if the header is missing, is not a string value, or is unparseable; if more than one header nameddateis present,SingleOrDefaultthrowsInvalidOperationExceptionhere instead of producing a nullResponseDate(BR-HTTP-14). - For an unsuccessful response, derive
ErrorMessagein order: RestSharp's own message, then matching JSON error tokens, then raw content on parse failure, then a formatted status fallback (BR-HTTP-15).- If the status is in the 4xx range, also populate
ErrorException(BR-HTTP-17).
- If the status is in the 4xx range, also populate
- Store the constructed result on the instance's
RequestResultproperty and return it to the consumer.
Async verb methods wrap this synchronous pipeline in Task.Run, so the retry delay in step 5 blocks a thread-pool thread rather than awaiting asynchronously. The generic *Async<T> overloads cannot be called at all (BR-HTTP-21).
Process flow¶
The following diagram visualizes the request-build, execute, and retry pipeline, including the point where a null RestSharp response currently throws rather than reaching the defensive null-handling code.
flowchart TD
Start(["Verb method called<br>e.g. Get/PostAsync"]) --> Validate[ValidateInitialization]
Validate -->|"ClientInfo or baseUri null"| Err1(["ClientNotInitializedException"])
Validate --> CreateClient["CreateClient:<br>authenticator, serializer, timeout"]
CreateClient --> CreateRequest["CreateRequest:<br>body, identity headers,<br>bearer, custom headers, clear headers"]
CreateRequest --> Exec["client.Execute(request)"]
Exec --> StatusRead["Read response.StatusCode<br>(no null check first)"]
StatusRead -->|"response is null"| Err2(["NullReferenceException"])
StatusRead --> Retryable{"Status in RetryOnStatusCodes<br>and retries remain?"}
Retryable -->|Yes| Sleep["Thread.Sleep(RetryDelayInMs)"] --> CreateClient
Retryable -->|No| BuildResult["CreateRequestResult<br>(null-response branch unreachable here)"]
BuildResult --> Store["Store on RequestResult<br>and return to consumer"]
Failure and edge-case behavior¶
- A retryable status with default options discards custom headers (including Basic auth) on every retry, so a 401 retry against a Basic-only endpoint cannot succeed and only adds blocking latency.
- NTLM credentials and the bearer token are resent unchanged on every retry, so a genuine authentication failure produces up to 4 identical failed attempts.
ErrorExceptionis populated only for 4xx statuses; 5xx failures surface only throughErrorMessage/Content.- A null RestSharp response throws
NullReferenceExceptionfrom the status-code read in step 4 rather than reachingCreateRequestResult's defensive null handling (BR-HTTP-13).
Related documentation¶
Sources¶
src/SAIF.NetCore.HttpClients/SAIF.NetCore.HttpClients/SAIF.NetCore.HttpClients/HttpJsonClient/HttpJsonClient.cs