Compatibility reference for StreamWriter and SendFile. The native Linux
io_uring worker backends currently reject streamed or hijacked responses, so these APIs
are documented here with their current runtime limits.
ALOS exposes a StreamWriter interface for streamed responses. When that API is
available, it uses HTTP/1.1 chunked transfer encoding or HTTP/2 DATA frames with flow
control. The native Linux io_uring worker backends currently return an error
instead of allowing streamed responses.
Current status: StreamWriter, SetStreamer,
and SendFile are compatibility APIs today. They are not available on the
native Linux io_uring worker runtimes yet.
Transfer-Encoding: chunked. Each WriteChunk sends a sized chunk. End
with a zero-length chunk via Close.
Full flow control with WINDOW_UPDATE management. Automatic frame
splitting at maxFrameSize. END_STREAM on close.
When a streamed response path is available, the StreamWriter interface provides
protocol-agnostic streaming:
| Method | Description |
|---|---|
WriteHeader(statusCode int, headers [][2]string, contentType string)
|
Send the response status, headers, and content type. Must be called first. |
WriteChunk(data []byte) error |
Send a chunk of data to the client |
Flush() error |
Flush any buffered data immediately |
Close() error |
End the stream (sends trailing chunk / END_STREAM flag) |
type StreamWriter interface { WriteHeader(statusCode int, headers [][2]string, contentType string) error WriteChunk(data []byte) error Flush() error Close() error }
Access the stream writer via req.StreamWriter on compatible request paths, or
via resp.Streamer() / resp.SetStreamer(). On the native Linux
io_uring worker runtimes, req.StreamWriter is
nil.
r.GET("/stream", func(req *core.Request, resp *core.Response) { sw := req.StreamWriter sw.WriteHeader(200, [][2]string{ {"Cache-Control", "no-cache"}, }, "text/event-stream") for i := 0; i < 10; i++ { data := fmt.Sprintf("data: event %d\n\n", i) sw.WriteChunk([]byte(data)) sw.Flush() time.Sleep(1 * time.Second) } sw.Close() })
When streaming is enabled on a compatible path, HTTP/1.1 uses chunked transfer encoding. That is useful for Server-Sent Events, live logs, and progressive rendering:
r.GET("/events", func(req *core.Request, resp *core.Response) { sw := req.StreamWriter sw.WriteHeader(200, [][2]string{ {"Cache-Control", "no-cache"}, {"Connection", "keep-alive"}, }, "text/event-stream") for i := 0; i < 50; i++ { data := fmt.Sprintf("id: %d\nevent: tick\ndata: {\"count\":%d}\n\n", i, i) if err := sw.WriteChunk([]byte(data)); err != nil { break } sw.Flush() time.Sleep(500 * time.Millisecond) } sw.Close() })
r.GET("/logs", func(req *core.Request, resp *core.Response) { sw := req.StreamWriter sw.WriteHeader(200, nil, "text/plain; charset=utf-8") logCh := getLogChannel() for line := range logCh { if err := sw.WriteChunk([]byte(line + "\n")); err != nil { break } sw.Flush() } sw.Close() })
r.GET("/users/export", func(req *core.Request, resp *core.Response) { sw := req.StreamWriter sw.WriteHeader(200, nil, "application/json") sw.WriteChunk([]byte("[")) first := true for _, user := range getAllUsers() { if !first { sw.WriteChunk([]byte(",")) } first = false data, _ := json.Marshal(user) sw.WriteChunk(data) } sw.WriteChunk([]byte("]")) sw.Close() })
When streaming is available over HTTP/2, ALOS uses DATA frames with built-in flow control and automatically:
maxFrameSize (default 16 KB)WINDOW_UPDATE frames arriver.GET("/h2-stream", func(req *core.Request, resp *core.Response) { sw := req.StreamWriter sw.WriteHeader(200, nil, "application/octet-stream") bigChunk := make([]byte, 1<<20) sw.WriteChunk(bigChunk) sw.Close() })
On compatible non-worker paths, you don't need to detect the protocol. The same
StreamWriter API works for both HTTP/1.1 and HTTP/2. ALOS creates the
correct implementation based on req.IsH2.
r.GET("/adaptive", func(req *core.Request, resp *core.Response) { sw := req.StreamWriter sw.WriteHeader(200, nil, "application/octet-stream") chunkSize := 32 * 1024 if req.IsH2 { chunkSize = 256 * 1024 } buf := make([]byte, chunkSize) for { n, err := dataSource.Read(buf) if n > 0 { sw.WriteChunk(buf[:n]) } if err != nil { break } } sw.Close() })
SendFile uses the same streamed-response machinery as StreamWriter.
It is documented here for compatibility paths and includes automatic MIME detection for ~40
extensions, path traversal prevention, range requests, and symlink resolution:
r.GET("/files/*path", func(req *core.Request, resp *core.Response) { resp.SendFile("./public/" + req.ParamValue("path")) })
Use WithAttachment to trigger a browser download with a specific filename:
r.GET("/download", func(req *core.Request, resp *core.Response) { resp.SendFile("alos-tls.exe", core.WithAttachment("alos-tls.exe"), core.WithRateLimit(100), ) })
r.GET("/limited/:file", func(req *core.Request, resp *core.Response) { resp.SendFile( "./files/" + req.ParamValue("file"), core.WithAttachment("download.zip"), core.WithRateLimit(50), ) })
Allow a fast initial burst, then throttle to a steady rate:
resp.SendFile( "./files/large-archive.tar.gz", core.WithAttachment("large-archive.tar.gz"), core.WithRateLimitBurst(100, 400), )
This allows an initial burst at 400 Mbps, then throttles to 100 Mbps for the remainder.
| Option | Description |
|---|---|
WithAttachment(filename string) |
Set Content-Disposition: attachment; filename="..." for download.
Filename is the name shown to the user. |
WithContentType(mime string) |
Override automatic MIME type detection |
WithRateLimit(mbps int64) |
Throttle download speed in Mbps. Range: 1–250,000. Internally converts
mbps * 125000 to bytes/sec.
|
WithRateLimitBurst(mbps, burstMbps int64) |
Throttle with a custom burst size in Mbps. Allows initial burst before throttling kicks in. |
resp.SendFile("./assets/report.pdf", core.WithAttachment("monthly-report.pdf"), core.WithContentType("application/pdf"), core.WithRateLimit(200), )
resp.SendFile("data.bin", core.WithContentType("application/x-custom"))
File serving includes built-in security measures:
.. segments and absolute
paths are rejected, preventing directory escapeRange headers for
resumable downloadsStreamWriter instead of being loaded entirely into memoryAlways use a dedicated directory for served files. Never pass user input directly as an
absolute path to SendFile. The path is sanitized but defense-in-depth is
recommended.
When streaming is available, stream writers (H1, H2, and plain) support per-connection and global bandwidth limiting via the server config:
s := core.New(core.Config{ Addr: ":443", ConnBandwidth: core.BandwidthConfig{ MaxDownloadRate: 40, BurstSize: 16, }, GlobalBandwidth: core.BandwidthConfig{ MaxDownloadRate: 1600, BurstSize: 800, }, })
These limits apply to all streamed responses, including SendFile downloads. Use
ConnBandwidth to limit per-connection speed and GlobalBandwidth
for server-wide throughput.