Skip to content

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

  1. Validate that both ClientInfo and the base URI are set.
  2. On failure: throw ClientNotInitializedException before any network call (BR-HTTP-01).
  3. Create the RestSharp RestClient: attach the configured authenticator (NTLM, if any), select the serializer based on HttpClientOptions.Serializer, and apply the stored timeout override when valid (BR-HTTP-06, BR-HTTP-10, BR-HTTP-11).
  4. Create the RestSharp RestRequest: serialize the JSON body when provided, add the caller-identity headers (Client-Name, Application-ID, Client-User), add the Authorization header from BearerToken when non-empty, copy any custom Headers, then clear Headers unless PersistHeadersAfterRequest is true (BR-HTTP-07, BR-HTTP-08, BR-HTTP-12).
  5. Execute the request synchronously through the RestSharp client (client.Execute(request)). ExecuteRestCall reads response.StatusCode immediately afterward, before any null check on the response, so a null response from RestSharp throws NullReferenceException at this point rather than continuing to step 5 or 6.
  6. Check the response status against RetryOnStatusCodes (default 401, 502, 503):
  7. If the status is retryable and retries remain (default 3 remaining, 4 attempts total): sleep RetryDelayInMs (default 1000 ms) with a blocking Thread.Sleep, then return to step 2 to rebuild the client and request from scratch (BR-HTTP-04, BR-HTTP-05).
  8. If not retryable, or no retries remain, continue to step 6.
  9. Build the IHttpRequestResult in CreateRequestResult:
  10. CreateRequestResult itself contains a defensive check that would set IsSuccessful = false and 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.
  11. For a non-null response, copy status code, content, response headers, and status description.
  12. Parse ResponseDate from the date header using a case-insensitive SingleOrDefault match followed by DateTime.TryParse, leaving it null if the header is missing, is not a string value, or is unparseable; if more than one header named date is present, SingleOrDefault throws InvalidOperationException here instead of producing a null ResponseDate (BR-HTTP-14).
  13. For an unsuccessful response, derive ErrorMessage in 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).
  14. Store the constructed result on the instance's RequestResult property 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.
  • ErrorException is populated only for 4xx statuses; 5xx failures surface only through ErrorMessage/Content.
  • A null RestSharp response throws NullReferenceException from the status-code read in step 4 rather than reaching CreateRequestResult's defensive null handling (BR-HTTP-13).

Sources

  • src/SAIF.NetCore.HttpClients/SAIF.NetCore.HttpClients/SAIF.NetCore.HttpClients/HttpJsonClient/HttpJsonClient.cs