Frontend Labs
⋯

Day 25 — Embedding & Composition

Learning Objectives

  • Build transparent wrappers using struct embedding
  • Understand method promotion and shadowing
  • Create MeteredConn for byte-counting — used in your TCP proxy

Key Concepts

// Embedding vs field
type Animal struct { Name string }
func (a Animal) Speak() string { return "..." }

type Dog struct {
    Animal       // embedding — Dog.Speak() works, Dog.Name works
    Breed string
}

type Cat struct {
    pet Animal   // field — must use cat.pet.Speak(), cat.pet.Name
}

// Shadowing — Dog's own method takes precedence
func (d Dog) Speak() string { return "Woof" }

// Interface embedding
type ReadWriter interface {
    io.Reader
    io.Writer
}

Exercise: MeteredConn — Transparent Byte Counter

package main

import (
    "fmt"
    "net"
    "sync/atomic"
)

// MeteredConn wraps net.Conn and counts bytes.
// Transparent: all net.Conn methods still work without modification.
type MeteredConn struct {
    net.Conn           // embedding: all net.Conn methods promoted
    bytesRead    atomic.Int64
    bytesWritten atomic.Int64
}

func NewMeteredConn(conn net.Conn) *MeteredConn {
    return &MeteredConn{Conn: conn}
}

// Override Read to count bytes
func (mc *MeteredConn) Read(b []byte) (int, error) {
    n, err := mc.Conn.Read(b)
    mc.bytesRead.Add(int64(n))
    return n, err
}

// Override Write to count bytes
func (mc *MeteredConn) Write(b []byte) (int, error) {
    n, err := mc.Conn.Write(b)
    mc.bytesWritten.Add(int64(n))
    return n, err
}

func (mc *MeteredConn) Stats() (read, written int64) {
    return mc.bytesRead.Load(), mc.bytesWritten.Load()
}

// TimedHTTPHandler wraps http.Handler and records latency
type TimedHTTPHandler struct {
    http.Handler
    histogram *RingBuffer[int64]
}

func NewTimedHTTPHandler(h http.Handler, histSize int) *TimedHTTPHandler {
    return &TimedHTTPHandler{Handler: h, histogram: NewRingBuffer[int64](histSize)}
}

func (th *TimedHTTPHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
    start := time.Now()
    th.Handler.ServeHTTP(w, r)
    th.histogram.Add(time.Since(start).Milliseconds())
}

func (th *TimedHTTPHandler) P99() int64 {
    samples := th.histogram.All()
    if len(samples) == 0 { return 0 }
    sort.Slice(samples, func(i, j int) bool { return samples[i] < samples[j] })
    idx := int(float64(len(samples)) * 0.99)
    if idx >= len(samples) { idx = len(samples) - 1 }
    return samples[idx]
}

func main() {
    // Create a TCP connection pair for testing
    server, client := net.Pipe()
    defer server.Close()
    defer client.Close()

    // Wrap client connection with metering
    metered := NewMeteredConn(client)

    // Write some data
    go func() {
        metered.Write([]byte("GET /api/data HTTP/1.1\r\nHost: example.com\r\n\r\n"))
    }()

    // Read from server side
    buf := make([]byte, 1024)
    n, _ := server.Read(buf)
    server.Write(buf[:n]) // echo back

    // Read response
    metered.Read(buf)

    read, written := metered.Stats()
    fmt.Printf("Bytes read: %d, written: %d\n", read, written)
}

Real-World Context

MeteredConn is used in your TCP proxy (Week 5) to count bytes per connection and expose them via expvar. This is how your load balancer tracks backend bandwidth usage — billing, capacity planning, and anomaly detection (a connection suddenly transferring 10x normal volume).


Generated from week4.md · 8 Oct 2026 16:36