Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Sign up
Appearance settings

reactiveui/Fusillade

Repository files navigation

NuGet Stats Build Code Coverage


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 handler

Design 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 fusillade

Optional (examples below): Akavache for caching.

  • .NET CLI: dotnet add package Akavache.SystemTextJson

Quick start

Create HttpClient instances by picking the right handler from NetCache:

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.

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.

// 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:

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

public interface IRequestCache
{
 Task Save(HttpRequestMessage request, HttpResponseMessage response, string key, CancellationToken ct);
 Task<byte[]> 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

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);
// 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<byte[]> 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
// 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:

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.
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.
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.

What’s with the name?

"Fusillade" is a synonym for Volley 🙂

Sponsor this project

Packages

Contributors 18

Languages

AltStyle によって変換されたページ (->オリジナル) /