Skip to main content

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:

go
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:

go
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:

go
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:

go
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:

go
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:

go
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:

go
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:

go
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:

go
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:

go
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:

go
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:

go
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

  1. Create a middleware that adds response headers to track timing information (how long the request took to process).
  2. Implement a content negotiation middleware that sets appropriate Content-Type headers based on the client's Accept header.
  3. Build an API endpoint that returns different responses based on a custom X-API-Version header.
  4. 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! :)