Echo Response Headers
Introduction
HTTP headers are an essential part of the communication between client and server in web applications. Headers provide metadata about the request or response, allowing both parties to send additional information beyond the actual content being transferred. In this tutorial, we'll explore how to set, modify, and manage response headers in Echo, a high-performance web framework for Go.
Response headers allow you to control various aspects of how your response is processed by the client, including:
- Content type specifications
- Caching behavior
- Security policies
- Cross-origin resource sharing
- Authentication information
- Custom application-specific data
Understanding how to properly manage response headers is crucial for developing secure, efficient, and compliant web applications.
Basic Header Operations
Setting a Single Header
Echo makes it simple to set response headers using the Header
method of the response writer accessed through the context:
func handler(c echo.Context) error {
c.Response().Header().Set("Content-Type", "application/json")
return c.String(http.StatusOK, "Response with custom header")
}
This example sets the Content-Type
header to application/json
.
Setting Multiple Headers
You can set multiple headers by chaining the Set
method:
func handler(c echo.Context) error {
header := c.Response().Header()
header.Set("Content-Type", "application/json")
header.Set("Cache-Control", "no-cache")
header.Set("X-Custom-Header", "custom-value")
return c.JSON(http.StatusOK, map[string]string{"message": "Response with multiple headers"})
}
Adding vs. Setting Headers
The Set
method replaces any existing header value, while the Add
method appends a value to an existing header:
func handler(c echo.Context) error {
header := c.Response().Header()
// Set replaces any existing values
header.Set("X-Custom-Header", "value1")
// Add appends to existing values
header.Add("X-Custom-Header", "value2")
// The client will receive: X-Custom-Header: value1, value2
return c.String(http.StatusOK, "Response with added headers")
}
Common HTTP Headers
Content-Type Header
One of the most common headers is Content-Type
, which tells the client what kind of data you're sending:
func handler(c echo.Context) error {
c.Response().Header().Set("Content-Type", "text/html; charset=UTF-8")
return c.HTML(http.StatusOK, "<h1>Hello World</h1>")
}
Echo often sets this header automatically based on the response method you use (JSON()
, HTML()
, etc.), but you can override it when needed.
Cache Control Headers
Controlling how your response is cached can significantly impact performance:
func handler(c echo.Context) error {
header := c.Response().Header()
// Prevent caching
header.Set("Cache-Control", "no-store, no-cache, must-revalidate")
header.Set("Pragma", "no-cache")
header.Set("Expires", "0")
return c.String(http.StatusOK, "This response will not be cached")
}
Or to encourage caching:
func handler(c echo.Context) error {
// Cache for 1 hour
c.Response().Header().Set("Cache-Control", "public, max-age=3600")
return c.String(http.StatusOK, "This response can be cached for 1 hour")
}
Security Headers
Modern web applications should include security headers:
func setSecurityHeaders(next echo.HandlerFunc) echo.HandlerFunc {
return func(c echo.Context) error {
header := c.Response().Header()
// Prevent clickjacking
header.Set("X-Frame-Options", "DENY")
// Enable XSS protection
header.Set("X-XSS-Protection", "1; mode=block")
// Prevent MIME type sniffing
header.Set("X-Content-Type-Options", "nosniff")
// Implement Content Security Policy
header.Set("Content-Security-Policy", "default-src 'self'")
// HTTP Strict Transport Security
header.Set("Strict-Transport-Security", "max-age=31536000; includeSubDomains")
return next(c)
}
}
func main() {
e := echo.New()
e.Use(setSecurityHeaders)
// Register your routes...
e.Start(":8080")
}
CORS Headers
Cross-Origin Resource Sharing (CORS) headers control which domains can access your resources:
func main() {
e := echo.New()
// Basic CORS middleware
e.Use(middleware.CORS())
// Custom CORS configuration
e.Use(middleware.CORSWithConfig(middleware.CORSConfig{
AllowOrigins: []string{"https://example.com", "https://app.example.com"},
AllowMethods: []string{http.MethodGet, http.MethodPost, http.MethodPut},
AllowHeaders: []string{echo.HeaderOrigin, echo.HeaderContentType, echo.HeaderAccept},
MaxAge: 86400, // 24 hours
}))
// Routes
e.Start(":8080")
}
Custom Headers for API Versioning
You can use custom headers to implement API versioning:
func apiVersionMiddleware(next echo.HandlerFunc) echo.HandlerFunc {
return func(c echo.Context) error {
// Add API version header
c.Response().Header().Set("X-API-Version", "1.2.3")
return next(c)
}
}
func main() {
e := echo.New()
e.Use(apiVersionMiddleware)
// Routes...
e.Start(":8080")
}
Real-world Example: File Download with Headers
Here's a practical example of setting appropriate headers for a file download:
func downloadHandler(c echo.Context) error {
filename := "report.pdf"
filepath := "/path/to/files/" + filename
// Open the file
file, err := os.Open(filepath)
if err != nil {
return c.JSON(http.StatusInternalServerError, map[string]string{"error": "File not found"})
}
defer file.Close()
// Get file stats
stats, err := file.Stat()
if err != nil {
return c.JSON(http.StatusInternalServerError, map[string]string{"error": "Could not get file info"})
}
// Set appropriate headers
header := c.Response().Header()
header.Set("Content-Disposition", fmt.Sprintf("attachment; filename=%q", filename))
header.Set("Content-Type", "application/pdf")
header.Set("Content-Length", fmt.Sprint(stats.Size()))
header.Set("Cache-Control", "no-cache")
// Stream the file to the client
return c.Stream(http.StatusOK, "application/pdf", file)
}
Response Header Middleware
For consistent header application across your application, you might want to create a custom middleware:
func StandardHeadersMiddleware(next echo.HandlerFunc) echo.HandlerFunc {
return func(c echo.Context) error {
// Process the request
err := next(c)
// Add standard headers after the handler is executed
header := c.Response().Header()
header.Set("X-Powered-By", "MyAwesomeApp")
header.Set("Server", "MyCustomServer")
return err
}
}
func main() {
e := echo.New()
e.Use(StandardHeadersMiddleware)
// Routes...
e.Start(":8080")
}
Reading Response Headers
While less common, you might need to read the headers you've set:
func handler(c echo.Context) error {
// Set a header
c.Response().Header().Set("X-Request-ID", "123456")
// Read the header we just set
requestID := c.Response().Header().Get("X-Request-ID")
return c.JSON(http.StatusOK, map[string]string{
"message": "Header check",
"request_id": requestID,
})
}
Summary
Response headers are powerful tools for controlling how your web application communicates with clients:
- Use
c.Response().Header().Set()
to set or replace header values - Use
c.Response().Header().Add()
to append to existing header values - Common headers control content type, caching, security, and CORS behavior
- Middleware can apply consistent headers across your entire application
- Custom headers allow you to implement API versioning or pass application-specific metadata
By properly managing response headers, you can improve security, performance, and the overall user experience of your web applications.
Additional Resources
Exercises
- Create a middleware that adds response headers to track timing information (how long the request took to process).
- Implement a content negotiation middleware that sets appropriate
Content-Type
headers based on the client'sAccept
header. - Build an API endpoint that returns different responses based on a custom
X-API-Version
header. - Create a file upload endpoint that properly sets content type and disposition headers for various file types.
If you spot any mistakes on this website, please let me know at [email protected]. I’d greatly appreciate your feedback! :)