Skip to content

Instantly share code, notes, and snippets.

@AnjanaMadu
Created December 20, 2024 19:39
Show Gist options
  • Save AnjanaMadu/c641545599124396e3d8b4317f1a6a28 to your computer and use it in GitHub Desktop.
Save AnjanaMadu/c641545599124396e3d8b4317f1a6a28 to your computer and use it in GitHub Desktop.
Streaming File API with Chunk Processing with resume able downloads
package main
import (
"fmt"
"log"
"net/http"
"time"
)
const (
chunkSize = 32 * 1024 // 32KB chunks
totalSize = chunkSize * 100 // ~3.2MB total file
)
// simulateChunk generates a chunk of data with a small delay
func simulateChunk(chunkNum int) []byte {
// Simulate processing delay
time.Sleep(500 * time.Millisecond)
// Generate chunk data
chunk := make([]byte, chunkSize)
for i := range chunk {
chunk[i] = byte(chunkNum % 256)
}
return chunk
}
func downloadHandler(w http.ResponseWriter, r *http.Request) {
// Handle range header for resume
startByte := int64(0)
if rangeHeader := r.Header.Get("Range"); rangeHeader != "" {
fmt.Sscanf(rangeHeader, "bytes=%d-", &startByte)
if startByte >= totalSize {
http.Error(w, "Range out of bounds", http.StatusRequestedRangeNotSatisfiable)
return
}
}
// Set response headers
w.Header().Set("Content-Type", "application/octet-stream")
w.Header().Set("Accept-Ranges", "bytes")
w.Header().Set("Content-Length", fmt.Sprintf("%d", totalSize-startByte))
if startByte > 0 {
w.Header().Set("Content-Range", fmt.Sprintf("bytes %d-%d/%d", startByte, totalSize-1, totalSize))
w.WriteHeader(http.StatusPartialContent)
}
// Calculate starting chunk
startChunk := int(startByte / chunkSize)
offset := int(startByte % chunkSize)
// Stream chunks
for chunkNum := startChunk; chunkNum < totalSize/chunkSize; chunkNum++ {
select {
case <-r.Context().Done():
log.Println("Client disconnected")
return
default:
chunk := simulateChunk(chunkNum)
// Handle partial first chunk for resumed downloads
if chunkNum == startChunk && offset > 0 {
chunk = chunk[offset:]
}
// Write chunk
if _, err := w.Write(chunk); err != nil {
log.Printf("Error writing chunk: %v", err)
return
}
// Flush the chunk
if f, ok := w.(http.Flusher); ok {
f.Flush()
}
}
}
}
func main() {
http.HandleFunc("/download", downloadHandler)
port := ":8080"
fmt.Printf("Server starting on port %s\n", port)
fmt.Printf("Total file size: %d bytes\n", totalSize)
if err := http.ListenAndServe(port, nil); err != nil {
log.Fatal(err)
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment