Context
Understand the Context API, request/response methods, and critical memory safety rules.
6 minute read
The router.Context provides access to the request/response and various utility methods. Understanding its lifecycle is critical for memory safety.
⚠️ Memory Safety - CRITICAL
Context objects are pooled and reused across requests. You must understand context lifecycle for memory safety.
CRITICAL RULES
- DO NOT retain references to Context objects beyond the request handler lifetime.
- For async operations, copy needed data from Context before starting goroutines.
- The router automatically returns contexts to the pool after request completion.
- DO NOT access Context concurrently. It is NOT thread-safe.
Why This Matters
- Memory leaks: Retaining references prevents contexts from being garbage collected.
- Data corruption: Contexts are reused. Old data may appear in new requests.
- Security issues: Sensitive request data may leak to other requests.
- Undefined behavior: Use-after-release causes unpredictable bugs.
Correct Usage
// ✅ CORRECT: Normal handler - context used within handler
func handler(c *router.Context) {
userID := c.Param("id")
c.JSON(200, map[string]string{"id": userID})
// Context automatically returned to pool by router
}
// ✅ CORRECT: Async operation with copied data
func handler(c *router.Context) {
// Copy needed data before starting goroutine
userID := c.Param("id")
go func(id string) {
// Process async work with copied data...
processAsync(id)
}(userID)
}
Incorrect Usage
// ❌ WRONG: Retaining context reference
var globalContext *router.Context
func handler(c *router.Context) {
globalContext = c // BAD! Memory leak and data corruption
}
// ❌ WRONG: Passing context to goroutine
func handler(c *router.Context) {
go func(ctx *router.Context) {
// BAD! Context may be reused by another request
processAsync(ctx.Param("id"))
}(c)
}
// ❌ WRONG: Storing context in struct
type Service struct {
ctx *router.Context // BAD! Never do this
}
Request Information
Basic Request Data
func handler(c *router.Context) {
// HTTP method
method := c.Request.Method // "GET", "POST", etc.
// URL path
path := c.Request.URL.Path // "/users/123"
// Headers
userAgent := c.Request.Header.Get("User-Agent")
contentType := c.Request.Header.Get("Content-Type")
// Remote address
remoteAddr := c.Request.RemoteAddr // "192.168.1.1:12345"
}
Path Parameters
Extract parameters from the URL path:
// Route: /users/:id/posts/:post_id
r.GET("/users/:id/posts/:post_id", func(c *router.Context) {
userID := c.Param("id")
postID := c.Param("post_id")
c.JSON(200, map[string]string{
"user_id": userID,
"post_id": postID,
})
})
Query Parameters
// GET /search?q=golang&limit=10&page=2
r.GET("/search", func(c *router.Context) {
query := c.Query("q") // "golang"
limit := c.Query("limit") // "10"
page := c.Query("page") // "2"
c.JSON(200, map[string]string{
"query": query,
"limit": limit,
"page": page,
})
})
Form Data
// POST with form data
r.POST("/login", func(c *router.Context) {
username := c.FormValue("username")
password := c.FormValue("password")
c.JSON(200, map[string]string{
"username": username,
})
})
Response Methods
JSON Responses
// Standard JSON (HTML-escaped)
c.JSON(200, data)
// Indented JSON (for debugging)
c.IndentedJSON(200, data)
// Pure JSON (no HTML escaping - 35% faster!)
c.PureJSON(200, data)
// Secure JSON (anti-hijacking prefix)
c.SecureJSON(200, data)
// ASCII JSON (pure ASCII with \uXXXX)
c.AsciiJSON(200, data)
// JSONP (with callback)
c.JSONP(200, data, "callback")
Other Response Formats
// YAML
c.YAML(200, config)
// Plain text
c.String(200, "Hello, World!")
c.Stringf(200, "Hello, %s!", name)
// HTML
c.HTML(200, "<h1>Welcome</h1>")
// Binary data
c.Data(200, "image/png", imageBytes)
// Stream from reader (zero-copy!)
c.DataFromReader(200, size, "video/mp4", file, nil)
// Status only
c.Status(204) // No Content
File Serving
// Serve file
c.ServeFile("/path/to/file.pdf")
// Force download
c.Download("/path/to/file.pdf", "custom-name.pdf")
Request Headers
Reading Headers
func handler(c *router.Context) {
userAgent := c.Request.Header.Get("User-Agent")
auth := c.Request.Header.Get("Authorization")
contentType := c.Request.Header.Get("Content-Type")
}
Setting Response Headers
func handler(c *router.Context) {
c.Header("Cache-Control", "no-cache")
c.Header("X-Custom-Header", "value")
c.JSON(200, data)
}
Helper Methods
Content Type Detection
func handler(c *router.Context) {
if c.IsJSON() {
// Request has JSON content-type
}
if c.AcceptsJSON() {
c.JSON(200, data)
} else if c.AcceptsHTML() {
c.HTML(200, htmlContent)
}
}
Client Information
func handler(c *router.Context) {
clientIP := c.ClientIP() // Real IP (considers X-Forwarded-For)
isSecure := c.IsHTTPS() // HTTPS check
}
Redirects
func handler(c *router.Context) {
c.Redirect(301, "/new-url") // Permanent redirect
c.Redirect(302, "/temp") // Temporary redirect
}
Cookies
// Set cookie
c.SetCookie(
"session_id", // name
"abc123", // value
3600, // max age (seconds)
"/", // path
"", // domain
false, // secure
true, // httpOnly
)
// Get cookie
sessionID, err := c.GetCookie("session_id")
Passing Values Between Middleware
Use context.WithValue() to pass values between middleware and handlers:
// Define context keys to avoid collisions
type contextKey string
const userKey contextKey = "user"
// In middleware - create new request with value
func AuthMiddleware() router.HandlerFunc {
return func(c *router.Context) {
user := authenticateUser(c)
// Create new context with value
ctx := context.WithValue(c.Request.Context(), userKey, user)
c.Request = c.Request.WithContext(ctx)
c.Next()
}
}
// In handler - retrieve value from request context
func handler(c *router.Context) {
user, ok := c.Request.Context().Value(userKey).(*User)
if !ok || user == nil {
c.JSON(401, map[string]string{"error": "Unauthorized"})
return
}
c.JSON(200, user)
}
Note
Use typed keys (like contextKey) instead of string keys to avoid collisions between packages.
File Uploads
r.POST("/upload", func(c *router.Context) {
// Single file
file, err := c.File("avatar")
if err != nil {
c.JSON(400, map[string]string{"error": "avatar required"})
return
}
// File info
fmt.Printf("Name: %s, Size: %d, Type: %s\n",
file.Name, file.Size, file.ContentType)
// Save file
if err := file.Save("./uploads/" + file.Name); err != nil {
c.JSON(500, map[string]string{"error": "failed to save"})
return
}
c.JSON(200, map[string]string{"filename": file.Name})
})
// Multiple files
r.POST("/upload-many", func(c *router.Context) {
files, err := c.Files("documents")
if err != nil {
c.JSON(400, map[string]string{"error": "documents required"})
return
}
for _, f := range files {
f.Save("./uploads/" + f.Name)
}
c.JSON(200, map[string]int{"count": len(files)})
})
Performance Tips
Extract Data Immediately
// ✅ GOOD: Extract data early
func handler(c *router.Context) {
userID := c.Param("id")
query := c.Query("q")
// Use extracted data
result := processData(userID, query)
c.JSON(200, result)
}
// ❌ BAD: Don't store context reference
var globalContext *router.Context
func handler(c *router.Context) {
globalContext = c // Memory leak!
}
Choose the Right Response Method
// Use PureJSON for HTML content (35% faster than JSON)
c.PureJSON(200, dataWithHTMLStrings)
// Use Data() for binary (98% faster than JSON)
c.Data(200, "image/png", imageBytes)
// Avoid YAML in hot paths (9x slower than JSON)
// c.YAML(200, data) // Only for config/admin endpoints
Complete Example
package main
import (
"encoding/json"
"net/http"
"rivaas.dev/router"
)
func main() {
r := router.MustNew()
// Request parameters
r.GET("/users/:id", func(c *router.Context) {
id := c.Param("id")
c.JSON(200, map[string]string{"id": id})
})
// Query parameters
r.GET("/search", func(c *router.Context) {
q := c.Query("q")
c.JSON(200, map[string]string{"query": q})
})
// Form data
r.POST("/login", func(c *router.Context) {
username := c.FormValue("username")
c.JSON(200, map[string]string{"username": username})
})
// JSON request body
r.POST("/users", func(c *router.Context) {
var req struct {
Name string `json:"name"`
Email string `json:"email"`
}
if err := json.NewDecoder(c.Request.Body).Decode(&req); err != nil {
c.JSON(400, map[string]string{"error": "Invalid JSON"})
return
}
c.JSON(201, req)
})
// Headers and cookies
r.GET("/info", func(c *router.Context) {
userAgent := c.Request.Header.Get("User-Agent")
session, _ := c.GetCookie("session_id")
c.Header("X-Custom", "value")
c.JSON(200, map[string]string{
"user_agent": userAgent,
"session": session,
})
})
http.ListenAndServe(":8080", r)
}
Next Steps
- Request Binding: Learn about automatic request binding
- Validation: See request validation strategies
- Response Rendering: Explore response rendering options
- API Reference: See complete Context API
Feedback
Was this page helpful?
Glad to hear it! Please tell us how we can improve.
Sorry to hear that. Please tell us how we can improve.