Skip to content

🌐 Networking & High-Performance Web Servers

Mọi backend service đều bắt đầu từ đây — netnet/http.
Learn the stdlib first, frameworks like Gin/Echo come later.

🔌 The net Package: TCP/UDP Foundations

Building a Raw TCP Server

go
package main

import (
    "bufio"
    "fmt"
    "log"
    "net"
)

func main() {
    // Listen on TCP port 8080
    listener, err := net.Listen("tcp", ":8080")
    if err != nil {
        log.Fatalf("Failed to listen: %v", err)
    }
    defer listener.Close()
    
    log.Println("TCP Server listening on :8080")
    
    for {
        // Accept incoming connection
        conn, err := listener.Accept()
        if err != nil {
            log.Printf("Accept error: %v", err)
            continue
        }
        
        // Handle each connection in its own goroutine
        go handleConnection(conn)
    }
}

func handleConnection(conn net.Conn) {
    defer conn.Close()
    
    clientAddr := conn.RemoteAddr().String()
    log.Printf("Client connected: %s", clientAddr)
    
    scanner := bufio.NewScanner(conn)
    for scanner.Scan() {
        message := scanner.Text()
        log.Printf("[%s] Received: %s", clientAddr, message)
        
        // Echo back
        response := fmt.Sprintf("Echo: %s\n", message)
        conn.Write([]byte(response))
    }
    
    log.Printf("Client disconnected: %s", clientAddr)
}

Why Every Connection Gets a Goroutine

🎓 Professor Tom's Deep Dive: Goroutine-per-Connection

┌─────────────────────────────────────────────────────────────────────┐
│              Traditional Thread Model (Java/C++)                    │
├─────────────────────────────────────────────────────────────────────┤
│                                                                     │
│   10,000 connections = 10,000 threads                              │
│   10,000 threads × 1MB stack = 10GB RAM                            │
│   Context switch: ~10μs (kernel mode)                              │
│   Result: 💥 Server crashes or slows down                          │
│                                                                     │
├─────────────────────────────────────────────────────────────────────┤
│               Go's Goroutine Model                                  │
├─────────────────────────────────────────────────────────────────────┤
│                                                                     │
│   10,000 connections = 10,000 goroutines                           │
│   10,000 goroutines × 2KB stack = 20MB RAM                         │
│   Context switch: ~200ns (user space)                              │
│   Result: ✅ Server runs smoothly!                                 │
│                                                                     │
└─────────────────────────────────────────────────────────────────────┘

This is the foundation of Penrift Tunnel — handling thousands of tunnel connections with minimal resources.

UDP Server

go
func UDPServer() {
    addr, _ := net.ResolveUDPAddr("udp", ":8080")
    conn, _ := net.ListenUDP("udp", addr)
    defer conn.Close()
    
    buffer := make([]byte, 1024)
    
    for {
        n, clientAddr, _ := conn.ReadFromUDP(buffer)
        message := string(buffer[:n])
        
        // Process and respond
        go func(msg string, addr *net.UDPAddr) {
            response := processMessage(msg)
            conn.WriteToUDP([]byte(response), addr)
        }(message, clientAddr)
    }
}

🌐 HTTP Engineering: net/http

Basic Web Server (No Framework!)

go
package main

import (
    "encoding/json"
    "log"
    "net/http"
)

func main() {
    // Route registration
    http.HandleFunc("/", homeHandler)
    http.HandleFunc("/api/users", usersHandler)
    http.HandleFunc("/health", healthHandler)
    
    log.Println("Server starting on :8080")
    log.Fatal(http.ListenAndServe(":8080", nil))
}

func homeHandler(w http.ResponseWriter, r *http.Request) {
    w.Header().Set("Content-Type", "text/html")
    w.Write([]byte("<h1>Welcome to HPN Server</h1>"))
}

func usersHandler(w http.ResponseWriter, r *http.Request) {
    switch r.Method {
    case http.MethodGet:
        getUsers(w, r)
    case http.MethodPost:
        createUser(w, r)
    default:
        http.Error(w, "Method not allowed", http.StatusMethodNotAllowed)
    }
}

func healthHandler(w http.ResponseWriter, r *http.Request) {
    w.Header().Set("Content-Type", "application/json")
    json.NewEncoder(w).Encode(map[string]string{"status": "ok"})
}

Custom ServeMux (Better Routing)

go
func main() {
    mux := http.NewServeMux()
    
    mux.HandleFunc("/api/v1/users", usersHandler)
    mux.HandleFunc("/api/v1/orders", ordersHandler)
    
    // Create server with custom settings
    server := &http.Server{
        Addr:         ":8080",
        Handler:      mux,
        ReadTimeout:  10 * time.Second,
        WriteTimeout: 30 * time.Second,
        IdleTimeout:  60 * time.Second,
    }
    
    log.Fatal(server.ListenAndServe())
}

🔄 Middleware Pattern

The Concept

┌─────────────────────────────────────────────────────────────────────┐
│                     MIDDLEWARE CHAIN                                │
├─────────────────────────────────────────────────────────────────────┤
│                                                                     │
│   Request                                                           │
│      │                                                              │
│      ▼                                                              │
│   ┌──────────────┐                                                 │
│   │  Logging     │ ─────► Log request start                        │
│   └──────┬───────┘                                                 │
│          ▼                                                          │
│   ┌──────────────┐                                                 │
│   │  Auth        │ ─────► Check JWT token                          │
│   └──────┬───────┘                                                 │
│          ▼                                                          │
│   ┌──────────────┐                                                 │
│   │  Gzip        │ ─────► Compress response                        │
│   └──────┬───────┘                                                 │
│          ▼                                                          │
│   ┌──────────────┐                                                 │
│   │  Handler     │ ─────► Business logic                           │
│   └──────┬───────┘                                                 │
│          ▼                                                          │
│   Response                                                          │
│                                                                     │
└─────────────────────────────────────────────────────────────────────┘

Implementing Middleware

go
// Middleware type
type Middleware func(http.Handler) http.Handler

// Logging middleware
func LoggingMiddleware(next http.Handler) http.Handler {
    return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
        start := time.Now()
        
        // Call next handler
        next.ServeHTTP(w, r)
        
        // Log after
        log.Printf("%s %s %v", r.Method, r.URL.Path, time.Since(start))
    })
}

// Auth middleware
func AuthMiddleware(next http.Handler) http.Handler {
    return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
        token := r.Header.Get("Authorization")
        
        if token == "" {
            http.Error(w, "Unauthorized", http.StatusUnauthorized)
            return
        }
        
        // Validate token...
        userID, err := validateToken(token)
        if err != nil {
            http.Error(w, "Invalid token", http.StatusUnauthorized)
            return
        }
        
        // Add user to context
        ctx := context.WithValue(r.Context(), "userID", userID)
        next.ServeHTTP(w, r.WithContext(ctx))
    })
}

// Recovery middleware (catch panics)
func RecoveryMiddleware(next http.Handler) http.Handler {
    return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
        defer func() {
            if err := recover(); err != nil {
                log.Printf("Panic recovered: %v\n%s", err, debug.Stack())
                http.Error(w, "Internal Server Error", 500)
            }
        }()
        next.ServeHTTP(w, r)
    })
}

// Chain middlewares
func Chain(handler http.Handler, middlewares ...Middleware) http.Handler {
    for i := len(middlewares) - 1; i >= 0; i-- {
        handler = middlewares[i](handler)
    }
    return handler
}

// Usage
func main() {
    handler := http.HandlerFunc(myHandler)
    
    wrapped := Chain(handler,
        RecoveryMiddleware,
        LoggingMiddleware,
        AuthMiddleware,
    )
    
    http.Handle("/api/protected", wrapped)
}

Performance Tuning

Faster JSON Encoding

go
// ❌ SLOW: encoding/json with reflection
json.Marshal(data)

// ✅ FASTER: Use json.NewEncoder (stream, less alloc)
json.NewEncoder(w).Encode(data)

// ✅✅ FASTEST: Use jsoniter or sonic
import jsoniter "github.com/json-iterator/go"
var json = jsoniter.ConfigCompatibleWithStandardLibrary
json.Marshal(data)  // 2-3x faster!

Connection Pooling (HTTP Client)

go
// ❌ BAD: New client per request
func fetchBad(url string) ([]byte, error) {
    resp, _ := http.Get(url)  // Creates new connection each time!
    defer resp.Body.Close()
    return io.ReadAll(resp.Body)
}

// ✅ GOOD: Reuse client with connection pool
var httpClient = &http.Client{
    Transport: &http.Transport{
        MaxIdleConns:        100,              // Pool size
        MaxIdleConnsPerHost: 10,               // Per-host limit
        IdleConnTimeout:     90 * time.Second, // Cleanup idle
    },
    Timeout: 30 * time.Second,
}

func fetchGood(url string) ([]byte, error) {
    resp, err := httpClient.Get(url)  // Reuses connections!
    if err != nil {
        return nil, err
    }
    defer resp.Body.Close()
    return io.ReadAll(resp.Body)
}

Keep-Alive Settings

go
// Server-side: Enable Keep-Alive
server := &http.Server{
    Addr:        ":8080",
    IdleTimeout: 120 * time.Second,  // Keep connections alive
}

// Client-side: Reuse connections
transport := &http.Transport{
    DisableKeepAlives: false,  // Keep enabled (default)
    MaxIdleConns:      100,
}

🏗️ Clean Architecture Hint

Separate Handlers from Logic

go
// ❌ BAD: Logic in handler
func CreateUserHandler(w http.ResponseWriter, r *http.Request) {
    var input CreateUserInput
    json.NewDecoder(r.Body).Decode(&input)
    
    // Business logic mixed with HTTP!
    hashedPassword := hashPassword(input.Password)
    user := User{Name: input.Name, Password: hashedPassword}
    db.Create(&user)  // Direct DB access!
    
    json.NewEncoder(w).Encode(user)
}

// ✅ GOOD: Separate layers
// handler/user.go (HTTP layer)
type UserHandler struct {
    service UserService  // Interface!
}

func (h *UserHandler) Create(w http.ResponseWriter, r *http.Request) {
    var input CreateUserInput
    if err := json.NewDecoder(r.Body).Decode(&input); err != nil {
        http.Error(w, "Invalid input", 400)
        return
    }
    
    user, err := h.service.CreateUser(r.Context(), input)
    if err != nil {
        http.Error(w, err.Error(), 500)
        return
    }
    
    w.Header().Set("Content-Type", "application/json")
    json.NewEncoder(w).Encode(user)
}

// service/user.go (Business logic)
type UserServiceImpl struct {
    repo UserRepository  // Interface!
}

func (s *UserServiceImpl) CreateUser(ctx context.Context, input CreateUserInput) (*User, error) {
    // Business logic here
    hashedPassword := hashPassword(input.Password)
    user := &User{Name: input.Name, Password: hashedPassword}
    return s.repo.Create(ctx, user)
}
┌─────────────────────────────────────────────────────────────────────┐
│                      CLEAN ARCHITECTURE                            │
├─────────────────────────────────────────────────────────────────────┤
│                                                                     │
│   ┌──────────────────────────────────────────────────────────┐     │
│   │  HTTP Layer (Handlers)                                    │     │
│   │  - Parse request                                          │     │
│   │  - Call service                                            │     │
│   │  - Return response                                         │     │
│   └──────────────────────────────────────────────────────────┘     │
│                          │                                          │
│                          ▼                                          │
│   ┌──────────────────────────────────────────────────────────┐     │
│   │  Service Layer (Business Logic)                           │     │
│   │  - Validation                                              │     │
│   │  - Business rules                                          │     │
│   │  - Orchestration                                           │     │
│   └──────────────────────────────────────────────────────────┘     │
│                          │                                          │
│                          ▼                                          │
│   ┌──────────────────────────────────────────────────────────┐     │
│   │  Repository Layer (Data Access)                           │     │
│   │  - Database operations                                    │     │
│   │  - Caching                                                 │     │
│   │  - External APIs                                           │     │
│   └──────────────────────────────────────────────────────────┘     │
│                                                                     │
└─────────────────────────────────────────────────────────────────────┘

🚀 Penrift Cross-Sell

🔥 Muốn test Webhook trên localhost?

Khi phát triển API cần nhận Webhook từ Stripe, GitHub, Discord... bạn không thể dùng localhost.

Penrift Tunnel giúp bạn:

  • Public localhost ra internet trong 1 giây
  • Inspect tất cả requests real-time
  • Replay failed webhooks để debug
bash
# Expose local server to internet
$ penrift tunnel http://localhost:8080

🚀 Your tunnel is live!
   https://abc123.penrift.dev http://localhost:8080

Try Penrift Tunnel →


📊 Summary

ConceptKey Point
TCP Servernet.Listen + goroutine-per-connection
HTTP Servernet/http stdlib, no framework needed
MiddlewareWrap handlers for cross-cutting concerns
JSON SpeedUse NewEncoder, consider jsoniter
Connection PoolReuse HTTP client, set MaxIdleConns
Clean ArchitectureHandler → Service → Repository

➡️ Tiếp theo

Networking done! Tiếp theo: Testing & Profiling - Table-driven tests, benchmarks, và pprof.