gofr-dev/gofr · error

internal server error

Error message

internal server error

What it means

errEmptyResponse is the sentinel error in pkg/gofr/http/responder.go that gofr's HTTP responder returns when a handler yields a nil/empty response that cannot be serialized into a valid HTTP reply. Rather than sending an empty body with a 200, the framework surfaces this as an 'internal server error' to the client, since it indicates the handler failed to produce any response payload.

Solutions

  1. Inspect the handler returning the empty response and ensure every code path returns a non-nil response object or a real error
  2. Log the handler name and inputs to find which route produced the empty payload
  3. Add unit tests (like Test_getPublicKeys) covering all return paths of the handler
  4. If nil is legitimate, return an explicit empty-but-non-nil response struct instead

Example fix

// before
func handler(ctx *gofr.Context) (any, error) {
    if ctx.Param("id") == "" {
        return nil, nil // yields internal server error
    }
    return data, nil
}
// after
func handler(ctx *gofr.Context) (any, error) {
    if ctx.Param("id") == "" {
        return emptyResponse{}, nil
    }
    return data, nil
}
Defensive patterns

Strategy: validation

Validate before calling

resp, err := handler(ctx)
if err != nil {
    return err
}
if resp == nil {
    return errors.New("handler returned nil response")
}

Type guard

func isNilResponse(v any) bool {
    if v == nil { return true }
    rv := reflect.ValueOf(v)
    switch rv.Kind() {
    case reflect.Ptr, reflect.Map, reflect.Slice, reflect.Interface:
        return rv.IsNil()
    }
    return false
}

Try / catch

if resp, err := doRequest(client, url); err != nil {
    if errors.Is(err, errEmptyResponse) {
        log.Printf("handler produced no response: %v", err)
        http.Error(w, "no content produced", http.StatusUnprocessableEntity)
        return
    }
    return err
}

Prevention

When it happens

Trigger: A handler registered via gofr returns nil (or a zero-value interface) as its response object, so getPublicKeys/determineResponse in the responder pipeline find nothing to encode and errEmptyResponse is raised.

Common situations: Handler logic that forgets its return statement value or returns nil on an early-exit path; refactoring a handler to a typed response but leaving a nil-returning branch; middleware swallowing the response object.

Understand the failure class

Related errors


AI-assisted analysis of gofr-dev/gofr@187eb24962 (2026-09-01). Data as JSON: /api/errors/1ce10042325b8dc2. Report an issue: GitHub.

Appendix: source

Thrown at pkg/gofr/http/responder.go:15

package http

import (
	"bytes"
	"encoding/json"
	"errors"
	"net/http"
	"reflect"
	"sync"

	resTypes "gofr.dev/pkg/gofr/http/response"
)

var (
	errEmptyResponse = errors.New("internal server error")
)

// maxRespPooledBuf caps the capacity of a response buffer returned to the pool
// so an occasional very large response does not permanently inflate every
// pooled buffer.
const maxRespPooledBuf = 64 << 10

// initialRespBufCap is the starting capacity of a freshly minted pooled buffer,
// sized to hold a typical small JSON response without a reslice.
const initialRespBufCap = 512

// respBufPool holds reusable buffers for encoding JSON response bodies. Encoding
// into a pooled buffer avoids the fresh []byte json.Marshal returns on every
// response and collapses the body + trailing newline into a single Write. A
// process-wide pool is the idiomatic shape for this and is safe for concurrent
// use by construction.
//
//nolint:gochecknoglobals // process-wide pool of reusable response-encode buffers.

View on GitHub (pinned to 187eb24962)