https://github.com/reactiveui/fusillade
An opinionated HTTP library for Mobile Development
https://github.com/reactiveui/fusillade
cross-platform dotnet dotnet-core http http-client mobile speculative-requests volley xamarin
Last synced: 2 days ago
JSON representation
An opinionated HTTP library for Mobile Development
- Host: GitHub
- URL: https://github.com/reactiveui/fusillade
- Owner: reactiveui
- License: mit
- Created: 2014-04-24T00:08:11.000Z (over 11 years ago)
- Default Branch: main
- Last Pushed: 2025-10-04T13:29:35.000Z (3 days ago)
- Last Synced: 2025-10-04T15:22:28.927Z (3 days ago)
- Topics: cross-platform, dotnet, dotnet-core, http, http-client, mobile, speculative-requests, volley, xamarin
- Language: C#
- Homepage:
- Size: 2.07 MB
- Stars: 312
- Watchers: 20
- Forks: 32
- Open Issues: 12
-
Metadata Files:
- Readme: README.md
- Contributing: CONTRIBUTING.md
- License: LICENSE
- Code of conduct: CODE_OF_CONDUCT.md
Awesome Lists containing this project
README
[](https://www.nuget.org/packages/fusillade)  [](https://codecov.io/gh/reactiveui/fusillade)
## Fusillade: An opinionated HTTP library for .NET apps
Fusillade helps you write efficient, resilient networked apps by composing HttpMessageHandlers for HttpClient. It focuses on:
- Request de-duplication for relevant HTTP methods
- Concurrency limiting via a priority-aware operation queue
- Request prioritization for predictable UX
- Speculative background fetching with byte-budget limits
- Optional caching of responses and an offline replay handlerDesign inspirations include Android's Volley and Picasso.
Supported targets: library is built for .NET Standard 2.0 and is used from modern .NET (e.g., .NET 8/9), Xamarin/Mono, and .NET for iOS/Android/Mac Catalyst apps.
## Install
- Package Manager: Install-Package fusillade
- .NET CLI: dotnet add package fusilladeOptional (examples below): Akavache for caching.
- .NET CLI: dotnet add package Akavache.SystemTextJson
## Quick start
Create HttpClient instances by picking the right handler from NetCache:
```csharp
using Fusillade;
using System.Net.Http;// Highest priority: the user is waiting now
var client = new HttpClient(NetCache.UserInitiated);
var json = await client.GetStringAsync("https://httpbin.org/get");
```Available built-ins:
- NetCache.UserInitiated: foreground work the user is waiting for
- NetCache.Background: background work that should not block UI work
- NetCache.Speculative: background prefetching with a byte budget
- NetCache.Offline: fetch from cache only (no network)By default, requests are processed four at a time via an operation queue.
## Core ideas
### 1) Request de-duplication
Fusillade de-duplicates concurrent requests for the same resource when the method is GET, HEAD, or OPTIONS. If multiple callers request the same URL concurrently, only one on-the-wire request is made; the others join the same in-flight response.
This happens transparently in RateLimitedHttpMessageHandler.
### 2) Concurrency limiting and prioritization
All work is scheduled through an OperationQueue (default parallelism is 4). Each handler has an effective priority:
- Priority.UserInitiated (100)
- Priority.Background (20)
- Priority.Speculative (10)
- Priority.Explicit (custom base with offset)Higher numbers run before lower ones. You can set a custom base (Explicit) and an offset to fit your scenario.
```csharp
using Fusillade;
using Punchclock;
using System.Net.Http;// Custom queue with 2 concurrent slots
var queue = new OperationQueue(2);var handler = new RateLimitedHttpMessageHandler(
new HttpClientHandler(),
basePriority: Priority.Explicit,
priority: 500, // higher runs earlier
opQueue: queue);var client = new HttpClient(handler);
```### 3) Speculative background fetching with byte budgets
Use NetCache.Speculative for prefetching scenarios. Limit the total number of bytes fetched; once the limit is reached, further speculative requests are canceled.
```csharp
// Reset byte budget to 5 MB (e.g., on app resume)
NetCache.Speculative.ResetLimit(5 * 1024 * 1024);var prefetch = new HttpClient(NetCache.Speculative);
_ = prefetch.GetStringAsync("https://example.com/expensive-data");
```To stop speculative fetching immediately:
```csharp
NetCache.Speculative.ResetLimit(-1); // any further requests will be canceled
```## Caching and offline
Fusillade can optionally cache responses (body bytes + headers) and replay them when offline.
There are two ways to wire caching:
1) Provide a cacheResultFunc to RateLimitedHttpMessageHandler, which gets called with the response and a unique request key when a response is received.
2) Set NetCache.RequestCache with an implementation of IRequestCache. Fusillade will invoke Save and Fetch automatically.
### IRequestCache
```csharp
public interface IRequestCache
{
Task Save(HttpRequestMessage request, HttpResponseMessage response, string key, CancellationToken ct);
Task Fetch(HttpRequestMessage request, string key, CancellationToken ct);
}
```- Save is called once the handler has fully buffered the body (as ByteArrayContent) and cloned headers.
- Fetch should return the previously saved body bytes for the key (or null if not found).Keys are generated by RateLimitedHttpMessageHandler.UniqueKeyForRequest(request). Treat the key as an implementation detail; persist what you receive and return it during Fetch.
### Simple Akavache-based cache
```csharp
using Akavache;
using Akavache.SystemTextJson;
using Fusillade;
using System.Net.Http;// Initialize a simple in-memory Akavache cache
var database = CacheDatabase.CreateBuilder().WithSerializerSystemTextJson().Build();
var blobCache = new InMemoryBlobCache(database.Serializer);// Option A: Provide a cacheResultFunc directly
var cachingHandler = new RateLimitedHttpMessageHandler(
new HttpClientHandler(),
Priority.UserInitiated,
cacheResultFunc: async (rq, resp, key, ct) =>
{
var data = await resp.Content.ReadAsByteArrayAsync(ct);
await blobCache.Insert(key, data);
});var client = new HttpClient(cachingHandler);
var fresh = await client.GetStringAsync("https://httpbin.org/get");// Option B: Implement IRequestCache and set NetCache.RequestCache
NetCache.RequestCache = new MyRequestCache(blobCache);
``````csharp
// Example IRequestCache wrapper over Akavache
class MyRequestCache : IRequestCache
{
private readonly IBlobCache _cache;
public MyRequestCache(IBlobCache cache) => _cache = cache;public async Task Save(HttpRequestMessage request, HttpResponseMessage response, string key, CancellationToken ct)
{
var bytes = await response.Content.ReadAsByteArrayAsync(ct);
await _cache.Insert(key, bytes);
}public Task Fetch(HttpRequestMessage request, string key, CancellationToken ct)
=> _cache.Get(key);
}
```### Offline replay
Use OfflineHttpMessageHandler to serve cached data only (no network). This handler asks IRequestCache (or your custom retrieveBodyFunc) for the cached body and returns:
- 200 OK with the cached body, or
- 503 Service Unavailable if not found```csharp
// Use NetCache.Offline after setting NetCache.RequestCache
var offline = new HttpClient(NetCache.Offline);
var data = await offline.GetStringAsync("https://httpbin.org/get");// Or construct explicitly
var offlineExplicit = new HttpClient(new OfflineHttpMessageHandler(
async (rq, key, ct) => await blobCache.Get(key)));
```## Dependency injection and Splat integration
If you use Splat, you can initialize NetCache to use your container’s services via the provided extension:
```csharp
using Splat.Builder;var app = AppBuilder.CreateSplatBuilder().Build();
app.CreateFusilladeNetCache();
```You can also register a platform-specific HttpMessageHandler (e.g., NSUrlSessionHandler on iOS, AndroidMessageHandler on Android) in your container beforehand; NetCache will pick it up as the inner HTTP handler.
## Advanced configuration
- Custom OperationQueue: override NetCache.OperationQueue with your own queue to control concurrency for the entire app.
```csharp
using Punchclock;
NetCache.OperationQueue = new OperationQueue(maxConcurrency: 6);
```- Custom priorities: compose RateLimitedHttpMessageHandler with Priority.Explicit and an offset to place certain pipelines ahead or behind the defaults.
```csharp
var urgent = new RateLimitedHttpMessageHandler(new HttpClientHandler(), Priority.Explicit, priority: 1_000);
var slow = new RateLimitedHttpMessageHandler(new HttpClientHandler(), Priority.Explicit, priority: -50);
```- Deduplication scope: deduplication is per-HttpMessageHandler instance via an in-memory in-flight map. Multiple handlers mean multiple scopes.
## Usage recipes
- Image gallery / avatars
- Use RateLimitedHttpMessageHandler for GETs
- De-dup prevents duplicate downloads for the same URL
- Use Background for preloading next images; switch to UserInitiated for visible images- Boot-time warmup
- On app start/resume, set NetCache.Speculative.ResetLimit to a sensible budget
- Queue speculative GETs for likely-next screens to reduce perceived latency- Offline-first data views
- Populate cache during online sessions using cacheResultFunc or IRequestCache
- When network is unavailable, point HttpClient to NetCache.Offline## FAQ
- How many requests run at once?
- Default is 4 (OperationQueue with concurrency 4). Override via NetCache.OperationQueue or pass a custom queue to a handler.- Which methods are de-duplicated?
- GET, HEAD, and OPTIONS.- How are cache keys generated?
- Via RateLimitedHttpMessageHandler.UniqueKeyForRequest(request). Treat this as an implementation detail; persist and reuse as given.- Can I cancel a request?
- Use CancellationToken in HttpClient APIs; dedup ensures the underlying request cancels only when all dependents cancel.## Contribute
Fusillade is developed under an OSI-approved open source license, making it freely usable and distributable, even for commercial use. We ❤ our contributors and welcome new contributors of all experience levels.
- Answer questions on StackOverflow: https://stackoverflow.com/questions/tagged/fusillade
- Share knowledge and mentor the next generation of developers
- Donations: https://reactiveui.net/donate and Corporate Sponsorships: https://reactiveui.net/sponsorship
- Ask your employer to support open-source: https://github.com/github/balanced-employee-ip-agreement
- Improve documentation and examples
- Contribute features and bugfixes via PRs## What’s with the name?
“Fusillade” is a synonym for Volley 🙂