Streamfab.keepstreams.generic.hook-smeagol-ther... 90%
return bytesRead;
var encrypted = new HookSmeagol<EncryptionHook>(baseStream, encryptionHook); var logged = new HookSmeagol<LoggingHook>(encrypted, loggingHook); var throttled = new HookSmeagol<ThrottlingHook>(logged, throttlingHook); The order matters: the outermost hook sees data all inner hooks have processed it. In the example above, the logger records encrypted bytes, then the throttler sees the same encrypted payload. 6. Performance considerations | Aspect | Guidance | |--------|----------| | Allocation avoidance | Prefer the ReadAsync(Memory<byte>) / WriteAsync(ReadOnlyMemory<byte>) overloads to avoid array rentals. HookSmeagol forwards the exact Memory instance to the hook. | | Buffer reuse | If a hook needs a temporary buffer (e.g., for decryption), allocate it once in the hook’s constructor and reuse it across calls. | | Async‑over‑sync | Never call .Result or .Wait() inside a hook; it can dead‑lock the caller. Use await all the way. | | Seek support | Some inner streams are non‑seekable (e.g., network sockets). The hook must check inner.CanSeek before forwarding Seek . A typical pattern is to throw NotSupportedException if the underlying stream can’t seek. | | Cancellation | Pass the caller’s CancellationToken straight to inner async calls and to any async hook work. This keeps the whole pipeline responsive. | | Thread‑safety | HookSmeagol itself is not thread‑safe – it mirrors the underlying stream’s contract. If you need concurrent reads/writes, wrap the whole pipeline in a SemaphoreSlim or expose a thread‑safe façade. |
using System; using System.IO; using System.IO.Compression; using System.Threading.Tasks; using StreamFab.KeepStreams.Generic; StreamFab.KeepStreams.Generic.Hook-Smeagol-TheR...
You can subscribe using:
public void BeforeRead(IHookContext ctx, byte[] buffer, int offset, int count) /* … */ public void AfterRead(IHookContext ctx, byte[] buffer, int offset, int bytesRead) /* … */ | | Async‑over‑sync | Never call
public void BeforeWrite(IHookContext ctx, byte[] buffer, int offset, int count) /* … */ public void AfterWrite(IHookContext ctx, byte[] buffer, int offset, int count) /* … */
// 3. Post‑hook (e.g., logging, decryption, metrics) await _hook.AfterReadAsync(_ctx, destination.Slice(0, bytesRead), cancellationToken) .ConfigureAwait(false); silently observing and occasionally meddling.
private readonly Stream _inner; private readonly THook _hook; private readonly IHookContext _ctx; // …
The async version ( DisposeAsync ) follows the same order with await . | Hook name | Typical use‑case | Sample code fragment | |-----------|------------------|----------------------| | LoggingHook | Write a line‑by‑line trace of every read/write, optionally throttling large payloads. | await logger.LogAsync($"bytesRead bytes read from ctx.StreamId"); | | CompressionHook | Transparent GZip/Deflate compression on the fly. | var compressor = new GZipStream(_inner, CompressionMode.Compress, leaveOpen:true); | | EncryptionHook | Apply AES‑CTR or ChaCha20 encryption per‑chunk. | Array.Copy(_cipher.TransformBlock(buffer, offset, count), 0, buffer, offset, count); | | MetricsHook | Emit Prometheus counters or OpenTelemetry spans for each operation. | meter.CreateHistogram<long>("stream.read.bytes").Record(bytesRead); | | ThrottlingHook | Enforce a max‑bytes‑per‑second quota. | await _rateLimiter.WaitAsync(bytesRead, cancellationToken); | Why the name “Smeagol”? In the original open‑source demo the author likened the hook to Smeagol – it “follows” the stream everywhere, silently observing and occasionally meddling. The name stuck and became part of the public API. 5. Extending the hook – writing your own THook 5.1 Minimal stub public sealed class MyCustomHook : IStreamHook