-
-
Notifications
You must be signed in to change notification settings - Fork 656
Synthetic Responses
Answer the client directly from a handler without contacting the origin, or replace an origin response entirely. Use e.Respond(ProxyResults.*) as the entry point for synthetic (locally generated) responses.
This page covers:
For modifying an origin response in place (chunk-by-chunk edits), see Streaming Bodies.
| Hook | Effect |
|---|---|
BeforeRequest |
Skip the origin entirely and answer from the proxy |
BeforeResponse |
Replace the response after headers were received from the origin |
AfterResponse |
Too late — the response was already sent to the client |
Decision tree:
- Do you need to edit the origin body? → Use
GetResponseBody/SetResponseBodyorOnResponseBodyWrite(Streaming Bodies). - Do you need to generate a new response? → Use
ProxyResultsbelow. - Is the body small and fits in memory? → Buffered factories (
Html,Json,WithStatus, …). - Is the body large or unbounded? → Streaming factories (
File,Stream).
Build a Response with ProxyResults, then pass it to e.Respond(...):
proxyServer.BeforeRequest += (sender, e) =>
{
if (ShouldBlock(e))
e.Respond(ProxyResults.Json(new { error = "blocked" }, HttpStatusCode.Forbidden));
return Task.CompletedTask;
};e.Respond(ProxyResults.Html("<html><body>Blocked</body></html>"));Sets Content-Type: text/html; charset=utf-8.
e.Respond(ProxyResults.Json(new { error = "denied" }, HttpStatusCode.Forbidden));Uses System.Text.Json. For NativeAOT / source generation, pass a JsonTypeInfo<T>. For Newtonsoft.Json, serialize manually and use ProxyResults.Text(json).
e.Respond(ProxyResults.WithStatus(HttpStatusCode.NotFound, "Not found"));
e.Respond(ProxyResults.NoContent());e.Respond(ProxyResults.Bytes(pngBytes, "image/png"));e.Respond(ProxyResults.Redirect("https://safe.example/", HttpStatusCode.MovedPermanently));Default is 302 Found. Use 307 or 308 to preserve the request method.
For large files or unbounded streams, use ProxyResults.File or ProxyResults.Stream. Both return a StreamingProxyResult consumed by e.RespondStreaming(...):
// Serve a cached file without buffering it in memory
e.RespondStreaming(ProxyResults.File(@"C:\cache\large.bin", "application/octet-stream"), closeServerConnection: false);
// Custom stream (e.g. server-sent events)
e.RespondStreaming(ProxyResults.Stream(
HttpStatusCode.OK,
"text/event-stream",
async (stream, ct) =>
{
await stream.WriteAsync("data: hello\n\n"u8.ToArray(), ct);
}), closeServerConnection: false);
// Fixed-length stream — set contentLength to avoid chunked framing
e.Respond(ProxyResults.Stream(
HttpStatusCode.OK,
"application/octet-stream",
contentLength: fileInfo.Length,
writeBody: async (stream, ct) => { /* write exactly contentLength bytes */ }));See Streaming Bodies — Generate a body as a stream for framing details (Content-Length vs chunked / HTTP/2 DATA frames).
ProxyResults.File always returns the full file with status 200. HTTP Range: request headers are not handled. This works for HttpClient, curl, and wget, but may fail for:
- Browser
<video>/<audio>tags that require 206 Partial Content for seeking - Download managers that resume partial downloads
For Range support, use ProxyResults.Stream (or RespondStreaming directly) with custom range logic.
| Legacy | Preferred |
|---|---|
e.Ok(html) |
e.Respond(ProxyResults.Html(html)) — Ok still works and now sets Content-Type: text/html
|
e.GenericResponse(body, status) |
e.Respond(ProxyResults.WithStatus(status, body)) |
e.Redirect(url) |
e.Respond(ProxyResults.Redirect(url)) — supports custom status codes |
Manual RespondStreaming setup |
e.RespondStreaming(ProxyResults.Stream(...)) or e.RespondStreaming(ProxyResults.File(...))
|
e.Respond(new Response { ... }) |
Still valid for advanced/custom cases |
-
Calling
Respondafter the response was sent throwsInvalidOperationException. Only call fromBeforeRequestorBeforeResponse. -
Replacing a response after the origin replied — the original server body is drained so the connection can be reused. For large origin bodies you don't want to read, pass
closeServerConnection: trueor calle.TerminateServerConnection(). -
Do not buffer large bodies through
Html/Json— useFileorStreaminstead. -
AfterResponsecannot synthesize — useBeforeRequestorBeforeResponse.