gRPC-GoSeniorSystem design
Как сопоставить gRPC status codes с HTTP status codes при использовании gateway?
gRPC статусы маппируются в HTTP по стандартной таблице: NOT_FOUND=404, UNAUTHENTICATED=401, PERMISSION_DENIED=403, UNAVAILABLE=503, RESOURCE_EXHAUSTED=429. grpc-gateway применяет маппинг автоматически через runtime.HTTPStatusFromCode.
Маппинг gRPC Status Codes в HTTP Status Codes
При использовании gRPC-Gateway или любого другого HTTP-to-gRPC транслятора необходимо корректно преобразовывать gRPC-коды ошибок в HTTP-статусы. Google определил официальную таблицу соответствия, которую реализует пакет google.golang.org/grpc/codes.
Официальная таблица маппинга
OK(0) → 200 OKCANCELLED(1) → 499 Client Closed RequestUNKNOWN(2) → 500 Internal Server ErrorINVALID_ARGUMENT(3) → 400 Bad RequestDEADLINE_EXCEEDED(4) → 504 Gateway TimeoutNOT_FOUND(5) → 404 Not FoundALREADY_EXISTS(6) → 409 ConflictPERMISSION_DENIED(7) → 403 ForbiddenRESOURCE_EXHAUSTED(8) → 429 Too Many RequestsFAILED_PRECONDITION(9) → 400 Bad RequestABORTED(10) → 409 ConflictOUT_OF_RANGE(11) → 400 Bad RequestUNIMPLEMENTED(12) → 501 Not ImplementedINTERNAL(13) → 500 Internal Server ErrorUNAVAILABLE(14) → 503 Service UnavailableDATA_LOSS(15) → 500 Internal Server ErrorUNAUTHENTICATED(16) → 401 Unauthorized
Реализация через grpc-gateway
grpc-gateway автоматически применяет этот маппинг, но его можно переопределить через кастомный ServeMuxOption:
import (
"net/http"
"github.com/grpc-ecosystem/grpc-gateway/v2/runtime"
"google.golang.org/grpc/codes"
"google.golang.org/grpc/status"
)
func customErrorHandler(
ctx context.Context,
mux *runtime.ServeMux,
marshaler runtime.Marshaler,
w http.ResponseWriter,
r *http.Request,
err error,
) {
st := status.Convert(err)
httpCode := runtime.HTTPStatusFromCode(st.Code())
// Кастомизация: FAILED_PRECONDITION -> 422 Unprocessable Entity
if st.Code() == codes.FailedPrecondition {
httpCode = http.StatusUnprocessableEntity
}
w.Header().Set("Content-Type", "application/json")
w.WriteHeader(httpCode)
body, _ := marshaler.Marshal(map[string]interface{}{
"code": st.Code().String(),
"message": st.Message(),
"details": st.Details(),
})
w.Write(body)
}
mux := runtime.NewServeMux(
runtime.WithErrorHandler(customErrorHandler),
)
Использование runtime.HTTPStatusFromCode
import "github.com/grpc-ecosystem/grpc-gateway/v2/runtime"
// Получение HTTP-кода из gRPC-кода
httpStatus := runtime.HTTPStatusFromCode(codes.NotFound) // 404
httpStatus = runtime.HTTPStatusFromCode(codes.Unauthenticated) // 401
httpStatus = runtime.HTTPStatusFromCode(codes.ResourceExhausted) // 429
Передача деталей ошибки через status.Details
import (
"google.golang.org/grpc/codes"
"google.golang.org/grpc/status"
"google.golang.org/genproto/googleapis/rpc/errdetails"
)
func (s *server) CreateUser(ctx context.Context, req *pb.CreateUserRequest) (*pb.User, error) {
st := status.New(codes.AlreadyExists, "user already exists")
st, _ = st.WithDetails(&errdetails.ErrorInfo{
Reason: "USER_ALREADY_EXISTS",
Domain: "myservice.example.com",
})
return nil, st.Err()
}
// HTTP: 409 Conflict + JSON с details
Подводные камни
CANCELLEDмаппируется в 499 — нестандартный код nginx; некоторые клиенты его не понимают. Рассмотрите переопределение на 400 или 503.FAILED_PRECONDITIONиOUT_OF_RANGEоба маппируются в 400 — это семантически разные ошибки; часто нужно переопределять на 422.- Не используйте
INTERNALдля валидационных ошибок — клиент получит 500 и не поймёт, что запрос неверный. - grpc-gateway по умолчанию не включает details в JSON-ответ без кастомного marshaler — добавьте явно.
- HTTP/1.1 клиенты не получают gRPC-трейлеры с кодами ошибок — весь маппинг должен идти через HTTP status code.
- Код
UNAUTHENTICATED→ 401, а не 403 (403 = PERMISSION_DENIED) — частая путаница при проектировании API. - При использовании Envoy как gateway маппинг может отличаться от grpc-gateway — сверяйтесь с документацией конкретного proxy.
DEADLINE_EXCEEDED→ 504, но если timeout истёк на стороне клиента до получения ответа — клиент видит network error, а не 504.
Common mistakes
- Давать ответ про сопоставление gRPC и HTTP status codes только на уровне определения, не показывая поведение в реальном приложении.
- Игнорировать границы ответственности вокруг темы «сопоставление gRPC и HTTP status codes»: кто отменяет работу, кто владеет ресурсом и где формируется ответ клиенту.
- Не связывать сопоставление gRPC и HTTP status codes с observability, тестированием или безопасностью, когда это влияет на продакшен-поведение.
What the interviewer is testing
- Точно объясняет, что именно делает сопоставление gRPC и HTTP status codes и где это используется в Go-коде.
- Связывает сопоставление gRPC и HTTP status codes с корректным lifecycle запроса, отменой, конкурентностью или конфигурацией сервера там, где это уместно.
- Не изобретает API и опирается на реальные контракты официальной документации.