http-stream is a Go 1.26 service that copies an HTTP source response body directly into either an HTTP target request body or a local file without buffering the full payload to disk. The service is controlled through a gRPC API and supports pluggable streaming pipeline stages, so transforms such as encryption can be applied in flight.
- gRPC service contract in
api/httpstream/v1/httpstream.proto - gRPC server in
cmd/http-streamd - HTTP source-to-target streaming with zero-disk buffering
- HTTP source-to-local-disk streaming
- Extensible pipeline registry over
io.Reader/io.ReadCloser - Built-in
encrypt.aes_ctrstage for streaming encryption before upload - Unit tests for the pipeline stage and the HTTP transfer flow
The service exposes one RPC:
rpc Transfer(TransferRequest) returns (TransferResponse);
rpc TransferStream(TransferRequest) returns (stream TransferProgress);TransferRequest contains:
source: upstream HTTP request definition, typicallyGETtarget: downstream HTTP request definition for remote uploads, or a local disk path for on-machine writespipeline: ordered stages to wrap the source stream before it is sent to the target
TransferResponse now includes transfer observability fields:
bytesTransferred: final streamed byte countsourceContentLength: source response content length when knowndurationMillis: end-to-end transfer duration inside the serviceaverageBytesPerSecond: average throughput over the completed transferprogressPercent: final completion percentage, typically100for successful transfers
TransferStream is the UI-oriented variant. It emits:
- an initial event with
bytesTransferred = 0 - intermediate progress events while bytes are flowing
- a final event with
done = true
The service now speaks standard protobuf gRPC, so IDE clients such as GoLand or IntelliJ gRPC requests can use the proto contract directly.
HTTP target example:
{
"source": {
"method": "GET",
"url": "https://source.example/object",
"headers": {
"Authorization": "Bearer source-token"
}
},
"target": {
"method": "PUT",
"url": "https://target.example/object",
"headers": {
"Authorization": "Bearer target-token",
"Content-Type": "application/octet-stream"
}
},
"pipeline": [
{
"name": "encrypt.aes_ctr",
"config": {
"key_b64": "<base64-encoded-16-24-or-32-byte-key>",
"iv_b64": "<base64-encoded-16-byte-iv>"
}
}
]
}Local disk target example:
{
"source": {
"method": "GET",
"url": "https://source.example/object"
},
"target": {
"localPath": "/tmp/http-stream/object.bin"
}
}go run ./cmd/http-streamdSet HTTP_STREAM_LISTEN_ADDR to override the default listen address :8080.
Set HTTP_STREAM_PROGRESS_LOG_INTERVAL to control periodic progress logs. Example: 500ms, 2s, 5s.
Build the image locally:
docker build -t http-stream:local .Run the container locally:
docker run --rm -p 8080:8080 \
-e HTTP_STREAM_LISTEN_ADDR=:8080 \
http-stream:localIf you use local disk targets from inside the container, mount a writable host directory into the container and point target.localPath at that mounted path.
The repository includes a workflow at .github/workflows/publish-image.yml that publishes to:
ghcr.io/openprojectx/http-stream
Publish behavior:
- triggers on pushes to
main - also supports manual
workflow_dispatch - tags the image with the first 8 characters of the commit SHA
- uses GitHub Actions Buildx cache to accelerate repeated image builds
Example published tag:
ghcr.io/openprojectx/http-stream:1a2b3c4d
go test ./...See CONTRIBUTING.md for setup instructions, GoLand guidance, and the usual build, run, format, and test workflow.
To add a new pipeline transform:
- Implement
pipeline.Stage. - Register it in the registry used by the server.
- Pass the stage name and config in the
pipelinefield of the request.
This keeps transport concerns, HTTP streaming, and transform logic separated so the project can grow into retries, observability, auth plugins, or richer transfer policies without reworking the core pipeline.
For local disk targets, the service creates parent directories automatically and returns target_status_code = 0 because no downstream HTTP response exists.
For observability, the gRPC response now carries final transfer metrics so clients can record throughput and completion without scraping logs.
For continuous observability, use TransferStream from the UI and bind progressPercent or bytesTransferred / sourceContentLength to the progress bar.
The service now logs detailed transfer lifecycle events for debugging:
- transfer start with source, target, and pipeline size
- source response status and content length
- target selection and request failures
- periodic progress snapshots with rate and percent complete
- final completion metrics
Progress logging is rate-limited by HTTP_STREAM_PROGRESS_LOG_INTERVAL and defaults to 2s.