Error Handling☍
Unlike HttpClient, Flurl.Http throws on any non-2XX HTTP status by default. Here's the reasoning:
-
Non-2XX conditions tend to be "exceptional", that is, they're not expected them under "normal" circumstances and logic flow, hence they fit the
try/catchparadigm. -
Especially in JSON APIs, error response bodies tend to take a different shape than regular responses, and if you're using shortcuts like
url.GetJsonAsync<RegularShape>(), Flurl'stry/catchpattern provides a way to deserialize to something different in thecatchblock.
try {
var result = await url.PostJsonAsync(poco).ReceiveJson<T>();
}
catch (FlurlHttpException ex) {
var error = await ex.GetResponseJsonAsync<TError>();
logger.Write($"Error returned from {ex.Call.Request.Url}: {error.SomeDetails}");
}
The Call property above is an instance of the same FlurlCall object used by event handlers, providing a wealth of details about the call. For simple logging and debugging, FlurlHttpException.Message gives you a handy summary of the error, including the URL, HTTP verb, and status code received.
FlurlHttpException also gives you a few shortcuts for deserializing the body:
Task<string> GetResponseStringAsync();
Task<T> GetResponseJsonAsync<T>();
These are all short-hand for equivalent methods on FlurlHttpException.Call.Response, so you can go that route if you need something different, such as a stream.
Timeouts☍
Flurl.Http defines a special exception type for timeouts: FlurlHttpTimeoutException. This type inherits from FlurlHttpException, and hence will get caught in a catch (FlurlHttpException) block. But you may want to handle timeouts differently:
catch (FlurlHttpTimeoutException) {
// handle timeout
}
catch (FlurlHttpException) {
// handle error response
}
FlurlHttpTimeoutException has no additional properties other than those in FlurlHttpException, but because a timeout implies that no response was received, all response-related properties will always be null.
The default timeout is 100 seconds (same as HttpClient), but this can be configured at any settings level, or inline per request:
await url.WithTimeout(200).GetAsync(); // 200 seconds
await url.WithTimeout(TimeSpan.FromMinutes(10)).GetAsync();
Allowing Non-2XX Responses☍
If you don't like the default throwing behavior, you can change it at any settings level via Settings.AllowedHttpStatusRange. This is a string based setting that accepts wildcards, so if you never want to throw, set it to *.
You can also allow non-2XX at the request level:
url.AllowHttpStatus("400-404,6xx").GetAsync();
url.AllowAnyHttpStatus().GetAsync();
The pattern in the first example is fairly self-explanatory. Allowed characters include digits, commas for separators, hyphens for ranges, and wildcards x or X or *. These syntax rules are the same for Settings.AllowedHttpStatusRange, but there's one subtle behavioral difference: the request-level methods above are additive, so for example you don't have to include 2XX in the request-level pattern if it's already allowed per the settings.
Inspecting the Response Before Deserializing☍
If you prefer to handle non-2XX as part of the normal control flow, that's easy enough:
var response = await url
.AllowAnyHttpStatus()
.GetAsync();
if (response.StatusCode < 300) {
var result = await response.GetJsonAsync<T>();
Console.WriteLine($"Success! {result}")
}
else if (response.StatusCode < 500) {
var error = await response.GetJsonAsync<UserErrorData>();
Console.WriteLine($"You did something wrong! {error}")
}
else {
var error = await response.GetJsonAsync<ServerErrorData>();
Console.WriteLine($"We did something wrong! {error}")
}
Here response is an instance of IFlurlResponse, which wraps (and exposes) a System.Net.Http.HttpResponseMessage. In addition to StatusCode, you can inspect Headers, Cookies, and get the body content in a variety of ways:
Task<T> GetJsonAsync<T>();
Task<string> GetStringAsync();
Task<Stream> GetStreamAsync();
Task<byte[]> GetBytesAsync();
Spot an error or omission? Edit this page.