Streaming tanpa buffering yang efisien untuk muatan HTTP besar – dibangun di atasnya net/http.
go get github.com/nativebpm/httpstream
httpstream menyediakan API minimal yang berorientasi streaming untuk membuat permintaan HTTP tanpa melakukan buffering terhadap seluruh payload di memori.
Ideal untuk badan JSON besar, unggahan multibagian, arsip yang dihasilkan, atau umpan data berkelanjutan.
Fitur Utama
-
Streaming data secara langsung melaluiÂ
io.Pipe—tidak ada buffer perantara -
Penggunaan memori yang konstan (
O(1)), berapa pun ukuran muatannya -
Tekanan balik alami (blok tulis ketika penerima lambat)
-
tipisâ
net/http pembungkus—sepenuhnya kompatibel -
Dukungan middleware:Â
func(http.RoundTripper) http.RoundTripper -
API yang lancar agar mudah dibaca (
GET,APOST,AMultipartdll.) -
Tidak ada kebocoran goroutine, tidak ada kebocoran global
Cara Kerjanya
httpstream menghubungkan penulis Anda langsung ke transportasi HTTP. Data dikirimkan saat diproduksi, sehingga server dapat segera memulai pemrosesan—tanpa menunggu seluruh data di-buffer.

Mengapa Streaming Penting
-
Klien HTTP tradisional menyangga seluruh isi permintaan sebelum mengirim. Untuk muatan yang besar atau dihasilkan secara dinamis, hal ini dapat menyebabkan:
-
Penggunaan memori yang tinggi (
O(n)dimanan = ukuran muatan) -
Permulaan transmisi yang lambat (server menunggu unggahan penuh)
-
Kesalahan kehabisan memori di lingkungan terbatas
httpstream menghilangkan masalah ini dengan sengaja.
Contoh Sederhana Dengan Logger
package main
import (
"context"
"log"
"log/slog"
"net/http"
"os"
"time"
"github.com/nativebpm/httpstream"
)
func main() {
logger := slog.New(slog.NewJSONHandler(os.Stdout, &slog.HandlerOptions{
Level: slog.LevelInfo,
}))
loggingMiddleware := httpstream.LoggingMiddleware(logger)
client, err := httpstream.NewClient(&http.Client{Timeout: 10 * time.Second}, "https://httpbin.org")
if err != nil {
log.Fatal(err)
}
resp, err := client.GET(context.Background(), "/get").
Use(loggingMiddleware).
Send()
if err != nil {
log.Fatal(err)
}
resp.Body.Close()
}
Contoh Streaming Multibagian
Contoh ini menunjukkan streaming data antara dua server menggunakan httpstream.
Server 1 (:8080) – Menghasilkan File Besar
package main
import (
"fmt"
"io"
"net/http"
"strings"
)
func main() {
http.HandleFunc("/file", func(w http.ResponseWriter, r *http.Request) {
w.Header().Set("Content-Type", "text/plain")
w.Header().Set("Content-Disposition", "attachment; filename=large.txt")
var builder strings.Builder
for i := 1; i <= 10000000; i++ {
builder.WriteString(fmt.Sprintf("Line %d: This is a line in the large file.\n", i))
}
reader := strings.NewReader(builder.String())
_, err := io.Copy(w, reader)
if err != nil {
http.Error(w, "Failed to generate file", http.StatusInternalServerError)
}
})
fmt.Println("Server 1 running on :8080")
http.ListenAndServe(":8080", nil)
}
Server 2 (:8081) – Menerima Unggahan Multibagian
package main
import (
"fmt"
"io"
"log/slog"
"net/http"
"os"
)
func main() {
logger := slog.New(slog.NewJSONHandler(os.Stdout, &slog.HandlerOptions{Level: slog.LevelInfo}))
http.HandleFunc("/upload", func(w http.ResponseWriter, r *http.Request) {
err := r.ParseMultipartForm(32 << 20) // 32MB max
if err != nil {
logger.Error("Failed to parse multipart", "error", err)
http.Error(w, err.Error(), http.StatusBadRequest)
return
}
file, header, err := r.FormFile("file")
if err != nil {
logger.Error("Failed to get form file", "error", err)
http.Error(w, err.Error(), http.StatusBadRequest)
return
}
defer file.Close()
dst, err := os.Create(header.Filename)
if err != nil {
logger.Error("Failed to create file", "error", err)
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}
defer dst.Close()
_, err = io.Copy(dst, file)
if err != nil {
logger.Error("Failed to copy file", "error", err)
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}
logger.Info("File saved successfully", "filename", header.Filename)
fmt.Fprintf(w, "File %s uploaded and saved", header.Filename)
})
fmt.Println("Server 2 running on :8081")
http.ListenAndServe(":8081", nil)
}
Klien – Streaming File dari Server 1 ke Server 2
package main
import (
"context"
"fmt"
"io"
"log/slog"
"mime"
"net/http"
"runtime"
"time"
"github.com/nativebpm/httpstream"
)
type countingReader struct {
reader io.Reader
count int64
}
func (cr *countingReader) Read(p []byte) (n int, err error) {
n, err = cr.reader.Read(p)
cr.count += int64(n)
return n, err
}
func (cr *countingReader) Close() error {
if closer, ok := cr.reader.(io.Closer); ok {
return closer.Close()
}
return nil
}
func main() {
logger := slog.Default()
var m runtime.MemStats
runtime.ReadMemStats(&m)
logger.Info("Before streaming", "Alloc (KB)", m.Alloc/1024, "TotalAlloc (KB)", m.TotalAlloc/1024)
client := &http.Client{Timeout: 60 * time.Second}
server1Client, _ := httpstream.NewClient(client, "http://localhost:8080")
server1Client.Use(httpstream.LoggingMiddleware(logger.WithGroup("server1")))
server2Client, _ := httpstream.NewClient(client, "http://localhost:8081")
server2Client.Use(httpstream.LoggingMiddleware(logger.WithGroup("server2")))
server1Resp, _ := server1Client.GET(context.Background(), "/file").Timeout(30 * time.Second).Send()
defer server1Resp.Body.Close()
filename := filename(server1Resp.Header, "default_filename")
counter := &countingReader{reader: server1Resp.Body}
server2Resp, _ := server2Client.Multipart(context.Background(), "/upload").
File("file", filename, counter).
Timeout(30 * time.Second).
Send()
defer server2Resp.Body.Close()
runtime.ReadMemStats(&m)
slog.Info("After streaming", "Alloc (KB)", m.Alloc/1024, "TotalAlloc (KB)", m.TotalAlloc/1024)
streamedMB := float64(counter.count) / (1024 * 1024)
slog.Info("Data streamed through pipeline", "bytes", counter.count, "megabytes", fmt.Sprintf("%.2f MB", streamedMB))
}
func filename(headers http.Header, defaultName string) string {
if v := headers.Get("Content-Disposition"); v != "" {
_, params, err := mime.ParseMediaType(v)
if err == nil {
if fn, ok := params["filename"]; ok {
return fn
}
}
}
return defaultName
}
Menjalankan Contoh
Jalankan server dan klien di terminal terpisah:
go run server1/main.go
go run server2/main.go
go run main.go
Log menunjukkan penggunaan memori sebelum dan sesudah streaming dan total data yang dialirkan.
Before streaming "Alloc (KB)"=218 "TotalAlloc (KB)"=218
Sending request server1.method=GET server1.url=http://localhost:8080/file
Response received server1.status=200 server1.duration=1.849253692s
Sending request server2.method=POST server2.url=http://localhost:8081/upload
Response received server2.status=200 server2.duration=4.204263582s
After streaming "Alloc (KB)"=694 "TotalAlloc (KB)"=694
Data streamed through pipeline bytes=478888897 megabytes="456.70 MB"
Upload successful "server2Resp response"="File large.txt uploaded and save"
Hasil
-
Server 1 menghasilkan file besar dengan baris bernomor.
-
Klien mengalirkan file dari Server 1 ke Server 2 tanpa melakukan buffering di memori.
-
Server 2 menyimpan file secara lokal.
-
Log menunjukkan konfirmasi unggahan dan penggunaan memori.
Streaming efisien, konstan dalam memori, dan siap untuk muatan besar.
Contoh Lengkap
Github: https://github.com/nativebpm/httpstream



