Response Handling 
Learn how to handle and modify HTTP responses in your Go plugins.
Response Object 
Access and modify responses through the Response object:
go
phase.ResponseFilter(func(ctx *sdk.PhaseResponseFilter) {
    res := ctx.Response()
    
    // Set status
    res.SetStatus(200)
    
    // Set headers
    res.SetHeader("X-Server", "Nylon")
    
    // Remove headers
    res.RemoveHeader("Server")
    
    ctx.Next()
})Response Phases 
ResponseFilter 
Modify response headers and status before body is processed:
go
phase.ResponseFilter(func(ctx *sdk.PhaseResponseFilter) {
    res := ctx.Response()
    
    // Inject security headers
    res.SetHeader("X-Frame-Options", "DENY")
    res.SetHeader("X-Content-Type-Options", "nosniff")
    
    ctx.Next()
})ResponseBodyFilter 
Modify response body content:
go
phase.ResponseBodyFilter(func(ctx *sdk.PhaseResponseBodyFilter) {
    res := ctx.Response()
    
    // Read body
    body := res.ReadBody()
    
    // Modify body
    modifiedBody := append(body, []byte("\n<!-- Injected -->")...)
    
    // Write modified body
    res.BodyRaw(modifiedBody)
    
    ctx.Next()
})Important: When modifying body, you must handle Content-Length:
go
phase.ResponseFilter(func(ctx *sdk.PhaseResponseFilter) {
    // Remove Content-Length
    res.RemoveHeader("Content-Length")
    
    // Use chunked encoding
    res.SetHeader("Transfer-Encoding", "chunked")
    
    ctx.Next()
})Logging 
Access final response information:
go
phase.Logging(func(ctx *sdk.PhaseLogging) {
    req := ctx.Request()
    res := ctx.Response()
    
    log.Printf("%s %s -> %d (%d bytes, %dms)",
        req.Method(),
        req.Path(),
        res.Status(),
        res.Bytes(),
        res.Duration(),
    )
    
    ctx.Next()
})Response Methods 
SetStatus(code int) 
Set HTTP status code:
go
res.SetStatus(200)  // OK
res.SetStatus(404)  // Not Found
res.SetStatus(500)  // Internal Server ErrorStatus() int 
Get response status:
go
status := res.Status()
// 200, 404, 500, etc.
if status >= 500 {
    // Server error
}SetHeader(name, value string) 
Set response header:
go
res.SetHeader("Content-Type", "application/json")
res.SetHeader("Cache-Control", "no-cache")
res.SetHeader("X-Server", "Nylon")RemoveHeader(name string) 
Remove response header:
go
res.RemoveHeader("Server")
res.RemoveHeader("X-Powered-By")Headers() map[string]string 
Get all response headers:
go
headers := res.Headers()
contentType := headers["content-type"]
cacheControl := headers["cache-control"]BodyRaw(data []byte) 
Set response body (raw bytes):
go
body := []byte("Hello, World!")
res.BodyRaw(body)BodyText(text string) 
Set response body (text):
go
res.BodyText("Hello, World!")BodyJSON(data interface{}) 
Set response body (JSON):
go
data := map[string]interface{}{
    "message": "Success",
    "code": 200,
}
res.BodyJSON(data)ReadBody() []byte 
Read response body (in ResponseBodyFilter phase):
go
phase.ResponseBodyFilter(func(ctx *sdk.PhaseResponseBodyFilter) {
    res := ctx.Response()
    body := res.ReadBody()
    
    // Modify body
    // ...
    
    ctx.Next()
})Bytes() int64 
Get response body size:
go
bytes := res.Bytes()
fmt.Printf("Response size: %d bytes\n", bytes)Duration() int64 
Get request duration in milliseconds:
go
duration := res.Duration()
fmt.Printf("Request took: %dms\n", duration)Error() string 
Get error message (if any):
go
err := res.Error()
if err != "" {
    log.Printf("Error: %s\n", err)
}Examples 
Security Headers 
go
phase.ResponseFilter(func(ctx *sdk.PhaseResponseFilter) {
    res := ctx.Response()
    
    // Security headers
    res.SetHeader("X-Frame-Options", "DENY")
    res.SetHeader("X-Content-Type-Options", "nosniff")
    res.SetHeader("X-XSS-Protection", "1; mode=block")
    res.SetHeader("Referrer-Policy", "no-referrer")
    res.SetHeader("Content-Security-Policy", "default-src 'self'")
    
    // Remove server info
    res.RemoveHeader("Server")
    res.RemoveHeader("X-Powered-By")
    
    ctx.Next()
})CORS Headers 
go
phase.ResponseFilter(func(ctx *sdk.PhaseResponseFilter) {
    res := ctx.Response()
    
    res.SetHeader("Access-Control-Allow-Origin", "*")
    res.SetHeader("Access-Control-Allow-Methods", "GET, POST, PUT, DELETE")
    res.SetHeader("Access-Control-Allow-Headers", "Content-Type, Authorization")
    res.SetHeader("Access-Control-Max-Age", "3600")
    
    ctx.Next()
})Custom Error Responses 
go
phase.ResponseFilter(func(ctx *sdk.PhaseResponseFilter) {
    res := ctx.Response()
    
    status := res.Status()
    
    if status == 404 {
        res.SetHeader("Content-Type", "application/json")
        res.BodyJSON(map[string]interface{}{
            "error": "Not Found",
            "code": 404,
            "message": "The requested resource was not found",
        })
    }
    
    if status >= 500 {
        res.SetHeader("Content-Type", "application/json")
        res.BodyJSON(map[string]interface{}{
            "error": "Internal Server Error",
            "code": status,
        })
    }
    
    ctx.Next()
})Response Body Injection 
go
phase.ResponseFilter(func(ctx *sdk.PhaseResponseFilter) {
    // Prepare for body modification
    res.RemoveHeader("Content-Length")
    res.SetHeader("Transfer-Encoding", "chunked")
    ctx.Next()
})
phase.ResponseBodyFilter(func(ctx *sdk.PhaseResponseBodyFilter) {
    res := ctx.Response()
    body := res.ReadBody()
    
    // Inject analytics script before </body>
    script := []byte(`<script src="/analytics.js"></script></body>`)
    modifiedBody := bytes.Replace(body, []byte("</body>"), script, 1)
    
    res.BodyRaw(modifiedBody)
    ctx.Next()
})Response Compression 
go
phase.ResponseBodyFilter(func(ctx *sdk.PhaseResponseBodyFilter) {
    res := ctx.Response()
    body := res.ReadBody()
    
    // Compress if large
    if len(body) > 1024 {
        var buf bytes.Buffer
        gz := gzip.NewWriter(&buf)
        gz.Write(body)
        gz.Close()
        
        res.SetHeader("Content-Encoding", "gzip")
        res.RemoveHeader("Content-Length")
        res.BodyRaw(buf.Bytes())
    }
    
    ctx.Next()
})Cache Control 
go
phase.ResponseFilter(func(ctx *sdk.PhaseResponseFilter) {
    req := ctx.Request()
    res := ctx.Response()
    
    path := req.Path()
    
    // Static assets - cache for 1 year
    if strings.HasPrefix(path, "/static/") {
        res.SetHeader("Cache-Control", "public, max-age=31536000, immutable")
    }
    
    // API - no cache
    if strings.HasPrefix(path, "/api/") {
        res.SetHeader("Cache-Control", "no-store, no-cache, must-revalidate")
    }
    
    ctx.Next()
})Response Transformation 
go
phase.ResponseBodyFilter(func(ctx *sdk.PhaseResponseBodyFilter) {
    res := ctx.Response()
    
    // Only transform JSON
    contentType := res.Headers()["content-type"]
    if !strings.Contains(contentType, "application/json") {
        ctx.Next()
        return
    }
    
    body := res.ReadBody()
    
    // Parse JSON
    var data map[string]interface{}
    json.Unmarshal(body, &data)
    
    // Add metadata
    data["_meta"] = map[string]interface{}{
        "timestamp": time.Now().Unix(),
        "version": "1.0",
    }
    
    // Encode back to JSON
    modifiedBody, _ := json.Marshal(data)
    res.BodyRaw(modifiedBody)
    
    ctx.Next()
})Performance Monitoring 
go
phase.Logging(func(ctx *sdk.PhaseLogging) {
    req := ctx.Request()
    res := ctx.Response()
    
    duration := res.Duration()
    status := res.Status()
    
    // Log slow requests
    if duration > 1000 {
        log.Printf("[SLOW] %s %s took %dms (status: %d)",
            req.Method(),
            req.Path(),
            duration,
            status,
        )
    }
    
    // Log errors
    if status >= 500 {
        log.Printf("[ERROR] %s %s failed with %d (error: %s)",
            req.Method(),
            req.Path(),
            status,
            res.Error(),
        )
    }
    
    ctx.Next()
})Access Logging 
go
phase.Logging(func(ctx *sdk.PhaseLogging) {
    req := ctx.Request()
    res := ctx.Response()
    
    log.Printf("%s - [%s] \"%s %s\" %d %d %dms \"%s\"",
        req.ClientIP(),
        time.Now().Format("02/Jan/2006:15:04:05 -0700"),
        req.Method(),
        req.Path(),
        res.Status(),
        res.Bytes(),
        res.Duration(),
        req.Header("User-Agent"),
    )
    
    ctx.Next()
})Early Response 
Send response without contacting backend:
go
phase.RequestFilter(func(ctx *sdk.PhaseRequestFilter) {
    req := ctx.Request()
    res := ctx.Response()
    
    // Serve from cache
    if cached := getFromCache(req.Path()); cached != nil {
        res.SetStatus(200)
        res.SetHeader("X-Cache", "HIT")
        res.BodyRaw(cached)
        res.RemoveHeader("Content-Length")
        res.SetHeader("Transfer-Encoding", "chunked")
        ctx.End()
        return
    }
    
    res.SetHeader("X-Cache", "MISS")
    ctx.Next()
})Streaming Responses 
For streaming responses (SSE, chunked):
go
phase.RequestFilter(func(ctx *sdk.PhaseRequestFilter) {
    res := ctx.Response()
    
    res.SetStatus(200)
    res.SetHeader("Content-Type", "text/event-stream")
    res.SetHeader("Cache-Control", "no-cache")
    
    stream, err := res.Stream()
    if err != nil {
        res.SetStatus(500)
        res.BodyText("Stream error")
        res.RemoveHeader("Content-Length")
        res.SetHeader("Transfer-Encoding", "chunked")
        ctx.End()
        return
    }
    
    // Write chunks
    stream.Write([]byte("data: hello\n\n"))
    stream.Write([]byte("data: world\n\n"))
    
    // End stream
    stream.End()
})Best Practices 
1. Always Call Next() 
go
// ✅ Good
phase.ResponseFilter(func(ctx *sdk.PhaseResponseFilter) {
    res.SetHeader("X-Server", "Nylon")
    ctx.Next()  // Don't forget!
})
// ❌ Bad
phase.ResponseFilter(func(ctx *sdk.PhaseResponseFilter) {
    res.SetHeader("X-Server", "Nylon")
    // Missing ctx.Next() - response will hang
})2. Handle Body Modification Correctly 
go
// ✅ Good
phase.ResponseFilter(func(ctx *sdk.PhaseResponseFilter) {
    res.RemoveHeader("Content-Length")
    res.SetHeader("Transfer-Encoding", "chunked")
    ctx.Next()
})
phase.ResponseBodyFilter(func(ctx *sdk.PhaseResponseBodyFilter) {
    body := ctx.Response().ReadBody()
    // Modify body...
    ctx.Response().BodyRaw(modifiedBody)
    ctx.Next()
})3. Check Content-Type 
go
contentType := res.Headers()["content-type"]
if strings.Contains(contentType, "application/json") {
    // Process JSON
}4. Log in Logging Phase 
go
// ✅ Good - Use Logging phase
phase.Logging(func(ctx *sdk.PhaseLogging) {
    res := ctx.Response()
    log.Printf("Status: %d, Duration: %dms", res.Status(), res.Duration())
    ctx.Next()
})5. Set Headers Before Body 
go
// ✅ Good
res.SetStatus(200)
res.SetHeader("Content-Type", "application/json")
res.BodyJSON(data)See Also 
- Request Handling - Handle requests
- Plugin Phases - Understanding phases
- Go SDK - Complete SDK reference