Request Timeout Middleware in ConnectSoft Microservice Template¶
Purpose & Overview¶
Request Timeout Middleware enforces maximum time limits on HTTP requests, helping prevent resource exhaustion and ensuring that long-running requests are terminated appropriately. In the ConnectSoft Microservice Template, request timeout is implemented using ASP.NET Core's built-in request timeout middleware, providing configurable timeout policies for different endpoints and scenarios.
Request timeout provides:
- Resource Protection: Prevents long-running requests from consuming server resources indefinitely
- Predictable Behavior: Ensures requests complete within expected timeframes
- Graceful Degradation: Allows services to handle timeouts gracefully with custom responses
- Performance Monitoring: Helps identify slow endpoints that may need optimization
- DoS Mitigation: Protects against slowloris attacks and resource exhaustion
- User Experience: Provides timely feedback when operations take too long
Request Timeout Philosophy
Request timeout is a critical performance and reliability feature that should be configured based on your application's requirements. The template provides a global default timeout policy with the ability to define named policies for specific endpoints. Timeouts should be set based on actual endpoint performance characteristics and user expectations. Note that request timeouts do not trigger in debug mode.
Architecture Overview¶
Request Timeout in the Request Pipeline¶
Incoming Request
↓
Routing Middleware (UseRouting)
↓
Request Timeout Middleware (UseRequestTimeouts)
├── Check Timeout Policy
├── Start Timeout Timer
├── Monitor Request Duration
│ ├── Within Timeout → Continue to endpoint
│ └── Exceeds Timeout → Cancel request, return timeout response
↓
Controller/Endpoint
↓
Response
Request Timeout Components¶
RequestTimeoutExtensions.cs
├── AddMicroserviceRequestTimeouts() - Service Registration
│ ├── Default Policy (if Enabled)
│ │ ├── Timeout Duration
│ │ ├── Timeout Status Code
│ │ └── Custom Response Handler (optional)
│ └── Named Policies
│ ├── Policy Name
│ ├── Timeout Duration
│ ├── Timeout Status Code
│ └── Custom Response Handler (optional)
└── UseMicroserviceRequestTimeouts() - Middleware
└── Place after UseRouting()
RequestTimeoutOptions.cs
├── Enabled (bool)
├── DefaultPolicy (RequestTimeoutPolicyOptions)
│ ├── Timeout (TimeSpan)
│ ├── TimeoutStatusCode (int)
│ └── WriteTimeoutResponse (Func<HttpContext, Task>)
└── Policies (Dictionary<string, RequestTimeoutPolicyOptions>)
Service Registration¶
AddMicroserviceRequestTimeouts Extension¶
Request timeout is registered via AddMicroserviceRequestTimeouts():
Implementation:
// RequestTimeoutExtensions.cs
internal static IServiceCollection AddMicroserviceRequestTimeouts(this IServiceCollection services)
{
ArgumentNullException.ThrowIfNull(services);
var options = OptionsExtensions.RequestTimeoutOptions;
// Only register if enabled
if (options?.Enabled == true)
{
services.AddRequestTimeouts(requestTimeoutOptions =>
{
// Configure default policy if provided
if (options.DefaultPolicy != null)
{
requestTimeoutOptions.DefaultPolicy = new RequestTimeoutPolicy
{
Timeout = options.DefaultPolicy.Timeout,
TimeoutStatusCode = options.DefaultPolicy.TimeoutStatusCode,
WriteTimeoutResponse = options.DefaultPolicy.WriteTimeoutResponse
};
}
// Configure named policies
if (options.Policies != null)
{
foreach (var policy in options.Policies)
{
requestTimeoutOptions.AddPolicy(policy.Key, new RequestTimeoutPolicy
{
Timeout = policy.Value.Timeout,
TimeoutStatusCode = policy.Value.TimeoutStatusCode,
WriteTimeoutResponse = policy.Value.WriteTimeoutResponse
});
}
}
});
}
return services;
}
UseMicroserviceRequestTimeouts Middleware¶
Pipeline Position:
Placement:
// Middleware order:
application.UseHttpsRedirection();
application.UseRouting(); // Before request timeout
application.UseMicroserviceRequestTimeouts(); // After routing
application.UseMicroserviceRateLimiter(); // After request timeout
application.UseEndpoints(...); // After request timeout
Important: Request timeout middleware must be placed:
- After UseRouting() (to access route information and apply named policies)
- Before UseEndpoints() (to intercept requests before endpoint execution)
Configuration¶
RequestTimeoutOptions¶
Configuration Class:
// RequestTimeoutOptions.cs
public sealed class RequestTimeoutOptions
{
public const string RequestTimeoutOptionsSectionName = "RequestTimeout";
[Required]
required public bool Enabled { get; set; } = true;
[RequiredIf(nameof(Enabled), true)]
[ValidateObjectMembers]
public RequestTimeoutPolicyOptions? DefaultPolicy { get; set; }
[ValidateObjectMembers]
public Dictionary<string, RequestTimeoutPolicyOptions>? Policies { get; set; }
}
RequestTimeoutPolicyOptions¶
Configuration Class:
// RequestTimeoutPolicyOptions.cs
public sealed class RequestTimeoutPolicyOptions
{
[Required]
[Range(typeof(TimeSpan), "00:00:01", "1.00:00:00", ErrorMessage = "Timeout must be between 1 second and 1 hour.")]
required public TimeSpan Timeout { get; set; }
[Range(400, 599, ErrorMessage = "TimeoutStatusCode must be between 400 and 599.")]
public int TimeoutStatusCode { get; set; } = 504;
public Func<HttpContext, Task>? WriteTimeoutResponse { get; set; }
}
appsettings.json Configuration¶
Example Configuration:
{
"RequestTimeout": {
"Enabled": true,
"DefaultPolicy": {
"Timeout": "00:00:30",
"TimeoutStatusCode": 504
},
"Policies": {
"Fast": {
"Timeout": "00:00:05",
"TimeoutStatusCode": 504
},
"LongRunning": {
"Timeout": "00:02:00",
"TimeoutStatusCode": 504
},
"FileUpload": {
"Timeout": "00:05:00",
"TimeoutStatusCode": 504
}
}
}
}
Configuration Parameters:
| Parameter | Type | Description | Default | Example |
|---|---|---|---|---|
Enabled |
bool |
Enable or disable request timeout middleware | true |
true |
DefaultPolicy |
RequestTimeoutPolicyOptions |
Default timeout policy (required when Enabled is true) | Required | See below |
Policies |
Dictionary<string, RequestTimeoutPolicyOptions> |
Named timeout policies for specific endpoints | Optional | See below |
RequestTimeoutPolicyOptions Parameters:
| Parameter | Type | Description | Default | Example |
|---|---|---|---|---|
Timeout |
TimeSpan |
Maximum request duration (1 second to 1 hour) | Required | 00:00:30 (30 seconds) |
TimeoutStatusCode |
int |
HTTP status code to return on timeout (400-599) | 504 |
504 (Gateway Timeout) |
WriteTimeoutResponse |
Func<HttpContext, Task> |
Custom response handler (not configurable via appsettings) | null |
N/A |
Environment-Specific Configuration¶
Development:
{
"RequestTimeout": {
"Enabled": true,
"DefaultPolicy": {
"Timeout": "00:01:00",
"TimeoutStatusCode": 504
},
"Policies": {
"Fast": {
"Timeout": "00:00:10",
"TimeoutStatusCode": 504
},
"LongRunning": {
"Timeout": "00:05:00",
"TimeoutStatusCode": 504
}
}
}
}
Production:
{
"RequestTimeout": {
"Enabled": true,
"DefaultPolicy": {
"Timeout": "00:00:30",
"TimeoutStatusCode": 504
},
"Policies": {
"Fast": {
"Timeout": "00:00:05",
"TimeoutStatusCode": 504
},
"LongRunning": {
"Timeout": "00:02:00",
"TimeoutStatusCode": 504
}
}
}
}
Testing:
Applying Timeout Policies¶
Default Policy¶
The default policy applies to all endpoints that don't have an explicit timeout policy configured:
// All endpoints use the default policy automatically
[HttpGet("api/products")]
public async Task<IActionResult> GetProducts()
{
// Uses default timeout policy
}
Named Policies¶
Apply named policies to specific endpoints using the [RequestTimeout] attribute:
[RequestTimeout("Fast")]
[HttpGet("api/health")]
public async Task<IActionResult> HealthCheck()
{
// Uses "Fast" policy (5 seconds)
}
[RequestTimeout("LongRunning")]
[HttpPost("api/reports/generate")]
public async Task<IActionResult> GenerateReport()
{
// Uses "LongRunning" policy (2 minutes)
}
Per-Endpoint Timeout¶
You can also specify a timeout directly on an endpoint:
[RequestTimeout(5000)] // 5 seconds
[HttpGet("api/search")]
public async Task<IActionResult> Search()
{
// Uses 5-second timeout
}
Disabling Timeout for Specific Endpoints¶
Disable timeout for endpoints that should not be subject to timeout limits:
[DisableRequestTimeout]
[HttpPost("api/long-running-operation")]
public async Task<IActionResult> LongRunningOperation()
{
// No timeout applied
}
Custom Timeout Responses¶
Default Response¶
By default, when a timeout occurs, the middleware returns the configured TimeoutStatusCode (default: 504 Gateway Timeout) with an empty response body.
Custom Response Handler¶
You can provide a custom response handler programmatically:
// RequestTimeoutExtensions.cs
services.AddRequestTimeouts(options =>
{
options.DefaultPolicy = new RequestTimeoutPolicy
{
Timeout = TimeSpan.FromSeconds(30),
TimeoutStatusCode = 504,
WriteTimeoutResponse = async context =>
{
context.Response.ContentType = "application/json";
await context.Response.WriteAsJsonAsync(new
{
error = "Request timeout",
message = "The request took too long to complete.",
timeout = "30 seconds"
});
}
};
});
Note: Custom response handlers cannot be configured via appsettings.json and must be set programmatically.
Response Behavior¶
Timeout Response¶
When a request exceeds the timeout:
Or with custom response handler:
HTTP/1.1 504 Gateway Timeout
Content-Type: application/json
Content-Length: 85
{
"error": "Request timeout",
"message": "The request took too long to complete.",
"timeout": "30 seconds"
}
Request Cancellation¶
When a timeout occurs:
- The request's CancellationToken is canceled
- The endpoint should handle cancellation gracefully
- Any ongoing operations should be aborted
- Resources should be cleaned up
Example:
[HttpGet("api/data")]
public async Task<IActionResult> GetData(CancellationToken cancellationToken)
{
try
{
var data = await _service.GetDataAsync(cancellationToken);
return Ok(data);
}
catch (OperationCanceledException)
{
// Request was canceled (likely due to timeout)
return StatusCode(504, "Request timeout");
}
}
Debug Mode Behavior¶
Debug Mode Limitation
Request timeouts do not trigger in debug mode. This is by design to allow developers to debug long-running operations without interruption. To test timeout behavior, run the application in Release mode or use integration tests.
Testing Timeouts:
// RequestTimeoutMiddlewareTests.cs
[TestMethod]
public async Task RequestShouldTimeoutAfterConfiguredDuration()
{
// Note: This test may not work in debug mode
// Run tests in Release mode or use integration tests
}
Best Practices¶
Do's¶
-
Enable Request Timeout in Production
-
Set Appropriate Timeouts
-
Use Named Policies for Different Endpoint Types
-
Handle Cancellation Gracefully
-
Monitor Timeout Metrics
- Track timeout occurrences
- Monitor endpoint response times
- Alert on high timeout rates
-
Identify slow endpoints
-
Disable Timeout for Long-Running Operations
Don'ts¶
-
Don't Disable Timeout in Production
-
Don't Set Timeouts Too Short
-
Don't Set Timeouts Too Long
-
Don't Ignore Cancellation Tokens
-
Don't Forget to Test Timeout Behavior
Advanced Scenarios¶
Custom Timeout Response¶
Programmatic Configuration:
services.AddRequestTimeouts(options =>
{
options.DefaultPolicy = new RequestTimeoutPolicy
{
Timeout = TimeSpan.FromSeconds(30),
TimeoutStatusCode = 504,
WriteTimeoutResponse = async context =>
{
context.Response.ContentType = "application/json";
var response = new
{
error = "Request timeout",
message = "The request exceeded the maximum allowed time.",
timeout = "30 seconds",
requestId = context.TraceIdentifier
};
await context.Response.WriteAsJsonAsync(response);
}
};
});
Conditional Timeout Policies¶
Based on Request Characteristics:
services.AddRequestTimeouts(options =>
{
// Default policy
options.DefaultPolicy = new RequestTimeoutPolicy
{
Timeout = TimeSpan.FromSeconds(30)
};
// Fast policy for health checks
options.AddPolicy("Fast", new RequestTimeoutPolicy
{
Timeout = TimeSpan.FromSeconds(5)
});
// Long-running policy for reports
options.AddPolicy("LongRunning", new RequestTimeoutPolicy
{
Timeout = TimeSpan.FromMinutes(5)
});
});
Timeout with Retry Logic¶
Combining Timeout with Retry:
[RequestTimeout("Fast")]
[HttpGet("api/data")]
public async Task<IActionResult> GetData(CancellationToken cancellationToken)
{
var retryPolicy = Policy
.Handle<HttpRequestException>()
.Or<OperationCanceledException>()
.WaitAndRetryAsync(3, retryAttempt => TimeSpan.FromSeconds(Math.Pow(2, retryAttempt)));
try
{
var result = await retryPolicy.ExecuteAsync(async ct =>
{
return await _httpClient.GetAsync("https://api.example.com/data", ct);
}, cancellationToken);
return Ok(await result.Content.ReadAsStringAsync());
}
catch (OperationCanceledException)
{
return StatusCode(504, "Request timeout after retries");
}
}
Troubleshooting¶
Issue: Timeouts Not Triggering¶
Symptoms: Requests exceeding timeout duration are not being canceled.
Solutions: 1. Verify Timeout is Enabled
-
Check Middleware Order
-
Run in Release Mode
- Timeouts do not trigger in debug mode
- Run application in Release mode to test timeout behavior
-
Use integration tests to verify timeout functionality
-
Verify Configuration is Loaded
- Check
RequestTimeoutOptionsis registered - Verify
appsettings.jsoncontains request timeout section - Check options validation passes
Issue: Too Many Timeouts¶
Symptoms: Legitimate requests are timing out.
Solutions: 1. Increase Timeout Duration
-
Use Named Policies for Slow Endpoints
-
Disable Timeout for Specific Endpoints
-
Optimize Slow Endpoints
- Review endpoint performance
- Identify bottlenecks
- Optimize database queries
- Add caching where appropriate
Issue: Timeout Response Not Customized¶
Symptoms: Default timeout response is returned instead of custom response.
Solutions: 1. Verify Custom Response Handler is Configured
// Custom response handlers must be set programmatically
options.DefaultPolicy.WriteTimeoutResponse = async context =>
{
// Custom response logic
};
- Check Response Handler is Not Null
- Ensure
WriteTimeoutResponseis set - Verify handler is not being overridden
Issue: Cancellation Not Handled¶
Symptoms: Endpoints don't respond to cancellation, resources not cleaned up.
Solutions: 1. Accept CancellationToken
public async Task<IActionResult> GetData(CancellationToken cancellationToken)
{
// Use cancellationToken in async operations
}
-
Handle OperationCanceledException
-
Pass CancellationToken to All Async Operations
Summary¶
Request timeout middleware in the ConnectSoft Microservice Template provides:
- ✅ Global Default Timeout: Configurable default timeout policy
- ✅ Named Policies: Different timeout policies for different endpoints
- ✅ Per-Endpoint Timeouts: Override timeout for specific endpoints
- ✅ Custom Responses: Programmatic custom timeout response handlers
- ✅ Graceful Cancellation: Automatic request cancellation on timeout
- ✅ Configurable Status Codes: Custom HTTP status codes for timeouts
- ✅ Production Ready: Configurable for different environments
By following these patterns, teams can:
- Protect Resources: Prevent long-running requests from consuming resources indefinitely
- Ensure Predictability: Guarantee requests complete within expected timeframes
- Improve User Experience: Provide timely feedback when operations take too long
- Monitor Performance: Identify slow endpoints that need optimization
- Handle Timeouts Gracefully: Implement proper cancellation handling
Request timeout is an essential performance and reliability feature that protects microservices from resource exhaustion while ensuring predictable request behavior.