fcgx
Error Handling
Error Handling
fcgx provides structured sentinel errors for robust error handling using Go's errors.Is().
Sentinel Errors
| Error | Description |
|---|---|
ErrClientClosed |
Client connection has been closed |
ErrTimeout |
Operation timed out |
ErrContextCancelled |
Context was cancelled |
ErrUnexpectedEOF |
Unexpected end of response |
ErrInvalidResponse |
Malformed response from server |
ErrPHPFPM |
PHP-FPM specific error |
ErrConnect |
Failed to establish connection |
ErrWrite |
Failed to write to connection |
ErrRead |
Failed to read from connection |
Using errors.Is
import "errors"
resp, err := client.Get(ctx, params)
if err != nil {
switch {
case errors.Is(err, fcgx.ErrTimeout):
log.Println("Request timed out, retrying...")
return retry(ctx, params)
case errors.Is(err, fcgx.ErrContextCancelled):
log.Println("Request was cancelled")
return nil
case errors.Is(err, fcgx.ErrClientClosed):
log.Println("Client closed, reconnecting...")
return reconnect()
case errors.Is(err, fcgx.ErrConnect):
log.Println("Connection failed, PHP-FPM may be down")
return err
default:
log.Printf("Unexpected error: %v\n", err)
return err
}
}
Error Context
Errors include context information for debugging:
resp, err := client.Get(ctx, params)
if err != nil {
// Error message includes context:
// "fcgx: timeout: timeout while reading header: i/o timeout"
fmt.Println(err)
}
Timeout Handling
ctx, cancel := context.WithTimeout(context.Background(), 2*time.Second)
defer cancel()
resp, err := client.Get(ctx, params)
if err != nil {
if errors.Is(err, fcgx.ErrTimeout) {
// Handle timeout - maybe retry with longer timeout
log.Println("Request timed out")
}
}
Connection Errors
client, err := fcgx.DialContext(ctx, "unix", "/var/run/php-fpm.sock")
if err != nil {
if errors.Is(err, fcgx.ErrConnect) {
log.Println("Cannot connect to PHP-FPM")
log.Println("Check if PHP-FPM is running and socket exists")
}
return err
}
Retry Pattern
func requestWithRetry(ctx context.Context, client *fcgx.Client, params map[string]string, maxRetries int) (*http.Response, error) {
var lastErr error
for i := 0; i < maxRetries; i++ {
resp, err := client.Get(ctx, params)
if err == nil {
return resp, nil
}
lastErr = err
// Only retry on timeout errors
if !errors.Is(err, fcgx.ErrTimeout) {
return nil, err
}
log.Printf("Attempt %d failed: %v, retrying...", i+1, err)
time.Sleep(time.Duration(i+1) * 100 * time.Millisecond)
}
return nil, fmt.Errorf("max retries exceeded: %w", lastErr)
}
Health Check Example
func checkFPMHealth(socketPath string) error {
ctx, cancel := context.WithTimeout(context.Background(), 2*time.Second)
defer cancel()
client, err := fcgx.DialContext(ctx, "unix", socketPath)
if err != nil {
if errors.Is(err, fcgx.ErrConnect) {
return fmt.Errorf("php-fpm not responding: %w", err)
}
return err
}
defer client.Close()
params := map[string]string{
"SCRIPT_NAME": "/ping",
"SCRIPT_FILENAME": "/ping",
}
resp, err := client.Get(ctx, params)
if err != nil {
if errors.Is(err, fcgx.ErrTimeout) {
return fmt.Errorf("php-fpm responding slowly: %w", err)
}
return err
}
defer resp.Body.Close()
body, _ := fcgx.ReadBody(resp)
if string(body) != "pong" {
return fmt.Errorf("unexpected ping response: %s", body)
}
return nil
}
Next Steps
- API Reference - Complete API documentation