This implementation adds robust HTTP resilience to the eForm SDK using Polly, a .NET resilience and transient-fault-handling library.
Added two NuGet packages to eFormCore/Microting.eForm.csproj:
- Polly (v8.6.4): Core resilience library
- Polly.Extensions.Http (v3.0.0): HttpClient integration
Modified eFormCore/Communication/Http.cs to include:
-
Retry Policy: Automatically retries transient HTTP failures
- Handles HTTP 5xx errors (500, 503, etc.)
- Handles HTTP 408 (Request Timeout)
- Handles
HttpRequestException(connection refused, network errors)
-
Exponential Backoff: Progressive delays between retries
- 1st retry: 2 seconds
- 2nd retry: 4 seconds
- 3rd retry: 8 seconds
-
Logging: Debug output for troubleshooting
- Logs each retry attempt
- Includes error details and timing
Applied retry policy to all HTTP methods:
HttpPost()HttpPut()HttpGet()HttpDelete()PostProto()(for protobuf requests)
New test project: eFormSDK.Http.Tests
- 6 comprehensive tests for HTTP resilience
- Uses WireMock for HTTP mocking
- Tests all critical scenarios:
- Transient failures with eventual success
- Persistent failures after max retries
- Connection refused handling
- Multiple HTTP verbs (GET, POST, DELETE)
Created http-tests.sh for easy test execution.
var response = await httpClient.PostAsync(url, content);
response.EnsureSuccessStatusCode(); // Throws on first errorIf the server returns 500 or the connection fails, the request fails immediately.
var response = await _retryPolicy.ExecuteAsync(async () =>
{
var httpClient = new HttpClient();
return await httpClient.PostAsync(url, content);
});
response.EnsureSuccessStatusCode(); // Only throws after all retriesIf the server returns 500:
- First attempt fails
- Wait 2 seconds, retry
- If still fails, wait 4 seconds, retry
- If still fails, wait 8 seconds, retry
- If still fails, throw exception
- HTTP 500 Internal Server Error
- HTTP 503 Service Unavailable
- HTTP 408 Request Timeout
- Connection refused
- Network timeouts
- HTTP 400 Bad Request
- HTTP 401 Unauthorized
- HTTP 403 Forbidden
- HTTP 404 Not Found
After all retries are exhausted:
- Public methods (Post, Status, Delete, etc.) catch exceptions
- Return error XML/JSON responses
- Maintains backward compatibility with existing error handling
- Improved Reliability: Automatically handles temporary network issues
- Better User Experience: Reduces failures from transient errors
- Minimal Code Changes: Centralized in Http.cs, no changes needed in calling code
- Configurable: Easy to adjust retry count and delays if needed
- Observable: Logging helps troubleshoot issues
- Well-Tested: Comprehensive test coverage validates behavior
All tests pass successfully:
$ ./http-tests.sh
Test Run Successful.
Total tests: 6
Passed: 6Potential improvements for consideration:
Prevent repeated calls to failing services:
var circuitBreaker = Policy
.Handle<HttpRequestException>()
.CircuitBreakerAsync(
exceptionsAllowedBeforeBreaking: 5,
durationOfBreak: TimeSpan.FromSeconds(30)
);Allow configuration via app settings:
{
"Polly": {
"RetryCount": 3,
"BaseDelay": 2,
"EnableCircuitBreaker": true
}
}Track and report retry statistics:
- Number of retries per request
- Success rate after retries
- Average retry duration
Combine multiple policies:
var policy = Policy.WrapAsync(timeoutPolicy, retryPolicy, circuitBreakerPolicy);Different policies for different endpoints:
- Critical endpoints: More retries
- Non-critical endpoints: Fewer retries
- Read operations: More aggressive retries
- Write operations: Conservative retries