Giao diện
🌐 Networking & High-Performance Web Servers
Mọi backend service đều bắt đầu từ đây —
Learn the stdlib first, frameworks like Gin/Echo come later.
net và net/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📊 Summary
| Concept | Key Point |
|---|---|
| TCP Server | net.Listen + goroutine-per-connection |
| HTTP Server | net/http stdlib, no framework needed |
| Middleware | Wrap handlers for cross-cutting concerns |
| JSON Speed | Use NewEncoder, consider jsoniter |
| Connection Pool | Reuse HTTP client, set MaxIdleConns |
| Clean Architecture | Handler → Service → Repository |
➡️ Tiếp theo
Networking done! Tiếp theo: Testing & Profiling - Table-driven tests, benchmarks, và pprof.